Merge branch 'main' into issue_6312

This commit is contained in:
Nicolas Raoul 2025-06-25 23:06:36 +09:00 committed by GitHub
commit 01b0d5251b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 153 additions and 77 deletions

View file

@ -24,8 +24,8 @@ android {
applicationId = "fr.free.nrw.commons" applicationId = "fr.free.nrw.commons"
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 1052 versionCode = 1053
versionName = "5.4.1" versionName = "5.5.0"
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@ -347,6 +347,7 @@ dependencies {
// Kotlin + coroutines // Kotlin + coroutines
implementation(libs.androidx.work.runtime.ktx) implementation(libs.androidx.work.runtime.ktx)
implementation(libs.androidx.work.runtime) implementation(libs.androidx.work.runtime)
implementation(libs.kotlinx.coroutines.rx2)
testImplementation(libs.androidx.work.testing) testImplementation(libs.androidx.work.testing)
//Glide //Glide

View file

@ -66,6 +66,9 @@
# Application classes that will be serialized/deserialized over Gson # Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { *; } -keep class com.google.gson.examples.android.model.** { *; }
# Prevent R8 from obfuscating project classes used by Gson for parsing
-keep class fr.free.nrw.commons.fileusages.** { *; }
# Prevent proguard from stripping interface information from TypeAdapterFactory, # Prevent proguard from stripping interface information from TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * implements com.google.gson.TypeAdapterFactory -keep class * implements com.google.gson.TypeAdapterFactory

View file

@ -53,7 +53,6 @@ class DeleteHelper @Inject constructor(
media: Media?, media: Media?,
reason: String? reason: String?
): Single<Boolean>? { ): Single<Boolean>? {
if(context == null && media == null) { if(context == null && media == null) {
return null return null
} }
@ -86,7 +85,6 @@ class DeleteHelper @Inject constructor(
* @return * @return
*/ */
private fun delete(media: Media, reason: String): Observable<Boolean> { private fun delete(media: Media, reason: String): Observable<Boolean> {
Timber.d("thread is delete %s", Thread.currentThread().name)
val summary = "Nominating ${media.filename} for deletion." val summary = "Nominating ${media.filename} for deletion."
val calendar = Calendar.getInstance() val calendar = Calendar.getInstance()
val fileDeleteString = """ val fileDeleteString = """

View file

@ -2,21 +2,19 @@ package fr.free.nrw.commons.delete
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import fr.free.nrw.commons.utils.DateUtil
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.profile.achievements.FeedbackResponse
import fr.free.nrw.commons.auth.SessionManager import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
import fr.free.nrw.commons.utils.DateUtil
import fr.free.nrw.commons.utils.ViewUtilWrapper import fr.free.nrw.commons.utils.ViewUtilWrapper
import io.reactivex.Single import io.reactivex.Single
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.rx2.rxSingle
import timber.log.Timber import timber.log.Timber
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton
/** /**
* This class handles the reason for deleting a Media object * This class handles the reason for deleting a Media object
@ -29,6 +27,8 @@ class ReasonBuilder @Inject constructor(
private val viewUtilWrapper: ViewUtilWrapper private val viewUtilWrapper: ViewUtilWrapper
) { ) {
private val defaultFileUsagePageSize = 10
/** /**
* To process the reason and append the media's upload date and uploaded_by_me string * To process the reason and append the media's upload date and uploaded_by_me string
* @param media * @param media
@ -39,7 +39,7 @@ class ReasonBuilder @Inject constructor(
if (media == null || reason == null) { if (media == null || reason == null) {
return Single.just("Not known") return Single.just("Not known")
} }
return fetchArticleNumber(media, reason) return getAndAppendFileUsage(media, reason)
} }
/** /**
@ -54,27 +54,36 @@ class ReasonBuilder @Inject constructor(
} }
} }
private fun fetchArticleNumber(media: Media, reason: String): Single<String> { private fun getAndAppendFileUsage(media: Media, reason: String): Single<String> {
return if (checkAccount()) { return rxSingle(context = Dispatchers.IO) {
okHttpJsonApiClient if (!checkAccount()) return@rxSingle ""
.getAchievements(sessionManager.userName)
.map { feedbackResponse -> appendArticlesUsed(feedbackResponse, media, reason) } try {
} else { val globalFileUsage = okHttpJsonApiClient.getGlobalFileUsages(
Single.just("") fileName = media.filename,
pageSize = defaultFileUsagePageSize
)
val globalUsages = globalFileUsage?.query?.pages?.sumOf { it.fileUsage.size } ?: 0
appendArticlesUsed(globalUsages, media, reason)
} catch (e: Exception) {
Timber.e(e, "Error fetching file usage")
throw e
}
} }
} }
/** /**
* Takes the uploaded_by_me string, the upload date, name of articles using images * Takes the uploaded_by_me string, the upload date, no. of articles using images
* and appends it to the received reason * and appends it to the received reason
* @param feedBack object * @param fileUsages No. of files/articles using this image
* @param media whose upload data is to be fetched * @param media whose upload data is to be fetched
* @param reason * @param reason string to be appended
*/ */
@SuppressLint("StringFormatInvalid") @SuppressLint("StringFormatInvalid")
private fun appendArticlesUsed(feedBack: FeedbackResponse, media: Media, reason: String): String { private fun appendArticlesUsed(fileUsages: Int, media: Media, reason: String): String {
val reason1Template = context.getString(R.string.uploaded_by_myself) val reason1Template = context.getString(R.string.uploaded_by_myself)
return reason + String.format(Locale.getDefault(), reason1Template, prettyUploadedDate(media), feedBack.articlesUsingImages) return reason + String.format(Locale.getDefault(), reason1Template, prettyUploadedDate(media), fileUsages)
.also { Timber.i("New Reason %s", it) } .also { Timber.i("New Reason %s", it) }
} }

View file

@ -74,7 +74,6 @@ import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.CameraPosition import fr.free.nrw.commons.CameraPosition
import fr.free.nrw.commons.CommonsApplication import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.CommonsApplication.Companion.instance import fr.free.nrw.commons.CommonsApplication.Companion.instance
import fr.free.nrw.commons.locationpicker.LocationPicker
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.MediaDataExtractor import fr.free.nrw.commons.MediaDataExtractor
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
@ -102,6 +101,7 @@ import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity
import fr.free.nrw.commons.kvstore.JsonKvStore import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.language.AppLanguageLookUpTable import fr.free.nrw.commons.language.AppLanguageLookUpTable
import fr.free.nrw.commons.location.LocationServiceManager import fr.free.nrw.commons.location.LocationServiceManager
import fr.free.nrw.commons.locationpicker.LocationPicker
import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider
import fr.free.nrw.commons.profile.ProfileActivity import fr.free.nrw.commons.profile.ProfileActivity
import fr.free.nrw.commons.review.ReviewHelper import fr.free.nrw.commons.review.ReviewHelper
@ -116,6 +116,7 @@ import fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources
import fr.free.nrw.commons.utils.PermissionUtils.PERMISSIONS_STORAGE import fr.free.nrw.commons.utils.PermissionUtils.PERMISSIONS_STORAGE
import fr.free.nrw.commons.utils.PermissionUtils.checkPermissionsAndPerformAction import fr.free.nrw.commons.utils.PermissionUtils.checkPermissionsAndPerformAction
import fr.free.nrw.commons.utils.PermissionUtils.hasPermission import fr.free.nrw.commons.utils.PermissionUtils.hasPermission
import fr.free.nrw.commons.utils.ViewUtil
import fr.free.nrw.commons.utils.ViewUtil.showShortToast import fr.free.nrw.commons.utils.ViewUtil.showShortToast
import fr.free.nrw.commons.utils.ViewUtilWrapper import fr.free.nrw.commons.utils.ViewUtilWrapper
import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage.Revision import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage.Revision
@ -125,6 +126,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.StringUtils
import timber.log.Timber import timber.log.Timber
import java.lang.String.format
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
import java.util.Objects import java.util.Objects
@ -1646,7 +1648,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
getString(R.string.cancel), getString(R.string.cancel),
{ {
val reason: String = input.text.toString() val reason: String = input.text.toString()
onDeleteClickeddialogtext(reason) onDeleteClickedDialogText(reason)
}, },
{}, {},
input input
@ -1700,26 +1702,48 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
resultSingle resultSingle
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { _ -> .subscribe(this::handleDeletionResult, this::handleDeletionError);
if (applicationKvStore.getBoolean(
String.format(
NOMINATING_FOR_DELETION_MEDIA, media!!.imageUrl
), false
)
) {
applicationKvStore.remove(
String.format(
NOMINATING_FOR_DELETION_MEDIA,
media!!.imageUrl
)
)
callback!!.nominatingForDeletion(index)
} }
/**
* Disables Progress Bar and Update delete button text.
*/
private fun disableProgressBar() {
activity?.run {
runOnUiThread(Runnable {
binding.progressBarDeletion.visibility = View.GONE
})
} ?: return // Prevent NullPointerException when fragment is not attached to activity
}
private fun handleDeletionResult(success: Boolean) {
if (success) {
binding.nominateDeletion.text = getString(R.string.nominated_for_deletion_btn)
ViewUtil.showLongSnackbar(requireView(), getString(R.string.nominated_for_deletion))
disableProgressBar()
checkAndClearDeletionFlag()
} else {
disableProgressBar()
}
}
private fun handleDeletionError(throwable: Throwable) {
throwable.printStackTrace()
disableProgressBar()
checkAndClearDeletionFlag()
}
private fun checkAndClearDeletionFlag() {
if (applicationKvStore
.getBoolean(format(NOMINATING_FOR_DELETION_MEDIA, media!!.imageUrl), false)
) {
applicationKvStore.remove(format(NOMINATING_FOR_DELETION_MEDIA, media!!.imageUrl))
callback!!.nominatingForDeletion(index)
} }
} }
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private fun onDeleteClickeddialogtext(reason: String) { private fun onDeleteClickedDialogText(reason: String) {
applicationKvStore.putBoolean( applicationKvStore.putBoolean(
String.format( String.format(
NOMINATING_FOR_DELETION_MEDIA, NOMINATING_FOR_DELETION_MEDIA,
@ -1736,22 +1760,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
resultSingletext resultSingletext
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { _ -> .subscribe(this::handleDeletionResult, this::handleDeletionError);
if (applicationKvStore.getBoolean(
String.format(
NOMINATING_FOR_DELETION_MEDIA, media!!.imageUrl
), false
)
) {
applicationKvStore.remove(
String.format(
NOMINATING_FOR_DELETION_MEDIA,
media!!.imageUrl
)
)
callback!!.nominatingForDeletion(index)
}
}
} }
private fun onSeeMoreClicked() { private fun onSeeMoreClicked() {

View file

@ -281,6 +281,7 @@ class OkHttpJsonApiClient @Inject constructor(
FeedbackResponse::class.java FeedbackResponse::class.java
) )
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace()
return@fromCallable FeedbackResponse(0, 0, 0, FeaturedImages(0, 0), 0, "") return@fromCallable FeedbackResponse(0, 0, 0, FeaturedImages(0, 0), 0, "")
} }
} }

View file

@ -472,7 +472,10 @@ class UploadWorker(
if (wikiDataPlace != null) { if (wikiDataPlace != null) {
if (!contribution.hasInvalidLocation()) { if (!contribution.hasInvalidLocation()) {
var revisionID: Long? = null var revisionID: Long? = null
val p18WasSkipped = !wikiDataPlace.imageValue.isNullOrBlank()
try { try {
if (!p18WasSkipped) {
// Only set P18 if the place does not already have a picture
revisionID = revisionID =
wikidataEditService.createClaim( wikidataEditService.createClaim(
wikiDataPlace, wikiDataPlace,
@ -490,8 +493,10 @@ class UploadWorker(
.blockingAwait() .blockingAwait()
Timber.d("Updated WikiItem place ${place.name} with image ${place.pic}") Timber.d("Updated WikiItem place ${place.name} with image ${place.pic}")
} }
showSuccessNotification(contribution)
} }
}
// Always show success notification, whether P18 was set or skipped
showSuccessNotification(contribution)
} catch (exception: Exception) { } catch (exception: Exception) {
Timber.e(exception) Timber.e(exception)
} }
@ -500,6 +505,7 @@ class UploadWorker(
wikidataEditService.handleImageClaimResult( wikidataEditService.handleImageClaimResult(
contribution.wikidataPlace!!, contribution.wikidataPlace!!,
revisionID, revisionID,
p18WasSkipped = p18WasSkipped
) )
} }
} else { } else {

View file

@ -196,13 +196,16 @@ class WikidataEditService @Inject constructor(
return wikidataClient.setClaim(claim, COMMONS_APP_TAG).blockingSingle() return wikidataClient.setClaim(claim, COMMONS_APP_TAG).blockingSingle()
} }
fun handleImageClaimResult(wikidataItem: WikidataItem, revisionId: Long?) { fun handleImageClaimResult(wikidataItem: WikidataItem, revisionId: Long?, p18WasSkipped: Boolean = false) {
if (revisionId != null) { if (revisionId != null) {
wikidataEditListener?.onSuccessfulWikidataEdit() wikidataEditListener?.onSuccessfulWikidataEdit()
showSuccessToast(wikidataItem.name) showSuccessToast(wikidataItem.name)
} else { } else if (!p18WasSkipped) {
Timber.d("Unable to make wiki data edit for entity %s", wikidataItem) Timber.d("Unable to make wiki data edit for entity %s", wikidataItem)
showLongToast(context, context.getString(R.string.wikidata_edit_failure)) showLongToast(context, context.getString(R.string.wikidata_edit_failure))
} else {
Timber.d("Wikidata edit skipped for entity %s because P18 already exists", wikidataItem)
// No error shown to user, as this is not a failure
} }
} }

View file

@ -180,7 +180,7 @@
<string name="tutorial_4_subtext_2">Kuvaus: Sydneyn oopperatalo lahden toiselta puolelta katsottuna</string> <string name="tutorial_4_subtext_2">Kuvaus: Sydneyn oopperatalo lahden toiselta puolelta katsottuna</string>
<string name="tutorial_4_subtext_3">Luokat: Sydneyn oopperatalo lännestä katsottuna, Sydneyn oopperatalo kaukaa katsottuna</string> <string name="tutorial_4_subtext_3">Luokat: Sydneyn oopperatalo lännestä katsottuna, Sydneyn oopperatalo kaukaa katsottuna</string>
<string name="welcome_wikipedia_text">Herätä Wikipedia-artikkelit eloon kuvillasi! Tuo kuvasi Wikipediaan.</string> <string name="welcome_wikipedia_text">Herätä Wikipedia-artikkelit eloon kuvillasi! Tuo kuvasi Wikipediaan.</string>
<string name="welcome_wikipedia_subtext">Wikipedian kuvat tulevat Wikimedia Commonsista.</string> <string name="welcome_wikipedia_subtext">Wikipedian kuvat ovat peräisin Wikimedia Commonsista.</string>
<string name="welcome_copyright_text">Kuvasi auttavat useita ihmisiä ympäri maailmaa artikkeleiden ymmärtämisessä.</string> <string name="welcome_copyright_text">Kuvasi auttavat useita ihmisiä ympäri maailmaa artikkeleiden ymmärtämisessä.</string>
<string name="welcome_copyright_subtext">Vältä tekijänoikeuksien alaista materiaalia, kuten julisteita, kirjan kansia ja useimpia Internetistä löydettyjä kuvia.</string> <string name="welcome_copyright_subtext">Vältä tekijänoikeuksien alaista materiaalia, kuten julisteita, kirjan kansia ja useimpia Internetistä löydettyjä kuvia.</string>
<string name="welcome_final_text">Luuletko ymmärtäneesi tämän?</string> <string name="welcome_final_text">Luuletko ymmärtäneesi tämän?</string>

View file

@ -136,7 +136,7 @@
<string name="no_parent_classes">%1$s مور ټولگې نه لري</string> <string name="no_parent_classes">%1$s مور ټولگې نه لري</string>
<string name="categories_skip_explanation">ويکي‌اوتوک خونديځ کې د خپلو انځورونو موندلو لپاره وېشنيزې ورگډې کړئ.\nوېشنيزو ورگډولو لپاره ټاپل پيل کړئ.</string> <string name="categories_skip_explanation">ويکي‌اوتوک خونديځ کې د خپلو انځورونو موندلو لپاره وېشنيزې ورگډې کړئ.\nوېشنيزو ورگډولو لپاره ټاپل پيل کړئ.</string>
<string name="categories_activity_title">وېشنيزې</string> <string name="categories_activity_title">وېشنيزې</string>
<string name="title_activity_settings">امستنې</string> <string name="title_activity_settings">اوڼنې</string>
<string name="title_activity_signup">نومليکنه</string> <string name="title_activity_signup">نومليکنه</string>
<string name="title_activity_featured_images">ټاکلی انځور</string> <string name="title_activity_featured_images">ټاکلی انځور</string>
<string name="title_activity_custom_selector">دوديز ټاکونکی</string> <string name="title_activity_custom_selector">دوديز ټاکونکی</string>
@ -216,7 +216,7 @@
<string name="navigation_item_upload">پورته کول</string> <string name="navigation_item_upload">پورته کول</string>
<string name="navigation_item_nearby">نژدې</string> <string name="navigation_item_nearby">نژدې</string>
<string name="navigation_item_about">په اړه</string> <string name="navigation_item_about">په اړه</string>
<string name="navigation_item_settings">امستنې</string> <string name="navigation_item_settings">اوڼنې</string>
<string name="navigation_item_feedback">غبرگون</string> <string name="navigation_item_feedback">غبرگون</string>
<string name="navigation_item_logout">وتل</string> <string name="navigation_item_logout">وتل</string>
<string name="navigation_item_review">بياکتنه</string> <string name="navigation_item_review">بياکتنه</string>

View file

@ -99,6 +99,8 @@
<string name="menu_from_camera">ფოტოშ გინოღალა</string> <string name="menu_from_camera">ფოტოშ გინოღალა</string>
<string name="menu_nearby">გოხოლუას</string> <string name="menu_nearby">გოხოლუას</string>
<string name="provider_contributions">ჩქიმი ეხარგუეფი</string> <string name="provider_contributions">ჩქიმი ეხარგუეფი</string>
<string name="menu_copy_link">რსხილიშ კოპირება</string>
<string name="menu_link_copied">რსხილი კოპირებული რე ბუფერულ შვენას</string>
<string name="menu_share">გობჟინაფა</string> <string name="menu_share">გობჟინაფა</string>
<string name="menu_view_file_page">ფაილიშ ხასჷლაშ ძირაფა</string> <string name="menu_view_file_page">ფაილიშ ხასჷლაშ ძირაფა</string>
<string name="share_title_hint">მუკნაჭარა (უციო)</string> <string name="share_title_hint">მუკნაჭარა (უციო)</string>
@ -109,6 +111,7 @@
<string name="login_failed_throttled">ძალამ მიარე უმწუძინუ ცადება. ქორთხინ, მუხირენ წუთშა ხოლო ქოცადით.</string> <string name="login_failed_throttled">ძალამ მიარე უმწუძინუ ცადება. ქორთხინ, მუხირენ წუთშა ხოლო ქოცადით.</string>
<string name="login_failed_blocked">მორდება, თე მახვარებუ ბლოკირი რე ვიკიოწკარუეს</string> <string name="login_failed_blocked">მორდება, თე მახვარებუ ბლოკირი რე ვიკიოწკარუეს</string>
<string name="login_failed_2fa_needed">თქვა გემშიონათ ოკო ჟირფაქტორიანი ავტორიზაციაშ კოდი.</string> <string name="login_failed_2fa_needed">თქვა გემშიონათ ოკო ჟირფაქტორიანი ავტორიზაციაშ კოდი.</string>
<string name="login_failed_email_auth_needed">დოდასურაფაშ კოდი ჯღონელი რე თქვანი ელექტრონული ფოშტაშა. ქორთხიინთ, მოჯღონელი კოდი გენშეჸონათ მიშაულარო.</string>
<string name="login_failed_generic">მიშულაქ ვემიხუჯინუ</string> <string name="login_failed_generic">მიშულაქ ვემიხუჯინუ</string>
<string name="share_upload_button">ეხარგუა</string> <string name="share_upload_button">ეხარგუა</string>
<string name="multiple_share_base_title">სერიაშ ჯოხო</string> <string name="multiple_share_base_title">სერიაშ ჯოხო</string>
@ -117,6 +120,7 @@
<string name="categories_search_text_hint">კატეგორიაშ გიშაგორუა</string> <string name="categories_search_text_hint">კატეგორიაშ გიშაგორუა</string>
<string name="depicts_search_text_hint">დოგორით ელემენტეფი, ნამუეფით მოჩამილი რე თქვანი სურათის (გვალა, ტაჯ-მაჰალი დო თ.უ.)</string> <string name="depicts_search_text_hint">დოგორით ელემენტეფი, ნამუეფით მოჩამილი რე თქვანი სურათის (გვალა, ტაჯ-მაჰალი დო თ.უ.)</string>
<string name="menu_save_categories">ჩუალა</string> <string name="menu_save_categories">ჩუალა</string>
<string name="menu_overflow_desc">გეძინელი მენიუ</string>
<string name="refresh_button">გოახალაფა</string> <string name="refresh_button">გოახალაფა</string>
<string name="display_list_button">ერკებული</string> <string name="display_list_button">ერკებული</string>
<string name="contributions_subtitle_zero">(ეხარგუეფი ვა რე)</string> <string name="contributions_subtitle_zero">(ეხარგუეფი ვა რე)</string>
@ -213,6 +217,7 @@
<string name="become_a_tester_title">ბეტა ტესტირებას კათაფი</string> <string name="become_a_tester_title">ბეტა ტესტირებას კათაფი</string>
<string name="become_a_tester_description">ჩართით Beta-შა ჭირინაფა Google Play-ს დო მიღით ორდოიანი ჭირინაფა ახალ ფუნქციეფშა დო ჩილათეფიშ ეშახინტკალო</string> <string name="become_a_tester_description">ჩართით Beta-შა ჭირინაფა Google Play-ს დო მიღით ორდოიანი ჭირინაფა ახალ ფუნქციეფშა დო ჩილათეფიშ ეშახინტკალო</string>
<string name="_2fa_code">2ფა კოდი</string> <string name="_2fa_code">2ფა კოდი</string>
<string name="email_auth_code">ელექტრონული ფოშტაშ დოდასურაფაშ კოდი</string>
<string name="logout_verification">გოკონანო გიშულა?</string> <string name="logout_verification">გოკონანო გიშულა?</string>
<string name="mediaimage_failed">მედიაფაილიშ ჩილათა</string> <string name="mediaimage_failed">მედიაფაილიშ ჩილათა</string>
<string name="no_subcategory_found">გიმენკატეგორიეფქ ვეგორინუ</string> <string name="no_subcategory_found">გიმენკატეგორიეფქ ვეგორინუ</string>
@ -360,6 +365,7 @@
<string name="previous">კინოხიანი</string> <string name="previous">კინოხიანი</string>
<string name="title_page_bookmarks_pictures">სურათეფი</string> <string name="title_page_bookmarks_pictures">სურათეფი</string>
<string name="title_page_bookmarks_locations">ორენეფი</string> <string name="title_page_bookmarks_locations">ორენეფი</string>
<string name="title_page_bookmarks_categories">კატეგორიეფი</string>
<string name="menu_bookmark">მიკოწონებულეფშა გეძინა/ლასუა</string> <string name="menu_bookmark">მიკოწონებულეფშა გეძინა/ლასუა</string>
<string name="provider_bookmarks">მიკოწონებულეფი</string> <string name="provider_bookmarks">მიკოწონებულეფი</string>
<string name="bookmark_empty">თქვა ვეგეიძინჷნა აკა მიკოწონებული</string> <string name="bookmark_empty">თქვა ვეგეიძინჷნა აკა მიკოწონებული</string>
@ -373,8 +379,10 @@
<string name="no_uploads">მოზოჯით ვიკიოწკარუეშა!\n\nგეხარგეთ თქვანი პირველი ფაილი, ქეგუნჭირით კონჭის გეძინა.</string> <string name="no_uploads">მოზოჯით ვიკიოწკარუეშა!\n\nგეხარგეთ თქვანი პირველი ფაილი, ქეგუნჭირით კონჭის გეძინა.</string>
<string name="no_categories_selected">კატეგორია ვა რე გიშაგორილი</string> <string name="no_categories_selected">კატეგორია ვა რე გიშაგორილი</string>
<string name="no_categories_selected_warning_desc">სურათეფი კატეგორიზაციაშ უმუშო შხირას მერკეთ გჷმორინაფონი რე. დასურო გონებჷნანო კატეგორიეფიშ მეწურაფაშ უმუშო გაგჷნძარაფა?</string> <string name="no_categories_selected_warning_desc">სურათეფი კატეგორიზაციაშ უმუშო შხირას მერკეთ გჷმორინაფონი რე. დასურო გონებჷნანო კატეგორიეფიშ მეწურაფაშ უმუშო გაგჷნძარაფა?</string>
<string name="no_depictions_selected">ეჭარუა ვა რე გიშაგორილი</string>
<string name="back_button_warning">ეხარგუაშ გოუქვაფა</string> <string name="back_button_warning">ეხარგუაშ გოუქვაფა</string>
<string name="back_button_continue">ეხარგუაშ გოგჷნძორაფა</string> <string name="back_button_continue">ეხარგუაშ გოგჷნძორაფა</string>
<string name="upload_flow_all_images_in_set">(ნაკორობაშ არძა სურათიშო)</string>
<string name="search_this_area">ათე არანს გორუა</string> <string name="search_this_area">ათე არანს გორუა</string>
<string name="nearby_card_permission_title">ალობაშ მოთხირი</string> <string name="nearby_card_permission_title">ალობაშ მოთხირი</string>
<string name="never_ask_again">თენა კჷნ დღას ვაბკითხა</string> <string name="never_ask_again">თენა კჷნ დღას ვაბკითხა</string>
@ -383,9 +391,20 @@
<string name="option_allow">ალობაშ მეჩამა</string> <string name="option_allow">ალობაშ მეჩამა</string>
<string name="option_dismiss">გოუქვაფა</string> <string name="option_dismiss">გოუქვაფა</string>
<string name="nominate_for_deletion_done">ღოლამირჷ რე</string> <string name="nominate_for_deletion_done">ღოლამირჷ რე</string>
<string name="review_category">თინას რენო კათეგორიეფი მეწურაფილი?</string>
<string name="review_thanks_yes_button_text">გეჸვენჯი სურათი</string>
<string name="review_thanks_no_button_text">ქო, მუშენ ვარი</string>
<string name="no_image">ვა რე გუმორინაფილი სურათეფი</string> <string name="no_image">ვა რე გუმორინაფილი სურათეფი</string>
<string name="no_image_uploaded">ვა რე ეხარგილი სურათეფი</string>
<string name="no_notification">თქვა ვაიღჷნა უკითხირუ გინაფეფი</string>
<string name="no_read_notification">ვა ვაიღჷნა კითხირებული გინაფეფი</string>
<string name="check_your_email_inbox">ქოძირით თქვანი ელექტრონული ფოსტა</string>
<string name="menu_option_read">კითხირებულიშ ძირაფა</string>
<string name="menu_option_unread">უკითხირებუშ ძირაფა</string>
<string name="error_occurred_in_picking_images">სურათიშ ეხარგუაშ ბორჯის ჩილათაქ მოხვადჷ</string> <string name="error_occurred_in_picking_images">სურათიშ ეხარგუაშ ბორჯის ჩილათაქ მოხვადჷ</string>
<string name="please_wait">ქორთხინთ ქჷმიცადით …</string> <string name="please_wait">ქორთხინთ ქჷმიცადით …</string>
<string name="copied_successfully">კოპირაფილი რე</string>
<string name="skip_image">თე სურათიშ გიშატება</string>
<string name="exif_tag_name_author">ავტორი</string> <string name="exif_tag_name_author">ავტორი</string>
<string name="exif_tag_name_location">ორენი</string> <string name="exif_tag_name_location">ორენი</string>
<string name="exif_tag_name_cameraModel">კამერაშ მოდელი</string> <string name="exif_tag_name_cameraModel">კამერაშ მოდელი</string>
@ -393,9 +412,32 @@
<string name="exif_tag_name_serialNumbers">სერიული ნომერი</string> <string name="exif_tag_name_serialNumbers">სერიული ნომერი</string>
<string name="exif_tag_name_software">პროგრამული უნარღელჸუა</string> <string name="exif_tag_name_software">პროგრამული უნარღელჸუა</string>
<string name="image_info">სურათიშ ინფორმაცია</string> <string name="image_info">სურათიშ ინფორმაცია</string>
<string name="delete_helper_ask_spam_selfie" fuzzy="true">სელფი</string> <string name="no_categories_found">კატეგორიეფქ ვეგორინუ</string>
<string name="delete_helper_ask_spam_blurry" fuzzy="true">ჸურილი</string> <string name="no_depiction_found">ეჭარუეფქ ვეორინუ</string>
<string name="upload_cancelled">ეხარგუაქ გეუქვუ</string>
<string name="dialog_box_text_nomination">მუშენ დილასას ოკო %1$s-ქ?</string>
<string name="review_is_uploaded_by">%1$s ეხარგილი რე: %2$s</string>
<string name="delete_helper_show_deletion_title_success">წუმოძინელო</string>
<string name="delete_helper_show_deletion_message_if">%1$s ნომინირებული რე ოლასარო.</string>
<string name="delete_helper_show_deletion_title_failed">ვემიხუჯინუ</string>
<string name="delete_helper_show_deletion_message_else">ვეშილებე ლასუაშ მოთხირი.</string>
<string name="delete_helper_ask_spam_selfie">სელფი, ნამუთ ვეგჷმირინუაფუ ნამთინ სტატიას</string>
<string name="delete_helper_ask_spam_blurry">ედომუშამო გინობარდილი რე</string>
<string name="delete_helper_ask_spam_nonsense">უაზრობა, აბსოლუტურო უმუხუჯუ რე ირნერი სტატიას</string>
<string name="delete_helper_ask_reason_copyright_press_photo">ახალი ამბეეფიშ სურათი</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">ნამდგა სურათი ინტერნეტიშე</string>
<string name="delete_helper_ask_reason_copyright_logo">ლოგო</string>
<string name="delete_helper_ask_reason_copyright_no_freedom_of_panorama">პანორამაშ დუდიშულაშ აკორცუაფა</string>
<string name="delete_helper_ask_alert_set_positive_button_reason">თიშენ ნამჷ-და თინა რე</string> <string name="delete_helper_ask_alert_set_positive_button_reason">თიშენ ნამჷ-და თინა რე</string>
<string name="category_edit_helper_make_edit_toast">კატეგორიეფიშ მოახალებაშ ცადება.</string>
<string name="category_edit_helper_show_edit_title">კატეგორიაშ მოახალება</string>
<string name="category_edit_helper_show_edit_title_success">წუმოძინელო</string>
<plurals name="category_edit_helper_show_edit_message_if">
<item quantity="one">კატეგორია %1$s გეძინელი რე.</item>
<item quantity="other">კატეგორიეფი %1$sგეძინელი რე.</item>
</plurals>
<string name="category_edit_helper_edit_message_else">ვემიხუჯინუ კატეგორიეფიშ გეძინაქ.</string>
<string name="category_edit_button_text">კატეგორიეფიშ მოახალება</string>
<string name="title_app_shortcut_bookmark">მიკოწონებეფი</string> <string name="title_app_shortcut_bookmark">მიკოწონებეფი</string>
<string name="title_app_shortcut_setting">პარამეტრეფი</string> <string name="title_app_shortcut_setting">პარამეტრეფი</string>
<string name="theme_dark_name">რუმე</string> <string name="theme_dark_name">რუმე</string>

View file

@ -431,7 +431,7 @@
<string name="deletion_reason_no_longer_want_public">I changed my mind, I don\'t want it to be publicly visible anymore</string> <string name="deletion_reason_no_longer_want_public">I changed my mind, I don\'t want it to be publicly visible anymore</string>
<string name="deletion_reason_not_interesting">Sorry this picture is not interesting for an encyclopedia</string> <string name="deletion_reason_not_interesting">Sorry this picture is not interesting for an encyclopedia</string>
<string name="uploaded_by_myself">Uploaded by myself on %1$s, used in %2$d article(s).</string> <string name="uploaded_by_myself">Uploaded by myself on %1$s, used in %2$d article(s) at least.</string>
<string name="no_uploads">Welcome to Commons!\n <string name="no_uploads">Welcome to Commons!\n
Upload your first media by tapping on the add button.</string> Upload your first media by tapping on the add button.</string>
@ -876,4 +876,5 @@ Upload your first media by tapping on the add button.</string>
<string name="show_in_nearby">Show in Nearby</string> <string name="show_in_nearby">Show in Nearby</string>
<string name="image_tag_line_created_and_uploaded_by">Created and uploaded by: %1$s</string> <string name="image_tag_line_created_and_uploaded_by">Created and uploaded by: %1$s</string>
<string name="image_tag_line_created_by_and_uploaded_by">Created by %1$s and uploaded by %2$s</string> <string name="image_tag_line_created_by_and_uploaded_by">Created by %1$s and uploaded by %2$s</string>
<string name="nominated_for_deletion_btn">Nominated for Deletion</string>
</resources> </resources>

View file

@ -5,13 +5,14 @@ import android.content.res.Resources
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.auth.SessionManager import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.fileusages.GlobalFileUsagesResponse
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
import fr.free.nrw.commons.profile.achievements.FeedbackResponse
import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse
import fr.free.nrw.commons.profile.leaderboard.UpdateAvatarResponse import fr.free.nrw.commons.profile.leaderboard.UpdateAvatarResponse
import fr.free.nrw.commons.utils.ViewUtilWrapper import fr.free.nrw.commons.utils.ViewUtilWrapper
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import kotlinx.coroutines.test.runTest
import media import media
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -58,16 +59,16 @@ class ReasonBuilderTest {
PowerMockito.`when`(context?.getString(R.string.user_not_logged_in)) PowerMockito.`when`(context?.getString(R.string.user_not_logged_in))
.thenReturn("Log-in expired. Please log in again.") .thenReturn("Log-in expired. Please log in again.")
reasonBuilder!!.getReason(mock(Media::class.java), "test") reasonBuilder!!.getReason(mock(Media::class.java), "test").test().await()
verify(sessionManager, times(1))!!.forceLogin(any(Context::class.java)) verify(sessionManager, times(1))!!.forceLogin(any(Context::class.java))
} }
@Test @Test
fun getReason() { fun getReason() = runTest {
`when`(sessionManager?.userName).thenReturn("Testuser") `when`(sessionManager?.userName).thenReturn("Testuser")
`when`(sessionManager?.doesAccountExist()).thenReturn(true) `when`(sessionManager?.doesAccountExist()).thenReturn(true)
`when`(okHttpJsonApiClient!!.getAchievements(anyString())) `when`(okHttpJsonApiClient!!.getGlobalFileUsages(anyString(), anyInt()))
.thenReturn(Single.just(mock(FeedbackResponse::class.java))) .thenReturn(mock(GlobalFileUsagesResponse::class.java))
`when`(okHttpJsonApiClient!!.getLeaderboard(anyString(), anyString(), anyString(), anyString(), anyString())) `when`(okHttpJsonApiClient!!.getLeaderboard(anyString(), anyString(), anyString(), anyString(), anyString()))
.thenReturn(Observable.just(mock(LeaderboardResponse::class.java))) .thenReturn(Observable.just(mock(LeaderboardResponse::class.java)))
`when`(okHttpJsonApiClient!!.setAvatar(anyString(), anyString())) `when`(okHttpJsonApiClient!!.setAvatar(anyString(), anyString()))
@ -75,8 +76,8 @@ class ReasonBuilderTest {
val media = media(filename = "test_file", dateUploaded = Date()) val media = media(filename = "test_file", dateUploaded = Date())
reasonBuilder!!.getReason(media, "test") reasonBuilder!!.getReason(media, "test").test().await()
verify(sessionManager, times(0))!!.forceLogin(any(Context::class.java)) verify(sessionManager, times(0))!!.forceLogin(any(Context::class.java))
verify(okHttpJsonApiClient, times(1))!!.getAchievements(anyString()) verify(okHttpJsonApiClient, times(1))!!.getGlobalFileUsages(anyString(), anyInt())
} }
} }

View file

@ -16,6 +16,7 @@ constraintlayout = "1.1.3"
coordinates2country = "1.8" coordinates2country = "1.8"
dexcount = "4.0.0" dexcount = "4.0.0"
githubTripletPlay = "2.7.2" githubTripletPlay = "2.7.2"
kotlinxCoroutinesRx2 = "1.8.0"
osmdroidAndroid = "6.1.17" osmdroidAndroid = "6.1.17"
testCore = "1.4.0" testCore = "1.4.0"
coreKtx = "1.9.0" coreKtx = "1.9.0"
@ -39,7 +40,7 @@ leakcanary = "2.10"
livedataTesting = "1.2.0" livedataTesting = "1.2.0"
swipelayout = "1.2.0" swipelayout = "1.2.0"
viewpagerIndicator = "2.4.1.1" viewpagerIndicator = "2.4.1.1"
lifecycleRuntimeKtx = "2.8.4" lifecycleRuntimeKtx = "2.8.7"
loggingInterceptor = "4.10.0" loggingInterceptor = "4.10.0"
logbackAndroidClassic = "1.1.1-6" logbackAndroidClassic = "1.1.1-6"
material = "1.12.0" material = "1.12.0"
@ -127,6 +128,7 @@ dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref =
facebook-fresco = { module = "com.facebook.fresco:fresco", version.ref = "frescoVersion" } facebook-fresco = { module = "com.facebook.fresco:fresco", version.ref = "frescoVersion" }
glide-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" } glide-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" } glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
kotlinx-coroutines-rx2 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-rx2", version.ref = "kotlinxCoroutinesRx2" }
photoview = { module = "com.github.chrisbanes:PhotoView", version.ref = "photoviewVersion" } photoview = { module = "com.github.chrisbanes:PhotoView", version.ref = "photoviewVersion" }
# RxJava and Reactive Programming # RxJava and Reactive Programming