From 614892c5cb213b5e901417bb77d02dcfb52f0e85 Mon Sep 17 00:00:00 2001 From: Neel Doshi Date: Thu, 26 Dec 2024 20:21:52 +0530 Subject: [PATCH 01/37] Migrated from Rxjava to Retroift, added MVVM Architecture. --- app/build.gradle | 17 +- .../free/nrw/commons/di/NetworkingModule.kt | 29 ++ .../fr/free/nrw/commons/network/APIService.kt | 23 ++ .../achievements/AchievementViewModel.kt | 41 +++ .../AchievementViewModelFactory.kt | 23 ++ .../achievements/AchievementsFragment.kt | 266 +++++------------- .../profile/model/AchievementResponse.kt | 34 +++ .../commons/profile/model/UserAchievements.kt | 15 + .../commons/repository/ProfileRepository.kt | 53 ++++ .../main/res/layout/fragment_achievements.xml | 6 +- 10 files changed, 303 insertions(+), 204 deletions(-) create mode 100644 app/src/main/java/fr/free/nrw/commons/network/APIService.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModel.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModelFactory.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/model/AchievementResponse.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/model/UserAchievements.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/repository/ProfileRepository.kt diff --git a/app/build.gradle b/app/build.gradle index 14bf5f3b7..4927dbc35 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,11 +51,26 @@ dependencies { implementation 'com.karumi:dexter:5.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + def lifecycle_version = "2.8.7" + // ViewModel + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" + // ViewModel utilities for Compose + implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version" + + // Lifecycles only (without ViewModel or LiveData) + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" + // Lifecycle utilities for Compose + implementation "androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version" + + // Saved state module for ViewModel + implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version" + + // Annotation processor + kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" // Jetpack Compose def composeBom = platform('androidx.compose:compose-bom:2024.11.00') implementation "androidx.activity:activity-compose:1.9.3" - implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.4" implementation (composeBom) implementation "androidx.compose.runtime:runtime" implementation "androidx.compose.ui:ui" diff --git a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.kt b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.kt index 0e9d83478..8a074f67a 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.kt +++ b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.kt @@ -25,6 +25,7 @@ import fr.free.nrw.commons.media.PageMediaInterface import fr.free.nrw.commons.media.WikidataMediaInterface import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient import fr.free.nrw.commons.mwapi.UserInterface +import fr.free.nrw.commons.network.APIService import fr.free.nrw.commons.notification.NotificationInterface import fr.free.nrw.commons.review.ReviewInterface import fr.free.nrw.commons.upload.UploadInterface @@ -42,6 +43,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor.Level +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory import timber.log.Timber import java.io.File import java.util.concurrent.TimeUnit @@ -295,6 +298,32 @@ class NetworkingModule { fun provideLanguageWikipediaSite(): WikiSite = WikiSite.forDefaultLocaleLanguageCode() + @Provides + @Named("tool_wmflabs_base_url") + fun provideToolWmflabsBaseUrl() : HttpUrl = + "https://tools.wmflabs.org/commons-android-app/tool-commons-android-app/".toHttpUrlOrNull()!! + + @Singleton + @Provides + @Named("tool_wmflabs_retrofit") + fun provideToolWmflabsBaseUrlRetrofit( + @Named("tool_wmflabs_base_url") baseUrl: HttpUrl, + okHttpClient: OkHttpClient + ) : Retrofit { + return Retrofit.Builder() + .baseUrl(baseUrl) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create()) +// .addCallAdapterFactory(CoroutineCallAdapterFactory()) + .build() + } + + @Provides + @Singleton + fun provideAPIService( + @Named("tool_wmflabs_retrofit") retrofit: Retrofit + ): APIService = retrofit.create(APIService::class.java) + companion object { private const val WIKIDATA_SPARQL_QUERY_URL = "https://query.wikidata.org/sparql" private const val TOOLS_FORGE_URL = diff --git a/app/src/main/java/fr/free/nrw/commons/network/APIService.kt b/app/src/main/java/fr/free/nrw/commons/network/APIService.kt new file mode 100644 index 000000000..ba66855fd --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/network/APIService.kt @@ -0,0 +1,23 @@ +package fr.free.nrw.commons.network + +import fr.free.nrw.commons.profile.model.AchievementResponse +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query + + +interface APIService { + + // https://tools.wmflabs.org/commons-android-app/tool-commons-android-app/uploadsbyuser.py?user=Devanonymous + @GET("uploadsbyuser.py") + suspend fun getImageUploadCount( + @Query("user") username : String + ) : Response + + + // https://tools.wmflabs.org/commons-android-app/tool-commons-android-app//feedback.py?user=Devanonymous + @GET("feedback.py") + suspend fun getUserAchievements( + @Query("user") username: String + ) : Response +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModel.kt b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModel.kt new file mode 100644 index 000000000..4d26fc2c4 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModel.kt @@ -0,0 +1,41 @@ +package fr.free.nrw.commons.profile.achievements + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import fr.free.nrw.commons.profile.model.UserAchievements +import fr.free.nrw.commons.repository.ProfileRepository +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +class AchievementViewModel @Inject constructor( + private val repository: ProfileRepository +) : ViewModel() { + + private val _achievements = MutableStateFlow(UserAchievements( + LevelController.LevelInfo.LEVEL_1, + articlesUsingImagesCount = 0, + thanksReceivedCount = 0, + featuredImagesCount = 0, + qualityImagesCount = 0, + imagesUploadedCount = 0, + revertedCount = 0, + uniqueImagesCount = 0, + imagesEditedBySomeoneElseCount = 0 + ) + ) + val achievements : StateFlow = _achievements + + private val _loading = MutableStateFlow(true) + val loading : StateFlow = _loading + + fun getUserAchievements(username: String){ + viewModelScope.launch { + repository.getUserLevel(username = username).collect { + _loading.value = false + _achievements.value = it + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModelFactory.kt b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModelFactory.kt new file mode 100644 index 000000000..d9c936a09 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModelFactory.kt @@ -0,0 +1,23 @@ +package fr.free.nrw.commons.profile.achievements + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import javax.inject.Inject +import javax.inject.Provider + +/** + * This class extends the ViewModelProvider.Factory and creates a ViewModelFactory class + * for AchievementViewModel + */ +class AchievementViewModelFactory @Inject constructor( + private val viewModelProvider: Provider +): ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(AchievementViewModel::class.java)) { + (@Suppress("UNCHECKED_CAST") + return viewModelProvider.get() as T) + } else { + throw IllegalArgumentException("Unknown class name") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt index af07423eb..f0369799a 100644 --- a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt @@ -1,15 +1,16 @@ package fr.free.nrw.commons.profile.achievements +import android.annotation.SuppressLint import android.net.Uri import android.os.Bundle -import android.util.DisplayMetrics +import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver import android.widget.Toast -import androidx.appcompat.view.ContextThemeWrapper -import androidx.constraintlayout.widget.ConstraintLayout +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat import com.google.android.material.badge.BadgeDrawable import com.google.android.material.badge.BadgeUtils @@ -22,21 +23,20 @@ import fr.free.nrw.commons.di.CommonsDaggerSupportFragment import fr.free.nrw.commons.kvstore.BasicKvStore import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient import fr.free.nrw.commons.profile.ProfileActivity -import fr.free.nrw.commons.profile.achievements.LevelController.LevelInfo.Companion.from import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog import fr.free.nrw.commons.utils.ViewUtil.showDismissibleSnackBar import fr.free.nrw.commons.utils.ViewUtil.showLongToast -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers -import org.apache.commons.lang3.StringUtils +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import timber.log.Timber -import java.util.Objects import javax.inject.Inject class AchievementsFragment : CommonsDaggerSupportFragment(){ - private lateinit var levelInfo: LevelController.LevelInfo + @Inject + lateinit var viewModelFactory: AchievementViewModelFactory + lateinit var viewModel: AchievementViewModel @Inject lateinit var sessionManager: SessionManager @@ -45,11 +45,8 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ private var _binding: FragmentAchievementsBinding? = null private val binding get() = _binding!! - // To keep track of the number of wiki edits made by a user - private var numberOfEdits: Int = 0 private var userName: String? = null - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { @@ -64,6 +61,8 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ ): View { _binding = FragmentAchievementsBinding.inflate(inflater, container, false) + viewModel = ViewModelProvider( + this@AchievementsFragment, viewModelFactory)[AchievementViewModel::class.java] binding.achievementInfo.setOnClickListener { showInfoDialog() } binding.imagesUploadInfoIcon.setOnClickListener { showUploadInfo() } binding.imagesRevertedInfoIcon.setOnClickListener { showRevertedInfo() } @@ -73,19 +72,15 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ binding.thanksImageIcon.setOnClickListener { showThanksReceivedInfo() } binding.qualityImageIcon.setOnClickListener { showQualityImagesInfo() } - // DisplayMetrics used to fetch the size of the screen - val displayMetrics = DisplayMetrics() - requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) - val height = displayMetrics.heightPixels - val width = displayMetrics.widthPixels - - // Used for the setting the size of imageView at runtime - // TODO REMOVE - val params = binding.achievementBadgeImage.layoutParams as ConstraintLayout.LayoutParams - params.height = (height * BADGE_IMAGE_HEIGHT_RATIO).toInt() - params.width = (width * BADGE_IMAGE_WIDTH_RATIO).toInt() - binding.achievementBadgeImage.requestLayout() - binding.progressBar.visibility = View.VISIBLE + lifecycleScope.launch { + viewModel.loading.collectLatest { + if (it){ + binding.progressBar.visibility = View.VISIBLE + } else { + binding.progressBar.visibility = View.GONE + } + } + } setHasOptionsMenu(true) if (sessionManager.userName == null || sessionManager.userName == userName) { @@ -100,8 +95,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ return binding.root } - - setWikidataEditCount() setAchievements() return binding.root @@ -145,74 +138,58 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ * which then calls parseJson when results are fetched */ + @SuppressLint("SetTextI18n") private fun setAchievements() { - binding.progressBar.visibility = View.VISIBLE if (checkAccount()) { - try { - compositeDisposable.add( - okHttpJsonApiClient - .getAchievements(userName ?: return) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { response -> - if (response != null) { - setUploadCount(Achievements.from(response)) - } else { - Timber.d("Success") - // TODO Create a Method to Hide all the Statistics -// binding.layoutImageReverts.visibility = View.INVISIBLE -// binding.achievementBadgeImage.visibility = View.INVISIBLE - // If the number of edits made by the user are more than 150,000 - // in some cases such high number of wiki edit counts cause the - // achievements calculator to fail in some cases, for more details - // refer Issue: #3295 - if (numberOfEdits <= 150_000) { - showSnackBarWithRetry(false) - } else { - showSnackBarWithRetry(true) - } - } - }, - { throwable -> - Timber.e(throwable, "Fetching achievements statistics failed") - if (numberOfEdits <= 150_000) { - showSnackBarWithRetry(false) - } else { - showSnackBarWithRetry(true) - } - } + viewModel.getUserAchievements(username = userName.toString()) + + lifecycleScope.launch { + viewModel.achievements.collect{ + + binding.achievementLevel.text = getString(R.string.level,it.level.levelNumber) + val store = BasicKvStore(requireContext(), userName) + store.putString("userAchievementsLevel", it.level.levelNumber.toString()) + + binding.achievementBadgeImage.setImageDrawable( + VectorDrawableCompat.create( + resources, R.drawable.badge, + ContextThemeWrapper(activity, it.level.levelStyle).theme ) - ) - } catch (e: Exception) { - Timber.d("Exception: ${e.message}") + ) + binding.achievementBadgeText.text = it.level.levelNumber.toString() + + // TODO(use String Format) + binding.imageUploadedTVCount.text = + it.imagesUploadedCount.toString() + "/" + it.level.maxUploadCount + binding.imagesUploadedProgressbar.progress = + 100 * it.imagesUploadedCount / it.level.maxUploadCount + + // Revert + binding.imageRevertTVCount.text = it.revertedCount.toString() + "%" + binding.imageRevertsProgressbar.progress = it.revertedCount + binding.imagesRevertLimitText.text = + resources.getString(R.string.achievements_revert_limit_message) + it.level.minNonRevertPercentage + "%" + + // Images Used + binding.imagesUsedProgressbar.progress = (100 * it.uniqueImagesCount) / it.level.maxUniqueImages + binding.imagesUsedCount.text = (it.uniqueImagesCount.toString() + "/" + + it.level.maxUniqueImages) + + // Thanks Received Badge + showBadgesWithCount(view = binding.thanksImageIcon, count = it.thanksReceivedCount) + + // Featured Images Badge + showBadgesWithCount(view = binding.featuredImageIcon, count = it.featuredImagesCount) + + // Quality Images Badge + showBadgesWithCount(view = binding.qualityImageIcon, count = it.qualityImagesCount) + + showBadgesWithCount(view = binding.wikidataEditsIcon, count = it.imagesEditedBySomeoneElseCount) + } } } } - /** - * To call the API to fetch the count of wiki data edits - * in the form of JavaRx Single object - */ - - private fun setWikidataEditCount() { - if (StringUtils.isBlank(userName)) { - return - } - compositeDisposable.add( - okHttpJsonApiClient - .getWikidataEdits(userName) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ edits: Int -> - numberOfEdits = edits - showBadgesWithCount(view = binding.wikidataEditsIcon, count = edits) - }, { e: Throwable -> - Timber.e("Error:$e") - }) - ) - } - /** * Shows a snack bar which has an action button which on click dismisses the snackbar and invokes the * listener passed @@ -253,49 +230,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ binding.progressBar.visibility = View.GONE } - /** - * used to the count of images uploaded by user - */ - - private fun setUploadCount(achievements: Achievements) { - if (checkAccount()) { - compositeDisposable.add(okHttpJsonApiClient - .getUploadCount(Objects.requireNonNull(userName)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { uploadCount: Int? -> - setAchievementsUploadCount( - achievements, - uploadCount ?:0 - ) - }, - { t: Throwable? -> - Timber.e(t, "Fetching upload count failed") - onError() - } - )) - } - } - - /** - * used to set achievements upload count and call hideProgressbar - * @param uploadCount - */ - private fun setAchievementsUploadCount(achievements: Achievements, uploadCount: Int) { - // Create a new instance of Achievements with updated imagesUploaded - val updatedAchievements = Achievements( - achievements.uniqueUsedImages, - achievements.articlesUsingImages, - achievements.thanksReceived, - achievements.featuredImages, - achievements.qualityImages, - uploadCount, // Update imagesUploaded with new value - achievements.revertCount - ) - - hideProgressBar(updatedAchievements) - } /** * used to the uploaded images progressbar @@ -306,9 +240,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ setZeroAchievements() } else { binding.imagesUploadedProgressbar.visibility = View.VISIBLE - binding.imagesUploadedProgressbar.progress = - 100 * uploadCount / levelInfo.maxUploadCount - binding.imageUploadedTVCount.text = uploadCount.toString() + "/" + levelInfo.maxUploadCount } } @@ -325,7 +256,7 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ getString(R.string.ok), {} ) - + binding.layout.visibility = View.INVISIBLE // binding.imagesUploadedProgressbar.setVisibility(View.INVISIBLE); // binding.imageRevertsProgressbar.setVisibility(View.INVISIBLE); // binding.imagesUsedByWikiProgressBar.setVisibility(View.INVISIBLE); @@ -335,52 +266,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ binding.imagesUploadTextParam.setText(R.string.no_image_uploaded) } - /** - * used to set the non revert image percentage - * @param notRevertPercentage - */ - private fun setImageRevertPercentage(notRevertPercentage: Int) { - binding.imageRevertsProgressbar.visibility = View.VISIBLE - binding.imageRevertsProgressbar.progress = notRevertPercentage - val revertPercentage = notRevertPercentage.toString() - binding.imageRevertTVCount.text = "$revertPercentage%" - binding.imagesRevertLimitText.text = - resources.getString(R.string.achievements_revert_limit_message) + levelInfo.minNonRevertPercentage + "%" - } - - /** - * Used the inflate the fetched statistics of the images uploaded by user - * and assign badge and level. Also stores the achievements level of the user in BasicKvStore to display in menu - * @param achievements - */ - private fun inflateAchievements(achievements: Achievements) { - - // Thanks Received Badge - showBadgesWithCount(view = binding.thanksImageIcon, count = achievements.thanksReceived) - - // Featured Images Badge - showBadgesWithCount(view = binding.featuredImageIcon, count = achievements.featuredImages) - - // Quality Images Badge - showBadgesWithCount(view = binding.qualityImageIcon, count = achievements.qualityImages) - - binding.imagesUsedByWikiProgressBar.progress = - 100 * achievements.uniqueUsedImages / levelInfo.maxUniqueImages - binding.imagesUsedCount.text = (achievements.uniqueUsedImages.toString() + "/" - + levelInfo.maxUniqueImages) - - binding.achievementLevel.text = getString(R.string.level,levelInfo.levelNumber) - binding.achievementBadgeImage.setImageDrawable( - VectorDrawableCompat.create( - resources, R.drawable.badge, - ContextThemeWrapper(activity, levelInfo.levelStyle).theme - ) - ) - binding.achievementBadgeText.text = levelInfo.levelNumber.toString() - val store = BasicKvStore(requireContext(), userName) - store.putString("userAchievementsLevel", levelInfo.levelNumber.toString()) - } - /** * This function is used to show badge on any view (button, imageView, etc) * @param view The View on which the badge will be displayed eg (button, imageView, etc) @@ -425,22 +310,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ }) } - /** - * to hide progressbar - */ - private fun hideProgressBar(achievements: Achievements) { - if (binding.progressBar != null) { - levelInfo = from( - achievements.imagesUploaded, - achievements.uniqueUsedImages, - achievements.notRevertPercentage - ) - inflateAchievements(achievements) - setUploadProgress(achievements.imagesUploaded) - setImageRevertPercentage(achievements.notRevertPercentage) - binding.progressBar.visibility = View.GONE - } - } fun showUploadInfo() { launchAlertWithHelpLink( @@ -546,9 +415,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ companion object{ - private const val BADGE_IMAGE_WIDTH_RATIO = 0.4 - private const val BADGE_IMAGE_HEIGHT_RATIO = 0.3 - /** * Help link URLs */ diff --git a/app/src/main/java/fr/free/nrw/commons/profile/model/AchievementResponse.kt b/app/src/main/java/fr/free/nrw/commons/profile/model/AchievementResponse.kt new file mode 100644 index 000000000..b44b7d16c --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/profile/model/AchievementResponse.kt @@ -0,0 +1,34 @@ +package fr.free.nrw.commons.profile.model + + +import com.google.gson.annotations.SerializedName + +data class AchievementResponse( + @SerializedName("articlesUsingImages") + val articlesUsingImages: Int, + @SerializedName("database") + val database: String, + @SerializedName("deletedUploads") + val deletedUploads: Int, + @SerializedName("featuredImages") + val featuredImages: FeaturedImages, + @SerializedName("imagesEditedBySomeoneElse") + val imagesEditedBySomeoneElse: Int, + @SerializedName("labs") + val labs: Boolean, + @SerializedName("status") + val status: String, + @SerializedName("thanksReceived") + val thanksReceived: Int, + @SerializedName("uniqueUsedImages") + val uniqueUsedImages: Int, + @SerializedName("user") + val user: String +) + +data class FeaturedImages( + @SerializedName("Featured_pictures_on_Wikimedia_Commons") + val featuredPicturesOnWikimediaCommons: Int, + @SerializedName("Quality_images") + val qualityImages: Int +) \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/profile/model/UserAchievements.kt b/app/src/main/java/fr/free/nrw/commons/profile/model/UserAchievements.kt new file mode 100644 index 000000000..b13b4d382 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/profile/model/UserAchievements.kt @@ -0,0 +1,15 @@ +package fr.free.nrw.commons.profile.model + +import fr.free.nrw.commons.profile.achievements.LevelController + +data class UserAchievements( + val level: LevelController.LevelInfo, + val articlesUsingImagesCount: Int = 0, + val thanksReceivedCount: Int = 0, + val featuredImagesCount: Int = 0, + val qualityImagesCount: Int = 0, + val imagesUploadedCount: Int = 0, + val revertedCount: Int = 0, + val uniqueImagesCount: Int = 0, + val imagesEditedBySomeoneElseCount: Int = 0 +) \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/repository/ProfileRepository.kt b/app/src/main/java/fr/free/nrw/commons/repository/ProfileRepository.kt new file mode 100644 index 000000000..a441464fa --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/repository/ProfileRepository.kt @@ -0,0 +1,53 @@ +package fr.free.nrw.commons.repository + +import fr.free.nrw.commons.network.APIService +import fr.free.nrw.commons.profile.achievements.LevelController +import fr.free.nrw.commons.profile.model.UserAchievements +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import timber.log.Timber +import javax.inject.Inject + +class ProfileRepository @Inject constructor(private val apiService: APIService) { + + fun getUserLevel(username: String) : Flow = flow { + try { + val uploadCountResponse = apiService.getImageUploadCount(username) + val imagesUploaded = uploadCountResponse.body() ?:0 + + val achievementResponse = apiService.getUserAchievements(username) + + val uniqueImages = achievementResponse.body()?.uniqueUsedImages ?: 0 + val articlesUsingImages = achievementResponse.body()?.articlesUsingImages ?: 0 + val thanksReceived = achievementResponse.body()?.thanksReceived ?: 0 + val featuredImages = achievementResponse.body()?.featuredImages?.featuredPicturesOnWikimediaCommons ?:0 + val qualityImages = achievementResponse.body()?.featuredImages?.qualityImages ?: 0 + val deletedUploads = achievementResponse.body()?.deletedUploads ?:0 + val revertCount = (imagesUploaded - deletedUploads) * 100 / imagesUploaded + val imagesEditedBySomeoneElse = achievementResponse.body()?.imagesEditedBySomeoneElse ?:0 + + val level = LevelController.LevelInfo.from( + imagesUploaded = imagesUploaded, + uniqueImagesUsed = uniqueImages, + nonRevertRate = revertCount) + + emit( + UserAchievements( + level = level, + articlesUsingImagesCount = articlesUsingImages, + featuredImagesCount = featuredImages, + imagesUploadedCount = imagesUploaded, + qualityImagesCount = qualityImages, + revertedCount = revertCount, + thanksReceivedCount = thanksReceived, + uniqueImagesCount = uniqueImages, + imagesEditedBySomeoneElseCount = imagesEditedBySomeoneElse + ) + ) + + } + catch(e : Exception) { + Timber.e(e.printStackTrace().toString()) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_achievements.xml b/app/src/main/res/layout/fragment_achievements.xml index 00c18b323..b199a23de 100644 --- a/app/src/main/res/layout/fragment_achievements.xml +++ b/app/src/main/res/layout/fragment_achievements.xml @@ -43,7 +43,7 @@ android:id="@+id/achievement_badge_image" android:layout_width="150dp" android:layout_height="150dp" - android:layout_marginTop="16dp" + android:layout_marginTop="100dp" android:background="@drawable/badge" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" @@ -263,7 +263,7 @@ app:layout_constraintTop_toBottomOf="@+id/images_used_tv"> + /> \ No newline at end of file From ffae85b18cfa005e2b0ce2b78d46d862e50f0262 Mon Sep 17 00:00:00 2001 From: Neel Doshi Date: Thu, 26 Dec 2024 21:00:02 +0530 Subject: [PATCH 02/37] Test : Removed the test which are longer used. --- .../AchievementsFragmentUnitTests.kt | 54 +------------------ 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/profile/achievements/AchievementsFragmentUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/profile/achievements/AchievementsFragmentUnitTests.kt index 4c2fbf52c..789f4d8b2 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/profile/achievements/AchievementsFragmentUnitTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/profile/achievements/AchievementsFragmentUnitTests.kt @@ -173,33 +173,6 @@ class AchievementsFragmentUnitTests { method.invoke(fragment, "", "") } - @Test - @Throws(Exception::class) - fun testHideProgressBar() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - val method: Method = - AchievementsFragment::class.java.getDeclaredMethod( - "hideProgressBar", - Achievements::class.java, - ) - method.isAccessible = true - method.invoke(fragment, achievements) - } - - @Test - @Throws(Exception::class) - fun testSetAchievementsUploadCount() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - val method: Method = - AchievementsFragment::class.java.getDeclaredMethod( - "setAchievementsUploadCount", - Achievements::class.java, - Int::class.java, - ) - method.isAccessible = true - method.invoke(fragment, achievements, 0) - } - @Test @Throws(Exception::class) fun testCheckAccount() { @@ -211,20 +184,7 @@ class AchievementsFragmentUnitTests { method.isAccessible = true method.invoke(fragment) } - - @Test - @Throws(Exception::class) - fun testSetUploadCount() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - val method: Method = - AchievementsFragment::class.java.getDeclaredMethod( - "setUploadCount", - Achievements::class.java, - ) - method.isAccessible = true - method.invoke(fragment, achievements) - } - + @Test @Throws(Exception::class) fun testOnError() { @@ -263,18 +223,6 @@ class AchievementsFragmentUnitTests { method.invoke(fragment, false) } - @Test - @Throws(Exception::class) - fun testSetWikidataEditCount() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - val method: Method = - AchievementsFragment::class.java.getDeclaredMethod( - "setWikidataEditCount", - ) - method.isAccessible = true - method.invoke(fragment) - } - @Test @Throws(Exception::class) fun testSetAchievements() { From 67ac92ff573482fa2efe54b68de2792dda6b11ea Mon Sep 17 00:00:00 2001 From: Sonal Yadav Date: Mon, 17 Mar 2025 04:23:58 +0530 Subject: [PATCH 03/37] Fix NullPointerException in onBackPressed() (#6249) --- .../nrw/commons/contributions/MainActivity.kt | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt index a61567393..a83532bdb 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt @@ -387,35 +387,40 @@ after opening the app. } override fun onBackPressed() { - if (contributionsFragment != null && activeFragment == ActiveFragment.CONTRIBUTIONS) { + when (activeFragment) { + ActiveFragment.CONTRIBUTIONS -> { // Means that contribution fragment is visible - if (!contributionsFragment!!.backButtonClicked()) { //If this one does not wan't to handle + if (contributionsFragment?.backButtonClicked() != true) { //If this one does not want to handle // the back press, let the activity do so super.onBackPressed() + } } - } else if (nearbyParentFragment != null && activeFragment == ActiveFragment.NEARBY) { + ActiveFragment.NEARBY -> { // Means that nearby fragment is visible - /* If function nearbyParentFragment.backButtonClick() returns false, it means that the bottomsheet is - not expanded. So if the back button is pressed, then go back to the Contributions tab */ - if (!nearbyParentFragment!!.backButtonClicked()) { - supportFragmentManager.beginTransaction().remove(nearbyParentFragment!!) - .commit() + if (nearbyParentFragment?.backButtonClicked() != true) { + nearbyParentFragment?.let { + supportFragmentManager.beginTransaction().remove(it).commit() + } setSelectedItemId(NavTab.CONTRIBUTIONS.code()) + } } - } else if (exploreFragment != null && activeFragment == ActiveFragment.EXPLORE) { - // Means that explore fragment is visible - if (!exploreFragment!!.onBackPressed()) { - if (applicationKvStore!!.getBoolean("login_skipped")) { + ActiveFragment.EXPLORE -> { + // Explore Fragment is visible + if (exploreFragment?.onBackPressed() != true) { + if (applicationKvStore?.getBoolean("login_skipped") == true) { super.onBackPressed() } else { setSelectedItemId(NavTab.CONTRIBUTIONS.code()) + } } } - } else if (bookmarkFragment != null && activeFragment == ActiveFragment.BOOKMARK) { + ActiveFragment.BOOKMARK -> { // Means that bookmark fragment is visible - bookmarkFragment!!.onBackPressed() - } else { + bookmarkFragment?.onBackPressed() + } + else -> { super.onBackPressed() + } } } From fa0bdf5747169812aae9e0fb0d9b7b37bd9f38d9 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 17 Mar 2025 13:01:37 +0100 Subject: [PATCH 04/37] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-oc/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index c097898e9..f837c4732 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -138,7 +138,7 @@ Mercejaments recebuts Paramètres Per defaut - Fosc + Escur Clar Mercejar l\'utilizator From 954a7aee91cab1616d29f39b19bcaf922df3f630 Mon Sep 17 00:00:00 2001 From: Ritika Pahwa <83745993+RitikaPahwa4444@users.noreply.github.com> Date: Tue, 18 Mar 2025 19:58:19 +0530 Subject: [PATCH 05/37] Bump up version code and name (#6250) * Bump up version code and name * Remove unintended change --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6890177e8..bc8e9bd1d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -212,8 +212,8 @@ android { defaultConfig { //applicationId 'fr.free.nrw.commons' - versionCode 1046 - versionName '5.1.3' + versionCode 1048 + versionName '5.2.0' setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) minSdkVersion 21 From 828f69fc46a5ba3ab6c5f2ca53307c9e57c01a98 Mon Sep 17 00:00:00 2001 From: Sonal Yadav Date: Thu, 20 Mar 2025 03:51:33 +0530 Subject: [PATCH 06/37] Update Privacy Policy Link to GitHub.io (#6255) * "moved privacy policy" * Update PRIVACY_POLICY_URL to https://commons-app.github.io/privacy-policy as suggested * Use BuildConfig.PRIVACY_POLICY_URL in launchPrivacyPolicy function --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index bc8e9bd1d..8e29852e1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -318,7 +318,7 @@ android { buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\"" buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes\"" buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.org/wiki/Special:PasswordReset\"" - buildConfigField "String", "PRIVACY_POLICY_URL", "\"https://github.com/commons-app/commons-app-documentation/blob/master/android/Privacy-policy.md\"" + buildConfigField "String", "PRIVACY_POLICY_URL", "\"https://commons-app.github.io/privacy-policy\"" buildConfigField "String", "FILE_USAGES_BASE_URL", "\"https://commons.wikimedia.org/w/api.php?action=query&format=json&formatversion=2\"" buildConfigField "String", "ACCOUNT_TYPE", "\"fr.free.nrw.commons\"" buildConfigField "String", "CONTRIBUTION_AUTHORITY", "\"fr.free.nrw.commons.contributions.contentprovider\"" @@ -355,7 +355,7 @@ android { buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\"" buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Main_Page&welcome=yes\"" buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/Special:PasswordReset\"" - buildConfigField "String", "PRIVACY_POLICY_URL", "\"https://github.com/commons-app/commons-app-documentation/blob/master/android/Privacy-policy.md\"" + buildConfigField "String", "PRIVACY_POLICY_URL", "\"https://commons-app.github.io/privacy-policy\"" buildConfigField "String", "FILE_USAGES_BASE_URL", "\"https://commons.wikimedia.org/w/api.php?action=query&format=json&formatversion=2\"" buildConfigField "String", "ACCOUNT_TYPE", "\"fr.free.nrw.commons.beta\"" buildConfigField "String", "CONTRIBUTION_AUTHORITY", "\"fr.free.nrw.commons.beta.contributions.contentprovider\"" From f1f4e8baff84610ecef8f7ed86bd86809187eee6 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 20 Mar 2025 13:01:41 +0100 Subject: [PATCH 07/37] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-gl/strings.xml | 28 +++++++-------- app/src/main/res/values-pa/strings.xml | 50 ++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 2c1b78ff7..0cf3aed91 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -40,13 +40,13 @@ (%1$d) (%1$d) - - Iniciando %1$d carga - Iniciando %1$d cargas + + Procesando %d carga + Procesando %d cargas - - %1$d carga - %1$d cargas + + %d carga + %d cargas Esta imaxe quedará baixo a licenza %1$s @@ -80,16 +80,16 @@ A actualizar lendas e descricións Agarde un chisco… Accedeu correctamente! - Erro durante o inició de sesión! + Erro durante o inicio de sesión! Ficheiro non atopado. Por favor, probe con outro. Alcanzouse o límite máximo de reintentos! Cancele o envío e ténteo de novo Desactivar a optimización da batería? Fallou a autenticación. Inicie sesión de novo. A carga comezou! Envío en cola (modo de conexión limitado activado) - Cargouse \"%1$s\"! + Subiuse \"%1$s\"! Prema para ollar a súa carga - A enviar ficheiro: %s + A subir o ficheiro: %s Cargando \"%1$s\" Rematando a carga de \"%1$s\" Produciuse un erro ao enviar %1$s @@ -352,7 +352,7 @@ Agradecementos recibidos Imaxes destacadas Imaxes vía \"Lugares próximos\" - Nivel + Nivel %d Imaxes cargadas Imaxes non revertidas Imaxes usadas @@ -408,14 +408,14 @@ Non volver a preguntar isto nunca Solicitar permiso de localización Pedir permisos de localización cando sexa necesario para a funcionalidade de notificación de proximidade. - Algo foi mal, non puidemos obter as túas achegas + Algo foi mal e non puidemos obter os logros Finaliza o: Amosar campañas Ver as campañas en curso Permitir Descartar Xa non verá as campañas. Porén, pode volver habilitar esta notificación na configuración. - Esta función require conexión de rede, verifique a súa configuración de conexión. + Esta función necesita conexión de rede. Verifique a súa configuración de conexión. Houbo un erro ó procesar a imaxe. Por favor, ténteo de novoǃ Obter un identificador para editar Engadir modelo para o control de categoría @@ -462,8 +462,8 @@ Fallou Non foi posíbel solicitar a eliminación. Un autorretrato que non se emprega en ningún artigo - Borrosa - Sen sentido + completamente borrosa + sen sentido, totalmente inusable en calquera artigo Foto de prensa Foto aleatoria de internet Logo diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 6aa3bcae4..c87a2c6af 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -22,7 +22,7 @@ ਤਸਵੀਰ ਸਾਰੇ ਦਿਨ ਦੀ ਤਸਵੀਰ - + ੧ ਫ਼ਾਈਲ ਚੜ੍ਹਾਈ ਜਾ ਰਹੀ ਹੈ %1$d ਫ਼ਾਈਲਾਂ ਚੜ੍ਹਾਈਆਂ ਜਾ ਰਹੀਆਂ ਹਨ @@ -45,7 +45,7 @@ ਆਮ ਸੁਝਾਅ ਪਰਦੇਦਾਰੀ - ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ + ਵਿਕੀਮੀਡੀਆ ਸ਼ਾਮਲਾਟ ਪਸੰਦਾਂ ਚੜ੍ਹਾਉਣਾ ਜਾਰੀ ਐ ਵਰਤੋਂਕਾਰ ਨਾਂ @@ -180,8 +180,11 @@ ਕੋਈ ਉਪਲਬਧ ਨਹੀਂ 2FA ਕੋਡ ਕੀ ਤੁਸੀਂ ਸੱਚੀਂ ਬੰਦ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ? + ਲਾਮਾ + ਸਤਰੰਗੀ ਪੁਲ ਵਿਕੀਪੀਡੀਆ \'ਤੇ ਜੀ ਆਇਆਂ ਨੂੰ ਜੀ ਆਇਆਂ ਨੂੰ ਕਾਪੀਰਾਈਟ + ਸਿਡਨੀ ਓਪੇਰਾ ਹਾਊਸ ਰੱਦ ਕਰੋ ਖੋਲ੍ਹੋ ਬੰਦ ਕਰੋ @@ -191,10 +194,13 @@ ਬਾਰੇ ਤਰਜੀਹਾਂ ਸੁਝਾਅ + ਗਿੱਟਹਬ (GitHub) ਰਾਹੀਂ ਸੁਝਾਅ ਬਾਹਰ ਆਉ ਸਿਖਲਾਈ ਸੂਚਨਾਵਾਂ ਪਰਖੋ + ਕੋਈ ਵੇਰਵਾ ਨਹੀਂ ਮਿਲਿਆ + ਵਿਕੀਡਾਟਾ ਵਸਤਾਂ ਵਿਕੀਪੀਡੀਆ ਲੇਖ ਤਸਵੀਰ ਬਹੁਤ ਗੂੜ੍ਹੀ ਹੈ। ਤਸਵੀਰ ਧੁੰਦਲੀ ਹੈ। @@ -203,35 +209,75 @@ ਦਾਖ਼ਲ ਹੋਵੋ ਵਿਕੀਡੇਟਾ ਵਿਕੀਪੀਡੀਆ + ਸਾਨੂੰ ਦਰਜਾ ਦਿਓ ਅਕਸਰ ਪੁੱਛੇ ਜਾਂਦੇ ਸੁਆਲ + ਵਰਤੋਂਕਾਰ ਦਸਤਿਆਂ ਸਿਖਲਾਈ ਛੱਡੋ ਤਰਜਮਾ ਕਰੋ ਬੋਲੀਆਂ ਅੱਗੇ ਵਧੋ ਰੱਦ ਕਰੋ + ਮੁੜ-ਕੋਸ਼ਿਸ਼ ਕਰੋ + ਇਸ ਜਗ੍ਹਾ ਨੂੰ ਇੱਕ ਤਸਵੀਰ ਦੀ ਲੋੜ ਏ। + ਇਸ ਜਗ੍ਹਾ \'ਤੇ ਪਹਿਲਾਂ ਹੀ ਇੱਕ ਤਸਵੀਰ ਏ। + ਇਹ ਜਗ੍ਹਾ ਹੁਣ ਮੌਜੂਦ ਨਹੀਂ ਏ। + ਕੋਈ ਤਸਵੀਰ ਨਹੀਂ ਲੱਭੀ! + ਤਸਵੀਰ ਚੜਾਉਨ ਵੇਲੇ ਗਲਤੀ ਆਈ ਏ। + %1$s: ਵੱਲੋਂ ਚੜ੍ਹਾਈ ਗਈ + ਰੋਕ ਲਾਈ ਗਈ + ਦਿਨ ਦੀ ਤਸਵੀਰ ਲੱਭੋ ਲੱਭੋ ਹਾਲੀਆ ਖੋਜਾਂ: ਹਾਲ ਦੀਆਂ ਪੁੱਛਗਿੱਛ ਖੋਜਾਂ ਹਾਲ ਹੀ ਵਿੱਚ ਬੋਲੀਆਂ ਬਾਰੇ ਪੁੱਛਗਿੱਛ ਸ਼੍ਰੇਣੀਆਂ + ਵਸਤਾਂ ਨਕਸ਼ਾ ਸਵਾਲ + ਨਤੀਜਾ ਤੁਹਾਡੇ ਦਾਖਲੇ ਦੀ ਮਿਆਦ ਪੁੱਗ ਗਈ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ ਮੁੜ ਦਾਖਲ ਹੋਵੋ। ਜਾਰੀ ਰੱਖੋ + ਸਹੀ ਜਵਾਬ + ਗਲਤ ਜਵਾਬ + ਐਪ ਸਾਂਝਾ ਕਰੋ + ਘੁੰਮਾਓ + ਨੇੜਲੀਆਂ ਥਾਵਾਂ ਲੋਡ ਨਹੀਂ ਕੀਤੀਆਂ ਜਾ ਸਕੀਆਂ + ਇਸ ਖੇਤਰ ਵਿੱਚ ਕੋਈ ਤਸਵੀਰਾਂ ਨਹੀਂ ਹਨ। + ਆਲੇ-ਦੁਆਲੇ ਕੋਈ ਨੇੜਲੀ ਥਾਂ ਨਹੀਂ ਏ। + ਨੇਡ਼ਲੇ ਸਮਾਰਕਾਂ ਨੂੰ ਲਿਆਉਣ ਵਿੱਚ ਗਲਤੀ। ਕੋਈ ਤਾਜ਼ਾ ਖੋਜ ਨਹੀਂ + ਕੀ ਤੁਸੀਂ ਯਕੀਨੀ ਤੌਰ ਉੱਤੇ ਆਪਣੇ ਖੋਜ ਇਤਿਹਾਸ ਨੂੰ ਸਾਫ਼ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ? + ਕੀ ਤੁਸੀਂ ਇਸ ਖੋਜ ਨੂੰ ਮਿਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ? + ਖੋਜ ਇਤਿਹਾਸ ਮਿਟਾਇਆ ਗਿਆ + ਮਿਟਾਉਣ ਲਈ ਨਾਮਜ਼ਦ ਕਰੋ ਮਿਟਾਓ ਪ੍ਰਾਪਤੀਆਂ + ਪ੍ਰੋਫਾਈਲ ਅੰਕੜੇ ਧੰਨਵਾਦ ਪ੍ਰਾਪਤ ਹੋਏ + ਵਿਸ਼ੇਸ਼ ਤਸਵੀਰ + \"ਨੇੜਲੀਆਂ ਥਾਵਾਂ\" ਰਾਹੀਂ ਤਸਵੀਰਾਂ ਪੱਧਰ %d + ਤਸਵੀਰਾਂ ਚੜ੍ਹਾਇਆਂ ਗਈਆਂ + ਤਸਵੀਰਾਂ ਵਾਪਸ ਨਹੀਂ ਕੀਤੀ ਗਈਆਂ + ਵਰਤੀ ਗਈਆਂ ਤਸਵੀਰਾਂ ਆਪਣੀਆਂ ਪ੍ਰਾਪਤੀਆਂ ਨੂੰ ਆਪਣੇ ਦੋਸਤਾਂ ਨਾਲ ਸਾਂਝਾ ਕਰੋ! + ਘੱਟੋ-ਘੱਟ ਲੋੜੀਂਦਾ: + ਗਲਤੀ ਆਈ! + ਨੇੜੇ-ਤੇੜੇ + ਸੂਚਨਾਵਾਂ ਸੂਚਨਾਵਾਂ (ਪੜ੍ਹਿਆਂ) ਸੂਚੀ ਅੱਗੇ ਪਿਛਲਾ + ਤਸਵੀਰਾਂ ਟਿਕਾਣਾ ਸ਼੍ਰੇਣੀਆਂ + ਇਸ ਖੇਤਰ ਵਿੱਚ ਖੋਜ ਕਰੋ + ਇਜਾਜ਼ਤ ਦੀ ਬੇਨਤੀ + ਇਹ ਮੁੜ ਕਦੇ ਨਾ ਪੁੱਛੋ + ਟਿਕਾਣੇਂ ਦੀ ਆਗਿਆ ਮੰਗੋ ਨੂੰ ਮਿਆਦ ਪੁਗਦੀ ਮੁਹਿੰਮਾਂ ਵੇਖਾਓ ਇਜਾਜ਼ਤ ਦਿਓ From 2e05a58e8ba545846b45b699b7bb865b95262ff8 Mon Sep 17 00:00:00 2001 From: Ritika Pahwa <83745993+RitikaPahwa4444@users.noreply.github.com> Date: Sat, 22 Mar 2025 14:07:51 +0530 Subject: [PATCH 08/37] Bump up version code to 1049 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 8e29852e1..41b3d85de 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -212,7 +212,7 @@ android { defaultConfig { //applicationId 'fr.free.nrw.commons' - versionCode 1048 + versionCode 1049 versionName '5.2.0' setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) From 5a5e660a4360624720d42fb7929c9c356cefed52 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 24 Mar 2025 13:01:43 +0100 Subject: [PATCH 09/37] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-io/strings.xml | 29 ++++++++++++++++++++++ app/src/main/res/values-krc/strings.xml | 23 ++++++++++++++++- app/src/main/res/values-pt-rBR/strings.xml | 2 +- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-io/strings.xml b/app/src/main/res/values-io/strings.xml index 057e77e10..294b72208 100644 --- a/app/src/main/res/values-io/strings.xml +++ b/app/src/main/res/values-io/strings.xml @@ -385,8 +385,11 @@ Avizi Avizi (lektita) Montrez proxima avizo + Montrar avizo en utensilo \'\'app\'\' pri la maxim proxima loko qua bezonas pikturi Listo Permiso pri enmagazinigo + Ni bezonas vua permiso por acesar l\'extera enmagazinigado di vua utensilo, por sendar imaji. + Vu ne pluse vidos la maxim proxima loko qua bezonas fotografuro. Tamen, vu povas kapabligar itere ca avizo en Ajusti (\'\'Settings\'\'), se vu deziros. Etapo %1$d de %2$d: %3$s Sequanta Antea @@ -411,17 +414,32 @@ Nula deskripturo selektita Cesar kargajo Durar kargajo + (Por omna imaji en la grupo) Serchez ca areo Permiso bezonata Ka vu deziras ke ni uzez vua nuna lokizo por montrar vicina loki qui bezonas imaji? + Ne esas posibla montrar la maxim proxima loko qua bezonas imajo, se vu ne montros vua lokizo Ne pluse demandez to Demandar lokala permiso Demandez lokala permiso, kande bezonata por uzar karto montranta proximeso. + Ulu faliis, ni ne povis montrar vua sucesi + Vu facis plu multa kontributaji, e nia kalkulo-utensilo ne povis kalkular li. To esis la maxim importanta suceso. Finas la: Montrez kampanii Videz la kampanii duranta + Permisez ke l\'utensilo (\'\'app\'\') lokizez, se la kamero ne enrejistros la lokizo. Kelka kameri ne havas utensilo por enrejistrar lokizo. Cakaze, vua kontributado divenos plu utila se vu permisos ke l\'\'\'app\'\' prenez ed enrejistrez lokizi. Vu povos abrogar ca permiso irgatempe en Ajusti (\'\'Settings\'\') Permisar Eskartar + Voluntez kapabligar registrago di lokizo en \'\'Settings\'\', e probez itere.\n\nNoto: l\'arkivo sendanta povas ne havar informo pri lokizo, se l\'\'\'app\'\' ne povas rekuperar l\'informo pri lokizo en kurta intervalo. + Kaptanta \'\'token\'\' por redaktar. + Adjuntanta shablono por verifikar kategorio + Demandanta verifiko di kategorio por %1$s + Demandanta verifiko di kategorio + Verifiko di kategorio demandita + Demando pri verifiko di kategorio ne funcionis + Dema%1$sndita verifiko di kategorio por %1$s + Ne povis demandar verifiko di kategorio por %1$s + Demandanta verifiko di kategorio por %1$s Facita Sendanta danko: Suceso Danko sendita sucese a %1$s @@ -430,6 +448,7 @@ Sendanta danko a %1$s Ka to obedias la reguli pri autoroyuro? Ka lua kategorio esas korekta? + Ka to apartenas al skopo dil projeto? Ka vu deziras dankar la kontributero? Kliktez NO por indikar ca imajo por efaco, se ol ne havas irga utileso. Ho, to ne mem havas kategorio! @@ -452,9 +471,11 @@ Vartez... Kopiita Exempli pri bona imaji por sendar a Commons + Exempli pri imaji por NE SENDAR Saltez ca imajo Descharjo faliis!! Ni ne povis descharjar l\'arkivo sen permiso pri extera konservo. Administrar etiketi EXIF + Dum sendo di arkivi, selektez quala etiketi EXIF devas mantenesar Autoro Autoroyuro Loko @@ -465,10 +486,14 @@ Informo pri imajo Nula kategorio trovesis implicita deskripto-linguo + Indikita por efaco Suceso + Indikita %1$s por efaco. Faliis Ne povis demandar efaco. + \'\'selfie\'\'-imajo qua ne uzesas en irga artiklo komplete neklara + sensencajo, qua ne povas uzesar en irga artiklo Fotografuro de komunikilaro Hazarda imajo de Interreto Emblemo @@ -534,9 +559,13 @@ Koloro obskura Koloro klara Charjez pluse + Adjuntar imajo a Wikipedio + Ka vu deziras adjuntar ca imajo al artiklo de Wikipedio en idiomo %1$s? Konfirmez Instrucioni 1. Uzez la sequanta wikitexto: + Kliktanta \"konfirmar\" (\'\'Confirm\'\') apertos l\'artiklo che Wikipedio + 3. Trovez adequata fako dil artiklo por inkluzar vua imajo pauzar durigar Pauzita diff --git a/app/src/main/res/values-krc/strings.xml b/app/src/main/res/values-krc/strings.xml index bf4f31998..81d8842ff 100644 --- a/app/src/main/res/values-krc/strings.xml +++ b/app/src/main/res/values-krc/strings.xml @@ -101,6 +101,8 @@ Суратха ал Джуўукъда Джюклегенлерим + Джибериуню копия эт + Джибериу алмашдырыу буферге копия этилгенди Юлюшле Файлны бетине къара Тюб джазыу (Амалсыз) @@ -274,6 +276,7 @@ Викитекстни алмашдырыу буферге копия эт Викитекст алмашдырыу буферге копия этилди Джууукъдагыла тюз ишлеялмайды, Локация хайырландырылалмайды. + Интернет джетишмейди. Къуру кэш этилген джерле кёргюзюледиле. Локациягъа джетишиу уналмады. Бу функцияны хайырланыр ючюн, тилейбиз, локациягъызны къолугъуз бла белгилегиз. Джууукъдагъы джерле тизмени кёргюзюр ючююн, эркинлик берирге керекди Джууукъдагъы суратла тизмени кёргюзюр ючююн, эркинлик берирге керекди @@ -357,11 +360,13 @@ Кетер Джетишимле Профиль + Белгичикле Статистика Бюсюреуле Алындыла Сайланнган Суратла \"Джууукъдагъы Джерле\" юсю бла суратла - Дараджа + Дараджа %d + %s (Дараджа %s) Суратла Джюклендиле Суратла Кери Алынмадыла Суратла Хайырландыла @@ -393,6 +398,7 @@ Девайсыгъызда келишген картография къошакъ табылмады. Тилейбиз, бу энчиликни хайырландырыр ючюн картография къошакъ джюклегиз. Суратла Локацияла + Категорияла Китаб белгилени къош/къорат Китаб белгиле Алкъын чырт китаб белги къошмадыгъыз @@ -794,4 +800,19 @@ Бу джерни сураты джокъду, хайда бирин эт! Бу джерни алайсыз да сураты барды. Бу джерни сураты болуб-болмагъанын тинте турама. + Джюкленнген заманда халат + Бир хайырланыучу да табылмады + Гёзен + Башха викиле + Файлны хайырланыулары + SingleWebViewActivity + Хыйсаб + Хыйсабны сюрт + Хыйсаб сюртюуню эсгертиую + Джокъ этиу — <b>ахыр амалды</b>, эмда аны <b>тюзетиуню тамамы бла тохтатыргъа излегесиз хайырланыргъа керекди</b>, неда эскиде ассоциацияланы бир мадар болуб аслам джашырыргъа излесегиз.<br/><br />Викигёзенде хыйсабны кетериу, башхала хыйсабны джокъ этиу атны джюрютген процессде сизни кошумугъузну танымазча, хыйсабыгъызны атын тюрлендириу бла этиледи.<b>Джокъ этиу толу анонимликни гарантия этмейди эмда проектде къошумларыгъызны къоратмайды</b>. + Тюб джазыу + Тюб джазыу алмашдырыу буферге копия этилгенди + Алгъышлайбыз, бу альбомна бютеу сратла не джюкленнгендиле, неда джюкленирге джораланмагъанлача белгиленнгендиле. + Explore-де кёргюз + Nearby-да кёргюз diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index da2de6c82..2949a707a 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -419,7 +419,7 @@ Favoritos Não adicionou nenhum favorito Favoritos - A recolha de registos foi iniciada. REINICIE a aplicação, execute a operação que pretende registar e prima outra vez \"Enviar ficheiro de registos\" + A coleta de registros foi iniciada. REINICIE o aplicativo, execute a operação que pretende registrar e toque em ‘Enviar arquivo de registros’ novamente Eu fiz o carregamento por engano Eu não sabia que seria publicamente visível Eu percebi que é ruim para minha privacidade From 669f3043aea15512b238c70acad398af305a470e Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 27 Mar 2025 13:01:54 +0100 Subject: [PATCH 10/37] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-diq/strings.xml | 2 +- app/src/main/res/values-lb/strings.xml | 3 ++- app/src/main/res/values-pa/strings.xml | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-diq/strings.xml b/app/src/main/res/values-diq/strings.xml index 5ffda9f2e..f6bbf1f0e 100644 --- a/app/src/main/res/values-diq/strings.xml +++ b/app/src/main/res/values-diq/strings.xml @@ -315,7 +315,7 @@ Resımi vıla kerê pê Hewna to iştirak nêkerdo Hesab vıraziya! - Metın kopyayê panoyi biyo + Metın be panoyi ra kopya bi Pêhesnayışi wanaye nışan bıkerê Yew xeta biye! Weziyetê cayi: diff --git a/app/src/main/res/values-lb/strings.xml b/app/src/main/res/values-lb/strings.xml index 2ef0b7c0a..65574cdb9 100644 --- a/app/src/main/res/values-lb/strings.xml +++ b/app/src/main/res/values-lb/strings.xml @@ -70,7 +70,7 @@ D\'Authentifizéierung huet net funktionéiert. Loggt Iech wgl. nach eng Kéier an. D\'Eroplueden huet ugefaang! %1$s eropgelueden! - Dréckt fir de Fichier ze gesinn deen Dir eropgelueden hutt + Tippt fir de Fichier ze gesinn, deen Dir eropgelueden hutt Fichier eroplueden: %s %1$s gëtt eropgelueden Eropluede vu(n) %1$s ofschléissen @@ -274,6 +274,7 @@ Sichen Op Commons sichen Sichen + Rezent gesicht: Medien Kategorien Elementer diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index c87a2c6af..fa2051047 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -265,6 +265,7 @@ ਆਪਣੀਆਂ ਪ੍ਰਾਪਤੀਆਂ ਨੂੰ ਆਪਣੇ ਦੋਸਤਾਂ ਨਾਲ ਸਾਂਝਾ ਕਰੋ! ਘੱਟੋ-ਘੱਟ ਲੋੜੀਂਦਾ: ਗਲਤੀ ਆਈ! + ਯੋਗਦਾਨ ਨੇੜੇ-ਤੇੜੇ ਸੂਚਨਾਵਾਂ ਸੂਚਨਾਵਾਂ (ਪੜ੍ਹਿਆਂ) From 44966645cabb71dfbd47b42d71ef7ccc39f9d061 Mon Sep 17 00:00:00 2001 From: Ritika Pahwa <83745993+RitikaPahwa4444@users.noreply.github.com> Date: Sat, 29 Mar 2025 13:21:03 +0530 Subject: [PATCH 11/37] Add v5.2.0 to CHANGELOG.md --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7accf82b..0da417cfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Wikimedia Commons for Android +## v5.2.0 + +v5.2.0 boasts several new functionalities like: + +* A new refresh button lets you quickly reload the Nearby map +* Bookmarks now support categories +* Improved feedback and consistency in the user interface +* Bug fixes and performance improvements + +### What's changed +* Implement "Refresh" button to clear the cache and reload the Nearby map. +* `CommonsApplication` migrate to kotlin & some lint fixes. +* Revert back to MainScope for database and UI updates and make database operations thread safe. +* Hide edit options for logged-out users in Explore screen. +* Introduced a button to delete the current folder in custom selector. +* Improve Unique File Name Search. +* Migration of several modules from Java to Kotlin. +* Fix modification on bottom sheet's data when coming from Nearby Banner and clicked on other pins. +* Bug fixes and enhancement of Achievements screen. +* Show where file is being used on Commons and other wikis. +* Migrate android.media.ExifInterface to androidx.exifinterface.media.ExifInterface as android.media.ExifInterface had security flaws on older devices. +* Make dialogs modal and always show the upload icon. +* Fix unintentional deletion of subfolders and non-images by custom selector. +* Bookmark categories. +* Add pull down to refresh in the Contributions screen. +* Fix race condition and lag when loading pin details, faster overlay management. +* Show cached pins in Nearby even when internet is unavailable + + Full changelog with the list of contributors: [`v5.1.2...v5.2.0`](https://github.com/commons-app/apps-android-commons/compare/v5.1.2...v5.2.0). + + ## v5.1.2 ### What's changed From 6e090c8d7a4e1f1e1b0dec574a67653662f93322 Mon Sep 17 00:00:00 2001 From: Jason-Whitmore Date: Mon, 31 Mar 2025 01:49:06 -0700 Subject: [PATCH 12/37] ExploreMapFragment.java: fix marker labels in Explore map fragment to display the username (#6260) Before this change, the labels that would appear on the marker when tapped did not include the author or username. Instead, it displayed "Unknown". After this change, the labels now display the author name. If the author name is not available, the username will be displayed. If both are unavailable, the default value of "Unknown" will be displayed. To improve the readability of the text, any HTML text is removed from the username/author. --- .../explore/map/ExploreMapFragment.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java index 1b1659182..f5657dd1b 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java @@ -708,8 +708,17 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment GeoPoint point = new GeoPoint( nearbyBaseMarker.getPlace().location.getLatitude(), nearbyBaseMarker.getPlace().location.getLongitude()); - OverlayItem item = new OverlayItem(nearbyBaseMarker.getPlace().name, null, - point); + + Media markerMedia = this.getMediaFromImageURL(nearbyBaseMarker.getPlace().pic); + String authorUser = null; + if (markerMedia != null) { + authorUser = markerMedia.getAuthorOrUser(); + // HTML text is sometimes part of the author string and needs to be removed + authorUser = Html.fromHtml(authorUser, Html.FROM_HTML_MODE_LEGACY).toString(); + } + + OverlayItem item = new OverlayItem(nearbyBaseMarker.getPlace().name, + authorUser, point); item.setMarker(d); items.add(item); ItemizedOverlayWithFocus overlay = new ItemizedOverlayWithFocus(items, @@ -740,6 +749,26 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment } } + /** + * Retrieves the specific Media object from the mediaList field. + * @param url The specific Media's image URL. + * @return The Media object that matches the URL or null if it could not be found. + */ + private Media getMediaFromImageURL(String url) { + if (mediaList == null || url == null) { + return null; + } + + for (int i = 0; i < mediaList.size(); i++) { + if (mediaList.get(i) != null && mediaList.get(i).getImageUrl() != null + && mediaList.get(i).getImageUrl().equals(url)) { + return mediaList.get(i); + } + } + + return null; + } + /** * Removes a marker from the map based on the specified NearbyBaseMarker. * From fdfd7781e94071b350db06d17cd01bb001d18ff7 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 31 Mar 2025 14:01:52 +0200 Subject: [PATCH 13/37] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-fr/strings.xml | 6 ++++++ app/src/main/res/values-zgh/strings.xml | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5aeda5299..179bef168 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -853,8 +853,14 @@ Autres wikis Utilisations du fichier + SingleWebViewActivity + Compte + Faire disparaître le compte + Avertissement de disparition du compte + La disparition est un <b>dernier recours</b> et ne devrait être <b>utilisée que quand vous voulez arrêter d’éditer pour toujours</b> et aussi pour cacher autant que possible vos associations passées.<br/><br/>La suppression de compte sur Wikimedia Commons se fait en changeant le nom de votre compte de sorte que les autres ne peuvent pas identifier vos contributions, lors d’un processus appelé disparition du compte. <b>La disparition ne garantit pas l’anonymat complet ni ne supprime les contributions de vos projets</b>. Légende Légende copiée dans le presse-papier + Félicitations, toutes les images dans cet album ont été soit téléchargées soit marquées comme non téléchargeables. Afficher dans Explorer Afficher à proximité diff --git a/app/src/main/res/values-zgh/strings.xml b/app/src/main/res/values-zgh/strings.xml index 27080b999..b3615cefc 100644 --- a/app/src/main/res/values-zgh/strings.xml +++ b/app/src/main/res/values-zgh/strings.xml @@ -1,5 +1,6 @@ @@ -79,7 +80,7 @@ ⵉⴼⵔⵙ ⵉⵙⵉⴹⵏⵏ ⵜⵉⵙⵖⴰⵍ - ⴰⵏⵙⵙⵎⵔⵙ + ⴰⵏⵙⵎⵔⴰⵙ ⵙⵙⵉⴹⵏ ⴰⵙⴳⵯⵙⴰⵏ ⴰⵏⵎⵍⴰⵙⵙ From 731ff62fafa2fc656e046d894a77d0d07ea64a70 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 3 Apr 2025 14:01:45 +0200 Subject: [PATCH 14/37] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-es/strings.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index a8b05824b..8622b6944 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -22,6 +22,7 @@ * Fitoschido * Gastonsaenz * Hasley +* HylianAngel * Ihojose * JO777 * Jack30 @@ -150,6 +151,7 @@ Tomar una foto Cercanos Mis subidas + Copiar enlace Compartir Ver página del archivo Leyenda (requerido) @@ -413,7 +415,7 @@ Agradecimientos recibidos Imágenes destacadas Imágenes vía \"Sitios Cercanos\" - Nivel + Nivel %d Imágenes subidas Imágenes no revertidas Imágenes utilizadas From 51da9e4dd667e5dd403114f4a970055ec1cb9cb2 Mon Sep 17 00:00:00 2001 From: Prinuel <78011576+Prinuel@users.noreply.github.com> Date: Thu, 3 Apr 2025 13:10:41 +0100 Subject: [PATCH 15/37] FooterAdapter.kt: changed enum access of FooterItem, from FooterItem.values to FooterItem.entries, this is the more efficient way of accessing Enum values as introduced in kotlin 1.9 (#6271) Co-authored-by: bethel-m --- .../java/fr/free/nrw/commons/explore/paging/FooterAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/fr/free/nrw/commons/explore/paging/FooterAdapter.kt b/app/src/main/java/fr/free/nrw/commons/explore/paging/FooterAdapter.kt index fc5f529b2..d739b35ee 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/paging/FooterAdapter.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/paging/FooterAdapter.kt @@ -32,7 +32,7 @@ class FooterAdapter( override fun onCreateViewHolder( parent: ViewGroup, viewType: Int, - ) = when (FooterItem.values()[viewType]) { + ) = when (FooterItem.entries[viewType]) { FooterItem.LoadingItem -> LoadingViewHolder( parent.inflate(R.layout.list_item_progress), From 7bf9276d1a090fb8bf72e1047f89416bd231fae0 Mon Sep 17 00:00:00 2001 From: Rohit Verma <101377978+rohit9625@users.noreply.github.com> Date: Sat, 5 Apr 2025 19:17:27 +0530 Subject: [PATCH 16/37] fix: resolve IndexOutOfBounds error when removing images from top card (#6124) replace deprecated onBackPressed with onBackPressedCallback remove unit test for deprecated onBackPressed method remove if-check before deleting picture to prevent hiding top thumbnail card hide the thumbnail card on fragments other than MediaDetailFragment Co-authored-by: Nicolas Raoul --- .../free/nrw/commons/upload/UploadActivity.kt | 132 ++++++++++-------- .../nrw/commons/upload/UploadPresenter.kt | 47 +++---- .../mediaDetails/UploadMediaDetailFragment.kt | 2 +- .../mediaDetails/UploadMediaPresenter.kt | 12 +- .../commons/upload/UploadActivityUnitTests.kt | 11 -- 5 files changed, 107 insertions(+), 97 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt index 020284934..ee0b21210 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt @@ -14,6 +14,7 @@ import android.os.Bundle import android.provider.Settings import android.view.View import android.widget.CheckBox +import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager @@ -122,7 +123,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C /** * Set the value of the showPermissionDialog variable. * - * @param showPermissionsDialog `true` to indicate to show + * @property isShowPermissionsDialog `true` to indicate to show * Permissions Dialog if permissions are missing, `false` otherwise. */ /** @@ -166,6 +167,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C private var _binding: ActivityUploadBinding? = null private val binding: ActivityUploadBinding get() = _binding!! + private lateinit var onBackPressedCallback: OnBackPressedCallback + @SuppressLint("CheckResult") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -173,6 +176,23 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C _binding = ActivityUploadBinding.inflate(layoutInflater) setContentView(binding.root) + // Overrides the back button to make sure the user is prepared to lose their progress + onBackPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + showAlertDialog( + this@UploadActivity, + getString(R.string.back_button_warning), + getString(R.string.back_button_warning_desc), + getString(R.string.back_button_continue), + getString(R.string.back_button_warning), + null + ) { + finish() + } + } + } + onBackPressedDispatcher.addCallback(this, onBackPressedCallback) + /* If Configuration of device is changed then get the new fragments created by the system and populate the fragments ArrayList @@ -187,7 +207,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C } init() - binding.rlContainerTitle.setOnClickListener { v: View? -> onRlContainerTitleClicked() } + binding.rlContainerTitle.setOnClickListener { _: View? -> onRlContainerTitleClicked() } nearbyPopupAnswers = mutableMapOf() //getting the current dpi of the device and if it is less than 320dp i.e. overlapping //threshold, thumbnails automatically minimizes @@ -201,7 +221,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C } locationManager!!.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER) locationManager!!.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) - store = BasicKvStore(this, storeNameForCurrentUploadImagesSize).apply { + store = BasicKvStore(this, STORE_NAME_FOR_CURRENT_UPLOAD_IMAGE_SIZE).apply { clearAll() } checkStoragePermissions() @@ -241,7 +261,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C override fun onPageSelected(position: Int) { currentSelectedPosition = position - if (position >= uploadableFiles!!.size) { + if (position >= uploadableFiles.size) { binding.cvContainerTopCard.visibility = View.GONE } else { thumbnailsAdapter!!.notifyDataSetChanged() @@ -274,7 +294,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .filter { result: Boolean? -> result!! } - .subscribe { result: Boolean? -> + .subscribe { _: Boolean? -> showAlertDialog( this, getString(R.string.block_notification_title), @@ -284,7 +304,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C }) } - fun checkStoragePermissions() { + private fun checkStoragePermissions() { // Check if all required permissions are granted val hasAllPermissions = hasPermission(this, PERMISSIONS_STORAGE) val hasPartialAccess = hasPartialAccess(this) @@ -355,7 +375,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C showLongToast(this, messageResourceId) } - override fun getUploadableFiles(): List? { + override fun getUploadableFiles(): List { return uploadableFiles } @@ -367,6 +387,14 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C override fun onUploadMediaDeleted(index: Int) { fragments!!.removeAt(index) //Remove the corresponding fragment uploadableFiles.removeAt(index) //Remove the files from the list + + val isMediaDetailFragment = fragments!!.getOrNull(currentSelectedPosition)?.let { + it is UploadMediaDetailFragment + } ?: false + if(!isMediaDetailFragment) { + // Should hide the top card current fragment is not the media detail fragment + showHideTopCard(false) + } thumbnailsAdapter!!.notifyItemRemoved(index) //Notify the thumbnails adapter uploadImagesAdapter!!.notifyDataSetChanged() //Notify the ViewPager } @@ -375,8 +403,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C binding.tvTopCardTitle.text = resources .getQuantityString( R.plurals.upload_count_title, - uploadableFiles!!.size, - uploadableFiles!!.size + uploadableFiles.size, + uploadableFiles.size ) } @@ -444,15 +472,16 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C receiveInternalSharedItems() } - if (uploadableFiles == null || uploadableFiles!!.isEmpty()) { + if (uploadableFiles.isEmpty()) { handleNullMedia() } else { //Show thumbnails - if (uploadableFiles!!.size > 1) { - if (!defaultKvStore.getBoolean("hasAlreadyLaunchedCategoriesDialog")) { //If there is only file, no need to show the image thumbnails + if (uploadableFiles.size > 1) { + if (!defaultKvStore.getBoolean("hasAlreadyLaunchedCategoriesDialog")) { + // If there is only file, no need to show the image thumbnails showAlertDialogForCategories() } - if (uploadableFiles!!.size > 3 && + if (uploadableFiles.size > 3 && !defaultKvStore.getBoolean("hasAlreadyLaunchedBigMultiupload") ) { showAlertForBattery() @@ -464,8 +493,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C binding.tvTopCardTitle.text = resources .getQuantityString( R.plurals.upload_count_title, - uploadableFiles!!.size, - uploadableFiles!!.size + uploadableFiles.size, + uploadableFiles.size ) @@ -474,7 +503,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C } - for (uploadableFile in uploadableFiles!!) { + for (uploadableFile in uploadableFiles) { val uploadMediaDetailFragment = UploadMediaDetailFragment() if (!uploadIsOfAPlace) { @@ -497,8 +526,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C object : UploadMediaDetailFragmentCallback { override fun deletePictureAtIndex(index: Int) { store!!.putInt( - keyForCurrentUploadImagesSize, - (store!!.getInt(keyForCurrentUploadImagesSize) - 1) + KEY_FOR_CURRENT_UPLOAD_IMAGE_SIZE, + (store!!.getInt(KEY_FOR_CURRENT_UPLOAD_IMAGE_SIZE) - 1) ) presenter!!.deletePictureAtIndex(index) } @@ -576,11 +605,11 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C fragments!!.add(mediaLicenseFragment!!) } else { for (i in 1 until fragments!!.size) { - fragments!![i]!!.callback = object : UploadBaseFragment.Callback { + fragments!![i].callback = object : UploadBaseFragment.Callback { override fun onNextButtonClicked(index: Int) { if (index < fragments!!.size - 1) { binding.vpUpload.setCurrentItem(index + 1, false) - fragments!![index + 1]!!.onBecameVisible() + fragments!![index + 1].onBecameVisible() (binding.rvThumbnails.layoutManager as LinearLayoutManager) .scrollToPositionWithOffset( if ((index > 0)) index - 1 else 0, @@ -594,7 +623,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C override fun onPreviousButtonClicked(index: Int) { if (index != 0) { binding.vpUpload.setCurrentItem(index - 1, true) - fragments!![index - 1]!!.onBecameVisible() + fragments!![index - 1].onBecameVisible() (binding.rvThumbnails.layoutManager as LinearLayoutManager) .scrollToPositionWithOffset( if ((index > 3)) index - 2 else 0, @@ -632,11 +661,12 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C binding.vpUpload.offscreenPageLimit = fragments!!.size } // Saving size of uploadableFiles - store!!.putInt(keyForCurrentUploadImagesSize, uploadableFiles!!.size) + store!!.putInt(KEY_FOR_CURRENT_UPLOAD_IMAGE_SIZE, uploadableFiles.size) } /** - * Changes current image when one image upload is cancelled, to highlight next image in the top thumbnail. + * Changes current image when one image upload is cancelled, to highlight next image in the top + * thumbnail. * Fixes: [Issue](https://github.com/commons-app/apps-android-commons/issues/5511) * * @param index Index of image to be removed @@ -771,7 +801,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C override fun onNextButtonClicked(index: Int) { if (index < fragments!!.size - 1) { binding.vpUpload.setCurrentItem(index + 1, false) - fragments!![index + 1]!!.onBecameVisible() + fragments!![index + 1].onBecameVisible() (binding.rvThumbnails.layoutManager as LinearLayoutManager) .scrollToPositionWithOffset(if ((index > 0)) index - 1 else 0, 0) if (index < fragments!!.size - 4) { @@ -786,10 +816,10 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C override fun onPreviousButtonClicked(index: Int) { if (index != 0) { binding.vpUpload.setCurrentItem(index - 1, true) - fragments!![index - 1]!!.onBecameVisible() + fragments!![index - 1].onBecameVisible() (binding.rvThumbnails.layoutManager as LinearLayoutManager) .scrollToPositionWithOffset(if ((index > 3)) index - 2 else 0, 0) - if ((index != 1) && ((index - 1) < uploadableFiles!!.size)) { + if ((index != 1) && ((index - 1) < uploadableFiles.size)) { // Shows the top card if it was hidden because of the last image being deleted and // now the user has hit previous button to go back to the media details showHideTopCard(true) @@ -797,7 +827,10 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C } } - override fun onThumbnailDeleted(position: Int) = presenter!!.deletePictureAtIndex(position) + override fun onThumbnailDeleted(position: Int) { + presenter!!.deletePictureAtIndex(position) + thumbnailsAdapter?.notifyDataSetChanged() + } /** * The adapter used to show image upload intermediate fragments @@ -824,11 +857,11 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C } - fun onRlContainerTitleClicked() { + private fun onRlContainerTitleClicked() { binding.rvThumbnails.visibility = if (isTitleExpanded) View.GONE else View.VISIBLE isTitleExpanded = !isTitleExpanded - binding.ibToggleTopCard.rotation = binding.ibToggleTopCard.rotation + 180 + binding.ibToggleTopCard.rotation += 180 } override fun onDestroy() { @@ -845,21 +878,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C if (uploadCategoriesFragment != null) { uploadCategoriesFragment!!.callback = null } - } - - /** - * Overrides the back button to make sure the user is prepared to lose their progress - */ - @SuppressLint("MissingSuperCall") - override fun onBackPressed() { - showAlertDialog( - this, - getString(R.string.back_button_warning), - getString(R.string.back_button_warning_desc), - getString(R.string.back_button_continue), - getString(R.string.back_button_warning), - null - ) { finish() } + onBackPressedCallback.remove() } /** @@ -879,7 +898,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C .setView(view) .setTitle(getString(R.string.multiple_files_depiction_header)) .setMessage(getString(R.string.multiple_files_depiction)) - .setPositiveButton("OK") { dialog: DialogInterface?, which: Int -> + .setPositiveButton("OK") { _: DialogInterface?, _: Int -> if (checkBox.isChecked) { // Save the user's choice to not show the dialog again defaultKvStore.putBoolean("hasAlreadyLaunchedCategoriesDialog", true) @@ -913,14 +932,14 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C getString(R.string.cancel), { /* Since opening the right settings page might be device dependent, using - https://github.com/WaseemSabir/BatteryPermissionHelper - directly appeared like a promising idea. - However, this simply closed the popup and did not make - the settings page appear on a Pixel as well as a Xiaomi device. - Used the standard intent instead of using this library as - it shows a list of all the apps on the device and allows users to - turn battery optimisation off. - */ + https://github.com/WaseemSabir/BatteryPermissionHelper + directly appeared like a promising idea. + However, this simply closed the popup and did not make + the settings page appear on a Pixel as well as a Xiaomi device. + Used the standard intent instead of using this library as + it shows a list of all the apps on the device and allows users to + turn battery optimisation off. + */ val batteryOptimisationSettingsIntent = Intent( Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS ) @@ -958,7 +977,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C Also, location information is discarded if the difference between current location and location recorded just before capturing the image is greater than 100 meters */ - if (isLocationTagUnchecked || locationDifference > 100 || !defaultKvStore.getBoolean("inAppCameraLocationPref") + if (isLocationTagUnchecked || locationDifference > 100 + || !defaultKvStore.getBoolean("inAppCameraLocationPref") || !isInAppCameraUpload ) { currLocation = null @@ -979,8 +999,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C @JvmField var nearbyPopupAnswers: MutableMap? = null - const val keyForCurrentUploadImagesSize: String = "CurrentUploadImagesSize" - const val storeNameForCurrentUploadImagesSize: String = "CurrentUploadImageQualities" + const val KEY_FOR_CURRENT_UPLOAD_IMAGE_SIZE: String = "CurrentUploadImagesSize" + const val STORE_NAME_FOR_CURRENT_UPLOAD_IMAGE_SIZE: String = "CurrentUploadImageQualities" /** * Sets the flag indicating whether the upload is of a specific place. diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.kt index 9ee8fb483..5d721f408 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.kt @@ -146,34 +146,31 @@ class UploadPresenter @Inject internal constructor( override fun deletePictureAtIndex(index: Int) { val uploadableFiles = view.getUploadableFiles() - if (index == uploadableFiles!!.size - 1) { - // If the next fragment to be shown is not one of the MediaDetailsFragment - // lets hide the top card so that it doesn't appear on the other fragments - view.showHideTopCard(false) - } - view.setImageCancelled(true) - repository.deletePicture(uploadableFiles[index].getFilePath()) - if (uploadableFiles.size == 1) { - view.showMessage(R.string.upload_cancelled) - view.finish() - return - } - - presenter.updateImageQualitiesJSON(uploadableFiles.size, index) - view.onUploadMediaDeleted(index) - if (index != uploadableFiles.size && index != 0) { - // if the deleted image was not the last item to be uploaded, check quality of next - repository.getUploadItem(index)?.let { - presenter.checkImageQuality(it, index) + uploadableFiles?.let { + view.setImageCancelled(true) + repository.deletePicture(uploadableFiles[index].getFilePath()) + if (uploadableFiles.size == 1) { + view.showMessage(R.string.upload_cancelled) + view.finish() + return } - } - if (uploadableFiles.size < 2) { - view.showHideTopCard(false) - } + presenter.updateImageQualitiesJSON(uploadableFiles.size, index) + view.onUploadMediaDeleted(index) + if (index != uploadableFiles.size && index != 0) { + // if the deleted image was not the last item to be uploaded, check quality of next + repository.getUploadItem(index)?.let { + presenter.checkImageQuality(it, index) + } + } - //In case lets update the number of uploadable media - view.updateTopCardTitle() + if (uploadableFiles.size < 2) { + view.showHideTopCard(false) + } + + //In case lets update the number of uploadable media + view.updateTopCardTitle() + } } override fun onAttachView(view: UploadContract.View) { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt index af850a7e3..4a4c13ba7 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt @@ -532,7 +532,7 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra basicKvStore!!.putBoolean(keyForShowingAlertDialog, false) if (isInternetConnectionEstablished(requireActivity())) { val sizeOfUploads = basicKvStore!!.getInt( - UploadActivity.keyForCurrentUploadImagesSize + UploadActivity.KEY_FOR_CURRENT_UPLOAD_IMAGE_SIZE ) for (i in indexOfFragment until sizeOfUploads) { presenter.getImageQuality( diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt index 90c426091..77999cf2f 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt @@ -310,7 +310,7 @@ class UploadMediaPresenter @Inject constructor( private fun storeImageQuality( imageResult: Int, uploadItemIndex: Int, activity: Activity, uploadItem: UploadItem ) { - val store = BasicKvStore(activity, UploadActivity.storeNameForCurrentUploadImagesSize) + val store = BasicKvStore(activity, UploadActivity.STORE_NAME_FOR_CURRENT_UPLOAD_IMAGE_SIZE) val value = store.getString(UPLOAD_QUALITIES_KEY, null) try { val jsonObject = value.asJsonObject().apply { @@ -339,8 +339,10 @@ class UploadMediaPresenter @Inject constructor( */ override fun checkImageQuality(uploadItem: UploadItem, index: Int) { if ((uploadItem.imageQuality != IMAGE_OK) && (uploadItem.imageQuality != IMAGE_KEEP)) { - val value = basicKvStoreFactory?.let { it(UploadActivity.storeNameForCurrentUploadImagesSize) } + + val value = basicKvStoreFactory?.let { it(UploadActivity.STORE_NAME_FOR_CURRENT_UPLOAD_IMAGE_SIZE) } ?.getString(UPLOAD_QUALITIES_KEY, null) + try { val imageQuality = value.asJsonObject()["UploadItem$index"] as Int view.showProgress(false) @@ -363,8 +365,9 @@ class UploadMediaPresenter @Inject constructor( * @param index Index of the UploadItem which was deleted */ override fun updateImageQualitiesJSON(size: Int, index: Int) { - val value = basicKvStoreFactory?.let { it(UploadActivity.storeNameForCurrentUploadImagesSize) } + val value = basicKvStoreFactory?.let { it(UploadActivity.STORE_NAME_FOR_CURRENT_UPLOAD_IMAGE_SIZE) } ?.getString(UPLOAD_QUALITIES_KEY, null) + try { val jsonObject = value.asJsonObject().apply { for (i in index until (size - 1)) { @@ -372,7 +375,8 @@ class UploadMediaPresenter @Inject constructor( } remove("UploadItem" + (size - 1)) } - basicKvStoreFactory?.let { it(UploadActivity.storeNameForCurrentUploadImagesSize) } + + basicKvStoreFactory?.let { it(UploadActivity.STORE_NAME_FOR_CURRENT_UPLOAD_IMAGE_SIZE) } ?.putString(UPLOAD_QUALITIES_KEY, jsonObject.toString()) } catch (e: Exception) { Timber.e(e) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadActivityUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadActivityUnitTests.kt index 1173d09b0..97fe68862 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadActivityUnitTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadActivityUnitTests.kt @@ -262,15 +262,4 @@ class UploadActivityUnitTests { method.isAccessible = true method.invoke(activity) } - - @Test - @Throws(Exception::class) - fun testOnBackPressed() { - val method: Method = - UploadActivity::class.java.getDeclaredMethod( - "onBackPressed", - ) - method.isAccessible = true - method.invoke(activity) - } } From 56fa8ceb5a8768c29de0034d2690755210c48945 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 7 Apr 2025 14:01:50 +0200 Subject: [PATCH 17/37] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-ja/strings.xml | 4 ++-- app/src/main/res/values-ku/strings.xml | 2 +- app/src/main/res/values-ps/strings.xml | 9 ++++++++- app/src/main/res/values-pt-rBR/strings.xml | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 8ef85d363..5219cf91e 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -119,7 +119,7 @@ ログインできません - ネットワークのエラーです 失敗した回数が多すぎます。数分後にもう一度お試しください。 申し訳ありませんが、この利用者はコモンズでブロックされています。 - 2段階認証コードを入力してください。 + 二要素認証コードを入力してください。 ログイン失敗 アップロード このセットに名前をつけてください @@ -221,7 +221,7 @@ 情報なし ベータ版テスターになる Google Playのベータ版チャンネルにオプトインして、新機能やバグ修正プログラムに早期にアクセス - 2段階認証コード + 2FAコード ログアウトしてもよろしいですか? メディアイメージが失敗しました 下位カテゴリは見つかりませんでした diff --git a/app/src/main/res/values-ku/strings.xml b/app/src/main/res/values-ku/strings.xml index 2dc2fb995..54364b533 100644 --- a/app/src/main/res/values-ku/strings.xml +++ b/app/src/main/res/values-ku/strings.xml @@ -179,7 +179,7 @@ Spasî Hate Wergirtin Wêneyên Bijartî Wêneyên bi riya \"Cihên Nêz\" - Derece + Derece %d Wêneyên Barkirî Wêneyê din Belê, çima na diff --git a/app/src/main/res/values-ps/strings.xml b/app/src/main/res/values-ps/strings.xml index eda0d954e..31e12027e 100644 --- a/app/src/main/res/values-ps/strings.xml +++ b/app/src/main/res/values-ps/strings.xml @@ -47,7 +47,14 @@ %d upload %d پورته کول - دا انځور به د %1$s په منښتليک سمبال وي. + + دا انځور به د منښتليک %1$s لاندې وي + دا انځورونه به د %1$s منښتليک لاندې وي + + + %1$d راپورته کول + %1$d راپورته کېدنې + سپړنه ښکارېدنه ټولګړی diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 2949a707a..021a8ca3c 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -696,7 +696,7 @@ WLM Essa imagem será enviada ao concurso Wiki Loves Monuments Monumentos de exibição - Estamos no mês no Wiki Loves Monuments! + Chegou o mês do Wiki Loves Monuments! SABER MAIS Wiki Loves Monuments O Wiki Loves Monuments é um concurso internacional organizado pela Wikimedia sobre fotografias de monumentos From 2eed44146275dfaa6aa78a40e49c456a2df53987 Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Tue, 8 Apr 2025 07:59:28 -0400 Subject: [PATCH 18/37] Enable EmailAuth support. (#6277) --- .../fr/free/nrw/commons/auth/LoginActivity.kt | 15 ++++++++-- .../nrw/commons/auth/csrf/CsrfTokenClient.kt | 13 ++++++++- .../nrw/commons/auth/login/LoginCallback.kt | 7 +++++ .../nrw/commons/auth/login/LoginClient.kt | 28 +++++++++++++++---- .../nrw/commons/auth/login/LoginInterface.kt | 3 +- .../nrw/commons/auth/login/LoginResponse.kt | 15 ++++++---- .../nrw/commons/auth/login/LoginResult.kt | 7 +++++ app/src/main/res/layout/activity_login.xml | 3 +- app/src/main/res/values/strings.xml | 2 ++ 9 files changed, 76 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt index 75c4ac26d..840bc7ca3 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt @@ -65,6 +65,7 @@ class LoginActivity : AccountAuthenticatorActivity() { private val delegate: AppCompatDelegate by lazy { AppCompatDelegate.create(this, null) } + private var lastLoginResult: LoginResult? = null public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -271,6 +272,7 @@ class LoginActivity : AccountAuthenticatorActivity() { showLoggingProgressBar() loginClient.doLogin(username, password, + lastLoginResult, twoFactorCode, Locale.getDefault().language, object : LoginCallback { @@ -280,9 +282,17 @@ class LoginActivity : AccountAuthenticatorActivity() { onLoginSuccess(loginResult) } - override fun twoFactorPrompt(caught: Throwable, token: String?) = runOnUiThread { + override fun twoFactorPrompt(loginResult: LoginResult, caught: Throwable, token: String?) = runOnUiThread { Timber.d("Requesting 2FA prompt") progressDialog!!.dismiss() + lastLoginResult = loginResult + askUserForTwoFactorAuth() + } + + override fun emailAuthPrompt(loginResult: LoginResult, caught: Throwable, token: String?) { + Timber.d("Requesting email auth prompt") + progressDialog!!.dismiss() + lastLoginResult = loginResult askUserForTwoFactorAuth() } @@ -341,12 +351,13 @@ class LoginActivity : AccountAuthenticatorActivity() { progressDialog!!.dismiss() with(binding!!) { twoFactorContainer.visibility = View.VISIBLE + twoFactorContainer.hint = getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.email_auth_code else R.string._2fa_code) loginTwoFactor.visibility = View.VISIBLE loginTwoFactor.requestFocus() } val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY) - showMessageAndCancelDialog(R.string.login_failed_2fa_needed) + showMessageAndCancelDialog(getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.login_failed_email_auth_needed else R.string.login_failed_2fa_needed)) } @VisibleForTesting diff --git a/app/src/main/java/fr/free/nrw/commons/auth/csrf/CsrfTokenClient.kt b/app/src/main/java/fr/free/nrw/commons/auth/csrf/CsrfTokenClient.kt index f35e5f003..6353e54ac 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/csrf/CsrfTokenClient.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/csrf/CsrfTokenClient.kt @@ -32,7 +32,7 @@ class CsrfTokenClient( try { if (retry > 0) { // Log in explicitly - loginClient.loginBlocking(userName, password, "") + loginClient.loginBlocking(userName, password) } // Get CSRFToken response off the main thread. @@ -92,6 +92,8 @@ class CsrfTokenClient( override fun failure(caught: Throwable?) = retryWithLogin(cb) { caught } override fun twoFactorPrompt() = cb.twoFactorPrompt() + + override fun emailAuthPrompt() = cb.emailAuthPrompt() }, ) @@ -165,10 +167,17 @@ class CsrfTokenClient( } override fun twoFactorPrompt( + loginResult: LoginResult, caught: Throwable, token: String?, ) = callback.twoFactorPrompt() + override fun emailAuthPrompt( + loginResult: LoginResult, + caught: Throwable, + token: String?, + ) = callback.emailAuthPrompt() + // Should not happen here, but call the callback just in case. override fun passwordResetPrompt(token: String?) = callback.failure(LoginFailedException("Logged in with temporary password.")) @@ -190,6 +199,8 @@ class CsrfTokenClient( fun failure(caught: Throwable?) fun twoFactorPrompt() + + fun emailAuthPrompt() } companion object { diff --git a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginCallback.kt b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginCallback.kt index 8092f73ae..8aa3d17a0 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginCallback.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginCallback.kt @@ -4,6 +4,13 @@ interface LoginCallback { fun success(loginResult: LoginResult) fun twoFactorPrompt( + loginResult: LoginResult, + caught: Throwable, + token: String?, + ) + + fun emailAuthPrompt( + loginResult: LoginResult, caught: Throwable, token: String?, ) diff --git a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt index 2a799c847..a653b8b55 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt @@ -1,6 +1,7 @@ package fr.free.nrw.commons.auth.login import android.text.TextUtils +import fr.free.nrw.commons.auth.login.LoginResult.EmailAuthResult import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult import fr.free.nrw.commons.wikidata.WikidataConstants.WIKIPEDIA_URL @@ -51,6 +52,7 @@ class LoginClient( password, null, null, + null, response.body()!!.query()!!.loginToken(), userLanguage, cb, @@ -75,6 +77,7 @@ class LoginClient( password: String, retypedPassword: String?, twoFactorCode: String?, + emailAuthCode: String?, loginToken: String?, userLanguage: String, cb: LoginCallback, @@ -82,7 +85,7 @@ class LoginClient( this.userLanguage = userLanguage loginCall = - if (twoFactorCode.isNullOrEmpty() && retypedPassword.isNullOrEmpty()) { + if (twoFactorCode.isNullOrEmpty() && emailAuthCode.isNullOrEmpty() && retypedPassword.isNullOrEmpty()) { loginInterface.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL) } else { loginInterface.postLogIn( @@ -90,6 +93,7 @@ class LoginClient( password, retypedPassword, twoFactorCode, + emailAuthCode, loginToken, userLanguage, true, @@ -112,10 +116,18 @@ class LoginClient( when (loginResult) { is OAuthResult -> cb.twoFactorPrompt( + loginResult, LoginFailedException(loginResult.message), loginToken, ) + is EmailAuthResult -> + cb.emailAuthPrompt( + loginResult, + LoginFailedException(loginResult.message), + loginToken + ) + is ResetPasswordResult -> cb.passwordResetPrompt(loginToken) is LoginResult.Result -> @@ -147,6 +159,7 @@ class LoginClient( fun doLogin( username: String, password: String, + lastLoginResult: LoginResult?, twoFactorCode: String, userLanguage: String, loginCallback: LoginCallback, @@ -159,7 +172,10 @@ class LoginClient( ) = if (response.isSuccessful) { val loginToken = response.body()?.query()?.loginToken() loginToken?.let { - login(username, password, null, twoFactorCode, it, userLanguage, loginCallback) + login(username, password, null, + if (lastLoginResult is OAuthResult) twoFactorCode else null, + if (lastLoginResult is EmailAuthResult) twoFactorCode else null, + it, userLanguage, loginCallback) } ?: run { loginCallback.error(IOException("Failed to retrieve login token")) } @@ -181,7 +197,8 @@ class LoginClient( fun loginBlocking( userName: String, password: String, - twoFactorCode: String?, + twoFactorCode: String? = null, + emailAuthCode: String? = null ) { val tokenResponse = getLoginToken().execute() if (tokenResponse @@ -195,7 +212,7 @@ class LoginClient( val loginToken = tokenResponse.body()?.query()?.loginToken() val tempLoginCall = - if (twoFactorCode.isNullOrEmpty()) { + if (twoFactorCode.isNullOrEmpty() && emailAuthCode.isNullOrEmpty()) { loginInterface.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL) } else { loginInterface.postLogIn( @@ -203,6 +220,7 @@ class LoginClient( password, null, twoFactorCode, + emailAuthCode, loginToken, userLanguage, true, @@ -214,7 +232,7 @@ class LoginClient( val loginResult = loginResponse.toLoginResult(password) ?: throw IOException("Unexpected response when logging in.") if ("UI" == loginResult.status) { - if (loginResult is OAuthResult) { + if (loginResult is OAuthResult || loginResult is EmailAuthResult) { // TODO: Find a better way to boil up the warning about 2FA throw LoginFailedException(loginResult.message) } diff --git a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginInterface.kt b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginInterface.kt index 07e1cd45c..39cbf7c9f 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginInterface.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginInterface.kt @@ -35,7 +35,8 @@ interface LoginInterface { @Field("password") pass: String?, @Field("retype") retypedPass: String?, @Field("OATHToken") twoFactorCode: String?, - @Field("logintoken") token: String?, + @Field("token") emailAuthToken: String?, + @Field("logintoken") loginToken: String?, @Field("uselang") userLanguage: String?, @Field("logincontinue") loginContinue: Boolean, ): Call diff --git a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResponse.kt b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResponse.kt index a96778e38..0fb035eea 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResponse.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResponse.kt @@ -2,6 +2,7 @@ package fr.free.nrw.commons.auth.login import com.google.gson.annotations.SerializedName import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult +import fr.free.nrw.commons.auth.login.LoginResult.EmailAuthResult import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult import fr.free.nrw.commons.auth.login.LoginResult.Result import fr.free.nrw.commons.wikidata.mwapi.MwServiceError @@ -27,11 +28,13 @@ internal class ClientLogin { fun toLoginResult(password: String): LoginResult { var userMessage = message if ("UI" == status) { - if (requests != null) { - for (req in requests) { - if ("MediaWiki\\Extension\\OATHAuth\\Auth\\TOTPAuthenticationRequest" == req.id()) { + requests?.forEach { request -> + request.id()?.let { + if (it.endsWith("TOTPAuthenticationRequest")) { return OAuthResult(status, userName, password, message) - } else if ("MediaWiki\\Auth\\PasswordAuthenticationRequest" == req.id()) { + } else if (it.endsWith("EmailAuthAuthenticationRequest")) { + return EmailAuthResult(status, userName, password, message) + } else if (it.endsWith("PasswordAuthenticationRequest")) { return ResetPasswordResult(status, userName, password, message) } } @@ -49,7 +52,7 @@ internal class Request { private val required: String? = null private val provider: String? = null private val account: String? = null - private val fields: Map? = null + internal val fields: Map? = null fun id(): String? = id } @@ -57,5 +60,5 @@ internal class Request { internal class RequestField { private val type: String? = null private val label: String? = null - private val help: String? = null + internal val help: String? = null } diff --git a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResult.kt b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResult.kt index 6a7594ec0..99abaeeec 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResult.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/login/LoginResult.kt @@ -24,6 +24,13 @@ sealed class LoginResult( message: String?, ) : LoginResult(status, userName, password, message) + class EmailAuthResult( + status: String, + userName: String?, + password: String?, + message: String?, + ) : LoginResult(status, userName, password, message) + class ResetPasswordResult( status: String, userName: String?, diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 7cfd761a7..0da9f5d9f 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -146,6 +146,7 @@ android:layout_marginEnd="@dimen/standard_gap" android:layout_marginRight="@dimen/standard_gap" android:layout_marginBottom="@dimen/standard_gap" + android:hint="@string/_2fa_code" android:visibility="gone" app:passwordToggleEnabled="false" tools:visibility="visible"> @@ -154,9 +155,7 @@ android:id="@+id/login_two_factor" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/_2fa_code" android:imeOptions="flagNoExtractUi" - android:inputType="number" android:visibility="gone" tools:visibility="visible" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f9b16512f..805649dff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -111,6 +111,7 @@ Too many unsuccessful attempts. Please try again in a few minutes. Sorry, this user has been blocked on Commons You must provide your two factor authentication code. + A login verification code has been sent to your email address. Please provide the code to log in. Log-in failed Upload Name this set @@ -218,6 +219,7 @@ Opt-in to our beta channel on Google Play and get early access to new features and bug fixes https://play.google.com/apps/testing/fr.free.nrw.commons 2FA Code + Email verification code Do you really want to logout? Media Image Failed No subcategories found From 262efe4d8c1b0a5bdb6d36d68cb9be0581dd4fd1 Mon Sep 17 00:00:00 2001 From: Jason-Whitmore Date: Wed, 9 Apr 2025 22:19:31 -0700 Subject: [PATCH 19/37] ExploreMapFragment.java: fix removeMarker() to remove the correct marker (#6279) Before this change, the removeMarker() method would determine the correct overlay to remove by comparing an overlay's Place coordinates with the target BaseMarker's Place coordinates. If two different markers had the same Place coordinates, the incorrect marker would be removed, leading to more than one green label appearing on the screen. After this change, the removeMarker() method now compares an overlay's title with the BaseMarker's Place name. removeMarker() will work properly as long as all BaseMarker's Place names are unique. Also, null checks were added to removeMarker(). --- .../free/nrw/commons/explore/map/ExploreMapFragment.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java index f5657dd1b..60758ac20 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java @@ -775,7 +775,11 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment * @param nearbyBaseMarker The NearbyBaseMarker object representing the marker to be removed. */ private void removeMarker(BaseMarker nearbyBaseMarker) { - Place place = nearbyBaseMarker.getPlace(); + if (nearbyBaseMarker == null || nearbyBaseMarker.getPlace().getName() == null) { + return; + } + + String target = nearbyBaseMarker.getPlace().getName(); List overlays = binding.mapView.getOverlays(); ItemizedOverlayWithFocus item; @@ -784,8 +788,7 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment item = (ItemizedOverlayWithFocus) overlays.get(i); OverlayItem overlayItem = item.getItem(0); - if (place.location.getLatitude() == overlayItem.getPoint().getLatitude() - && place.location.getLongitude() == overlayItem.getPoint().getLongitude()) { + if (overlayItem.getTitle().equals(target)) { binding.mapView.getOverlays().remove(i); binding.mapView.invalidate(); break; From e3dd00bcfa908741816a240cd1c5acb3297262f6 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 10 Apr 2025 14:01:48 +0200 Subject: [PATCH 20/37] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-ar/strings.xml | 2 ++ app/src/main/res/values-da/strings.xml | 2 ++ app/src/main/res/values-lb/strings.xml | 2 ++ app/src/main/res/values-mk/strings.xml | 2 ++ app/src/main/res/values-pms/strings.xml | 2 ++ app/src/main/res/values-zh-rTW/strings.xml | 2 ++ 6 files changed, 12 insertions(+) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 577b031cb..5c9e7bec9 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -168,6 +168,7 @@ الكثير من المحاولات غير الناجحة. الرجاء المحاولة مرة أخرى في بضع دقائق. عذراً، لقد تم منع هذا المستخدم على كومنز يجب توفير رمز التحقق المزدوج. + تم إرسال رمز التحقق إلى بريدك الإلكتروني. يُرجى إدخال الرمز لتسجيل الدخول. فشل تسجيل الدخول ارفع اسم هذه المجموعة @@ -273,6 +274,7 @@ انضم لمختبري اصدارات Beta (بيتا) يمكنك الاشتراك في القناة التجريبية على جوجل بلاي والحصول على إمكانية الوصول المبكر إلى الميزات الجديدة وإصلاحات الأخطاء رمز التحقق المزدوج 2FA + رمز التحقق من البريد الإلكتروني أترغب فعلا في الخروج؟ صورة الوسائط فشلت لم يتم العثور على تصنيفات فرعية. diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 232f56c49..2e535b879 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -119,6 +119,7 @@ For mange mislykkede forsøg. Prøv igen om et par minutter. Beklager, denne bruger er blevet blokeret på Commons Du skal angive din tofaktorgodkendelseskode. + En login-bekræftelseskode er blevet sendt til din e-mailadresse. Angiv koden for at logge ind. Login mislykkedes Upload Navngiv dette sæt @@ -224,6 +225,7 @@ Bliv betatester Registrer dig på vores betakanal på Google Play og få tidlig adgang til nye funktioner og fejlrettelser 2FA-kode + E-mail-bekræftelseskode Ønsker du at logge ud? Mediebillede mislykkedes Ingen underkategorier fundet diff --git a/app/src/main/res/values-lb/strings.xml b/app/src/main/res/values-lb/strings.xml index 65574cdb9..afca8fcd4 100644 --- a/app/src/main/res/values-lb/strings.xml +++ b/app/src/main/res/values-lb/strings.xml @@ -99,6 +99,7 @@ Ze dacks ouni Succès probéiert. Probéiert wgl. an e puer Minutten nach eng Kéier. Pardon, dëse Benotzer ass op Commons gespaart Dir musst de Code vun Ärer Zwee-Facteur-Authentifizéierung uginn. + Dir hutt ee Login-Verifikatiounscode per E-Mail geschéckt kritt. Gitt wgl. de Code an, fir Iech anzeloggen. Aloggen huet net funktionéiert Eroplueden Gitt dëser Biller een Numm @@ -192,6 +193,7 @@ Beta-Tester ginn Schreift Iech op GooglePlay a fir eise Beta-Kanal a kritt fréi Zougang zu neie Funktiounen a Verbesserunge vu Feeler 2FA-Code + E-Mail-Verifikatiounscode Wëllt dir Iech wierklech ausloggen? Keng Ënnerkategorie fonnt Bierg Zao diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 94e8f638b..2a48ddd1e 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -113,6 +113,7 @@ Направени се премногу неуспешни обиди. Обидете се пак за некоја минута. За жал, корисникот е блокиран на Ризницата Мора да го укажете вашиот код за двочинителска заверка. + На вашата е-поштенска адреса ви испративме потврден код за најава. Внесете го кодот за да се најавите. Најавата не успеа Подигни Дајте му име на овој комплет @@ -218,6 +219,7 @@ Станете бета-испробувач Пријавете се на нашиот бета-канал на Google Play и добивајте ран пристап до нови можности и исправки на грешки 2ЧЗ-код + Испрати го потврдниот код на е-пошта Дали навистина сакате да се одјавите? Сликата не успеа Не пронајдов поткатегории diff --git a/app/src/main/res/values-pms/strings.xml b/app/src/main/res/values-pms/strings.xml index 76a0be425..d6dd2b3ff 100644 --- a/app/src/main/res/values-pms/strings.xml +++ b/app/src/main/res/values-pms/strings.xml @@ -111,6 +111,7 @@ Tròpi tentativ falì. Për piasì, ch\'a preuva torna da-sì chèiche minute. An dëspias, s\'utent-sì a l\'é stàit blocà ansima a Commons A dev fornì sò còdes d\'autentificassion a doi fator. + Un còdes ëd verìfica ëd conession a l\'é stàit mandà a soa adrëssa ëd pòsta eletrònica. Për piasì, ch\'a anserissa col còdes për intré ant ël sistema. Falì a rintré ant ël sistema Carié Deje un nòm a s\'ansem @@ -216,6 +217,7 @@ Dventé në sperimentador Beta Anscriv-se a nòstr canal beta su Google Play a oten-e n\'acess antissipà a le neuve fonsionalità e coression ëd givo Còdes 2FA + Mandé un còdes ëd verifica Veul-lo për da bon seurte dal sistema? Faliment ëd la plancia dël mojen Gnun-e sot-categorìe trovà diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index ca11a4ec9..b2fd85c23 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -136,6 +136,7 @@ 失敗次數過多。請於幾分鐘後重試。 很抱歉,該使用者已被維基共享資源封禁 必須提供您的雙重驗證代碼。 + 登入驗證碼已發送到您的電子郵件地址。請提供驗證碼以登入。 登入失敗 上傳 給這個集合命名 @@ -241,6 +242,7 @@ 成為 Beta 測試員 選擇加入我們在 Google Play 上的測試版頻道並儘早訪問新功能和錯誤修復 2FA 代碼 + 電子郵件驗證碼 您確定要登出嗎? 媒體圖片失敗 找不到子分類 From 2c41176a6e37a4568f8f92bfb5b45b5b248589bd Mon Sep 17 00:00:00 2001 From: Sonal Yadav Date: Fri, 11 Apr 2025 14:30:47 +0530 Subject: [PATCH 21/37] =?UTF-8?q?Mark=20=E2=9D=8C=20for=20closed=20locatio?= =?UTF-8?q?ns=20(P3999)=20in=20Nearby=20(#6273)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Exclude closed locations (P3999) from Nearby query * feat: Show ❌ for P3999 items (official closure) * revert changes * Add P3999 (date of closure) support for non-existent places * Typo fixing * fix-typo * . --------- Co-authored-by: Nicolas Raoul --- app/src/main/java/fr/free/nrw/commons/nearby/Place.java | 4 +++- .../java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt | 4 ++++ app/src/main/resources/queries/query_for_item.rq | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java index 3b3b798eb..402399a24 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java @@ -153,7 +153,9 @@ public class Place implements Parcelable { .build(), item.getPic().getValue(), // Checking if the place exists or not - (item.getDestroyed().getValue() == "") && (item.getEndTime().getValue() == ""), entityId); + (item.getDestroyed().getValue() == "") && (item.getEndTime().getValue() == "") + && (item.getDateOfOfficialClosure().getValue() == ""), + entityId); } /** diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt b/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt index f28cc833e..a6227a145 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt +++ b/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt @@ -18,6 +18,7 @@ class NearbyResultItem( @field:SerializedName("description") private val description: ResultTuple?, @field:SerializedName("endTime") private val endTime: ResultTuple?, @field:SerializedName("monument") private val monument: ResultTuple?, + @field:SerializedName("dateOfOfficialClosure") private val dateOfOfficialClosure: ResultTuple?, ) { fun getItem(): ResultTuple = item ?: ResultTuple() @@ -41,6 +42,8 @@ class NearbyResultItem( fun getDestroyed(): ResultTuple = destroyed ?: ResultTuple() + fun getDateOfOfficialClosure(): ResultTuple = dateOfOfficialClosure ?: ResultTuple() + fun getDescription(): ResultTuple = description ?: ResultTuple() fun getEndTime(): ResultTuple = endTime ?: ResultTuple() @@ -48,4 +51,5 @@ class NearbyResultItem( fun getAddress(): String = address?.value ?: "" fun getMonument(): ResultTuple? = monument + } diff --git a/app/src/main/resources/queries/query_for_item.rq b/app/src/main/resources/queries/query_for_item.rq index 4a946ac96..edcb29ddf 100644 --- a/app/src/main/resources/queries/query_for_item.rq +++ b/app/src/main/resources/queries/query_for_item.rq @@ -10,6 +10,7 @@ SELECT (SAMPLE(?wikipediaArticle) AS ?wikipediaArticle) (SAMPLE(?commonsArticle) AS ?commonsArticle) (SAMPLE(?commonsCategory) AS ?commonsCategory) + (SAMPLE(?dateOfOfficialClosure) AS ?dateOfOfficialClosure) WHERE { SERVICE { values ?item { @@ -45,6 +46,7 @@ WHERE { # Get existence OPTIONAL {?item wdt:P576 ?destroyed} OPTIONAL {?item wdt:P582 ?endTime} + OPTIONAL {?item wdt:P3999 ?dateOfOfficialClosure} # Get Commons category OPTIONAL {?item wdt:P373 ?commonsCategory} From 6aeb3c07ccbfa9be374e1338a3b603e272788b6e Mon Sep 17 00:00:00 2001 From: samimshoaib01 Date: Sat, 12 Apr 2025 21:05:42 +0530 Subject: [PATCH 22/37] ui: make recenter FAB theme-aware using Material attributes (#6281) --- app/src/main/res/layout/fragment_explore_map.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/fragment_explore_map.xml b/app/src/main/res/layout/fragment_explore_map.xml index e2d628dab..ad0b40b82 100644 --- a/app/src/main/res/layout/fragment_explore_map.xml +++ b/app/src/main/res/layout/fragment_explore_map.xml @@ -26,10 +26,11 @@ android:clickable="true" android:focusable="true" android:visibility="visible" - app:backgroundTint="@color/main_background_light" + app:backgroundTint="?attr/colorSurface" app:elevation="@dimen/dimen_6" app:fabSize="normal" app:srcCompat="@drawable/ic_my_location_black_24dp" + app:tint="?attr/colorOnSurface" app:useCompatPadding="true" /> Date: Sun, 13 Apr 2025 06:39:14 +0100 Subject: [PATCH 23/37] BookmarkLocationsFragment.kt:fix android studio warnings for this file, : Eliminated three unused imports, and changed calls to object: FilePicker.HandleActivityResult to use lamda (#6283) Co-authored-by: bethel-m --- .../locations/BookmarkLocationsFragment.kt | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.kt index 4ab21462c..f10e02ebc 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.kt @@ -1,7 +1,6 @@ package fr.free.nrw.commons.bookmarks.locations import android.Manifest.permission -import android.annotation.SuppressLint import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -9,15 +8,12 @@ import android.view.ViewGroup import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import dagger.android.support.DaggerFragment import fr.free.nrw.commons.R import fr.free.nrw.commons.contributions.ContributionController import fr.free.nrw.commons.databinding.FragmentBookmarksLocationsBinding -import fr.free.nrw.commons.filepicker.FilePicker import fr.free.nrw.commons.nearby.Place import fr.free.nrw.commons.nearby.fragments.CommonPlaceClickActions import fr.free.nrw.commons.nearby.fragments.PlaceAdapter @@ -41,33 +37,27 @@ class BookmarkLocationsFragment : DaggerFragment() { private val cameraPickLauncherForResult = registerForActivityResult(StartActivityForResult()) { result -> contributionController.handleActivityResultWithCallback( - requireActivity(), - object: FilePicker.HandleActivityResult { - override fun onHandleActivityResult(callbacks: FilePicker.Callbacks) { - contributionController.onPictureReturnedFromCamera( - result, - requireActivity(), - callbacks - ) - } - } - ) + requireActivity() + ) { callbacks -> + contributionController.onPictureReturnedFromCamera( + result, + requireActivity(), + callbacks + ) + } } private val galleryPickLauncherForResult = registerForActivityResult(StartActivityForResult()) { result -> contributionController.handleActivityResultWithCallback( - requireActivity(), - object: FilePicker.HandleActivityResult { - override fun onHandleActivityResult(callbacks: FilePicker.Callbacks) { - contributionController.onPictureReturnedFromGallery( - result, - requireActivity(), - callbacks - ) - } - } - ) + requireActivity() + ) { callbacks -> + contributionController.onPictureReturnedFromGallery( + result, + requireActivity(), + callbacks + ) + } } companion object { From 5b5aeead88e8437f8cfa23ce33bcfe8aaaf569d0 Mon Sep 17 00:00:00 2001 From: Ritika Pahwa <83745993+RitikaPahwa4444@users.noreply.github.com> Date: Sun, 13 Apr 2025 13:09:19 +0530 Subject: [PATCH 24/37] Bump up version code to 1050 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 41b3d85de..133d39b6e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -212,8 +212,8 @@ android { defaultConfig { //applicationId 'fr.free.nrw.commons' - versionCode 1049 - versionName '5.2.0' + versionCode 1050 + versionName '5.3.0' setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) minSdkVersion 21 From 69b35441075ee0273b8f9044a4ad5c1c4f02cf5f Mon Sep 17 00:00:00 2001 From: Khushbu Khemchandani Date: Mon, 14 Apr 2025 07:24:29 +0530 Subject: [PATCH 25/37] Exclude past locations (P585) from Nearby query (#6284) --- app/src/main/java/fr/free/nrw/commons/nearby/Place.java | 3 ++- .../java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt | 3 +++ app/src/main/resources/queries/query_for_item.rq | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java index 402399a24..cff2ed4de 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java @@ -154,7 +154,8 @@ public class Place implements Parcelable { item.getPic().getValue(), // Checking if the place exists or not (item.getDestroyed().getValue() == "") && (item.getEndTime().getValue() == "") - && (item.getDateOfOfficialClosure().getValue() == ""), + && (item.getDateOfOfficialClosure().getValue() == "") + && (item.getPointInTime().getValue()==""), entityId); } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt b/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt index a6227a145..c39d8901d 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt +++ b/app/src/main/java/fr/free/nrw/commons/nearby/model/NearbyResultItem.kt @@ -19,6 +19,7 @@ class NearbyResultItem( @field:SerializedName("endTime") private val endTime: ResultTuple?, @field:SerializedName("monument") private val monument: ResultTuple?, @field:SerializedName("dateOfOfficialClosure") private val dateOfOfficialClosure: ResultTuple?, + @field:SerializedName("pointInTime") private val pointInTime: ResultTuple?, ) { fun getItem(): ResultTuple = item ?: ResultTuple() @@ -52,4 +53,6 @@ class NearbyResultItem( fun getMonument(): ResultTuple? = monument + fun getPointInTime(): ResultTuple = pointInTime ?: ResultTuple() + } diff --git a/app/src/main/resources/queries/query_for_item.rq b/app/src/main/resources/queries/query_for_item.rq index edcb29ddf..2957b9b5b 100644 --- a/app/src/main/resources/queries/query_for_item.rq +++ b/app/src/main/resources/queries/query_for_item.rq @@ -11,6 +11,7 @@ SELECT (SAMPLE(?commonsArticle) AS ?commonsArticle) (SAMPLE(?commonsCategory) AS ?commonsCategory) (SAMPLE(?dateOfOfficialClosure) AS ?dateOfOfficialClosure) + (SAMPLE(?pointInTime) AS ?pointInTime) WHERE { SERVICE { values ?item { @@ -47,6 +48,7 @@ WHERE { OPTIONAL {?item wdt:P576 ?destroyed} OPTIONAL {?item wdt:P582 ?endTime} OPTIONAL {?item wdt:P3999 ?dateOfOfficialClosure} + OPTIONAL {?item wdt:P585 ?pointInTime} # Get Commons category OPTIONAL {?item wdt:P373 ?commonsCategory} From efdc9c5548a707969780d1a54f9d7ad8fd70ac6a Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 14 Apr 2025 14:01:44 +0200 Subject: [PATCH 26/37] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-iw/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 7203f3e1f..4697c9294 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -144,6 +144,7 @@ יותר מדי ניסיונות כניסה כושלים. נא לנסות שוב בעוד כמה דקות. סליחה, החשבון הזה חסום בוויקישיתוף יש לספק את קוד האימות הדו־שלבי שלך. + נשלח קוד אימות כניסה לכתובת הדוא״ל שלך. נא לספק את הקוד כדי להיכנס. הכניסה נכשלה העלאה שם האוסף @@ -249,6 +250,7 @@ להפוך לבודק בטא להירשם לערוץ הבטא שלנו בחנות גוגל Play ולקבל גישה מוקדמת לאפשרויות חדשות ותיקוני באגים קוד אימות דו־שלבי + קוד אימות בדוא״ל האם באמת לצאת מהחשבון? תמונת המדיה נכשלה לא נמצאו תת־קטגוריות From 9289dcc42c688d186fada2eb733dae49335c203c Mon Sep 17 00:00:00 2001 From: Khushbu Khemchandani Date: Mon, 14 Apr 2025 19:28:28 +0530 Subject: [PATCH 27/37] UI enhancement Issue(#6285) (#6287) * Exclude past locations (P585) from Nearby query * "Send" button text should be white --- app/src/main/res/layout/activity_wikidata_feedback.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/activity_wikidata_feedback.xml b/app/src/main/res/layout/activity_wikidata_feedback.xml index 5f6647efe..52034b3ac 100644 --- a/app/src/main/res/layout/activity_wikidata_feedback.xml +++ b/app/src/main/res/layout/activity_wikidata_feedback.xml @@ -102,6 +102,7 @@ android:layout_marginEnd="8dp" android:text="SEND" android:visibility="visible" + android:textColor="@color/white" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@+id/textHeader" app:layout_constraintHorizontal_bias="1.0" From 1a13cb3383cb3944e423672c9e5e94fae7643764 Mon Sep 17 00:00:00 2001 From: Ritika Pahwa <83745993+RitikaPahwa4444@users.noreply.github.com> Date: Tue, 15 Apr 2025 02:43:33 +0530 Subject: [PATCH 28/37] Add v5.3.0 to CHANGELOG.md --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0da417cfa..df55b1124 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Wikimedia Commons for Android +## v5.3.0 + +### What's changed +* Enable EmailAuth support +* Explore map images no longer show "Unknown" +* Fix crash when removing last two images of multiupload +* Mark ❌ for closed locations (P3999) in Nearby +* Fix two pin labels staying visible at the same time in Explore map +* Refactoring and minor UI improvements + ## v5.2.0 v5.2.0 boasts several new functionalities like: From 78d29bcf2039e5afdd166600a11db365b4c1cfb6 Mon Sep 17 00:00:00 2001 From: Sonal Yadav Date: Tue, 15 Apr 2025 09:23:26 +0530 Subject: [PATCH 29/37] FIX : Custom picker detect images that is already in commons (#6288) * Fix: Exclude specific text from being posted in WikidataFeedback * Add detection logic for images already on Commons in custom picker --- .../customselector/ui/adapter/ImageAdapter.kt | 54 ++++++++----------- .../ui/selector/CustomSelectorActivity.kt | 21 ++++---- .../nrw/commons/nearby/WikidataFeedback.kt | 2 +- 3 files changed, 36 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt index ff623d496..abb2dc84a 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.MainScope import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.util.TreeMap import kotlin.collections.ArrayList @@ -342,45 +343,36 @@ class ImageAdapter( numberOfSelectedImagesMarkedAsNotForUpload-- } notifyItemChanged(position, ImageUnselected()) - - // Getting index from all images index when switch is on - val indexes = - if (showAlreadyActionedImages) { - ImageHelper.getIndexList(selectedImages, images) - - // Getting index from actionable images when switch is off - } else { - ImageHelper.getIndexList(selectedImages, ArrayList(actionableImagesMap.values)) - } - for (index in indexes) { - notifyItemChanged(index, ImageSelectedOrUpdated()) - } } else { + val image = images[position] + scope.launch(ioDispatcher) { + val imageSHA1 = imageLoader.getSHA1(image, defaultDispatcher) + withContext(Dispatchers.Main) { if (holder.isItemUploaded()) { Toast.makeText(context, R.string.custom_selector_already_uploaded_image_text, Toast.LENGTH_SHORT).show() - } else { - if (holder.isItemNotForUpload()) { - numberOfSelectedImagesMarkedAsNotForUpload++ - } - - // Getting index from all images index when switch is on - val indexes: ArrayList = - if (showAlreadyActionedImages) { - selectedImages.add(images[position]) - ImageHelper.getIndexList(selectedImages, images) - - // Getting index from actionable images when switch is off - } else { - selectedImages.add(ArrayList(actionableImagesMap.values)[position]) - ImageHelper.getIndexList(selectedImages, ArrayList(actionableImagesMap.values)) + return@withContext } - for (index in indexes) { - notifyItemChanged(index, ImageSelectedOrUpdated()) + if (imageSHA1.isNotEmpty() && imageLoader.getFromUploaded(imageSHA1) != null) { + holder.itemUploaded() + Toast.makeText(context, R.string.custom_selector_already_uploaded_image_text, Toast.LENGTH_SHORT).show() + return@withContext + } + + if (!holder.isItemUploaded() && imageSHA1.isNotEmpty() && imageLoader.getFromUploaded(imageSHA1) != null) { + Toast.makeText(context, R.string.custom_selector_already_uploaded_image_text, Toast.LENGTH_SHORT).show() + } + + if (holder.isItemNotForUpload()) { + numberOfSelectedImagesMarkedAsNotForUpload++ + } + selectedImages.add(image) + notifyItemChanged(position, ImageSelectedOrUpdated()) + + imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload) } } } - imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload) } /** diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt index 4e2d58bab..6b78dfd41 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt @@ -638,17 +638,20 @@ class CustomSelectorActivity : finishPickImages(arrayListOf()) return } - var i = 0 - while (i < selectedImages.size) { - val path = selectedImages[i].path - val file = File(path) - if (!file.exists()) { - selectedImages.removeAt(i) - i-- + scope.launch(ioDispatcher) { + val uniqueImages = selectedImages.distinctBy { image -> + CustomSelectorUtils.getImageSHA1( + image.uri, + ioDispatcher, + fileUtilsWrapper, + contentResolver + ) + } + + withContext(Dispatchers.Main) { + finishPickImages(ArrayList(uniqueImages)) } - i++ } - finishPickImages(selectedImages) } /** diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt b/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt index e5196bee8..a1bad1f26 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt +++ b/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt @@ -103,4 +103,4 @@ class WikidataFeedback : BaseActivity() { onBackPressed() return true } -} +} \ No newline at end of file From ed42d85f6766f5707c54bb0bf63b6cfbe2f3b14c Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 17 Apr 2025 14:01:53 +0200 Subject: [PATCH 30/37] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-ru/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d25d949ae..b984183de 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -170,6 +170,7 @@ Слишком много неудачных попыток. Пожалуйста, попробуйте ещё раз через несколько минут. Извините, но участник с таким именем был заблокирован на Викискладе Вы должны ввести код двухфакторной аутентификации. + Код подтверждения был отправлен на адрес вашей электронной почты. Пожалуйста, введите его для входа. Ошибка входа в систему Загрузка Введите название для этой группы файлов @@ -275,6 +276,7 @@ Стать бета-тестером Подпишитесь на наш канал бета-версии на Google Play и получите ранний доступ к новым функциям и исправлениям ошибок Код 2ФА + Код подтверждения электронной почты Вы действительно хотите выйти? Ошибка медиафайла Подкатегории не найдены. From 7479d96675c3270f33d55e227c849691f6354ef1 Mon Sep 17 00:00:00 2001 From: Khushbu Khemchandani Date: Mon, 21 Apr 2025 11:19:15 +0530 Subject: [PATCH 31/37] Code Enhancement (Explore Map) (#6293) * Exclude past locations (P585) from Nearby query * "Send" button text should be white * Custom picker: logic * Revert back changes * Enhancement :- Explore Map information * Enhancement :- Explore Map information * Style --------- Co-authored-by: Nicolas Raoul --- .../customselector/ui/adapter/ImageAdapter.kt | 8 ++++---- .../commons/explore/map/ExploreMapFragment.java | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt index abb2dc84a..20a2fe70a 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt @@ -348,12 +348,12 @@ class ImageAdapter( scope.launch(ioDispatcher) { val imageSHA1 = imageLoader.getSHA1(image, defaultDispatcher) withContext(Dispatchers.Main) { - if (holder.isItemUploaded()) { - Toast.makeText(context, R.string.custom_selector_already_uploaded_image_text, Toast.LENGTH_SHORT).show() + if (holder.isItemUploaded()) { + Toast.makeText(context, R.string.custom_selector_already_uploaded_image_text, Toast.LENGTH_SHORT).show() return@withContext } - if (imageSHA1.isNotEmpty() && imageLoader.getFromUploaded(imageSHA1) != null) { + if (imageSHA1.isNotEmpty() && imageLoader.getFromUploaded(imageSHA1) != null) { holder.itemUploaded() Toast.makeText(context, R.string.custom_selector_already_uploaded_image_text, Toast.LENGTH_SHORT).show() return@withContext @@ -369,7 +369,7 @@ class ImageAdapter( selectedImages.add(image) notifyItemChanged(position, ImageSelectedOrUpdated()) - imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload) + imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload) } } } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java index 60758ac20..a222a98ac 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java @@ -717,8 +717,20 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment authorUser = Html.fromHtml(authorUser, Html.FROM_HTML_MODE_LEGACY).toString(); } - OverlayItem item = new OverlayItem(nearbyBaseMarker.getPlace().name, - authorUser, point); + String title = nearbyBaseMarker.getPlace().name; + // Remove "File:" if present at start + if (title.startsWith("File:")) { + title = title.substring(5); + } + // Remove extensions like .jpg, .jpeg, .png, .svg (case insensitive) + title = title.replaceAll("(?i)\\.(jpg|jpeg|png|svg)$", ""); + title = title.replace("_", " "); + //Truncate if too long because it doesn't fit the screen + if (title.length() > 43) { + title = title.substring(0, 40) + "…"; + } + + OverlayItem item = new OverlayItem(title, authorUser, point); item.setMarker(d); items.add(item); ItemizedOverlayWithFocus overlay = new ItemizedOverlayWithFocus(items, From 30762971db0b8b54674f1d458605449fb146becc Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 21 Apr 2025 14:01:47 +0200 Subject: [PATCH 32/37] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-bn/strings.xml | 8 ++++++ app/src/main/res/values-de/strings.xml | 14 +++++++--- app/src/main/res/values-fr/strings.xml | 3 +++ app/src/main/res/values-io/strings.xml | 6 +++++ app/src/main/res/values-ko/strings.xml | 5 ++-- app/src/main/res/values-ps/strings.xml | 36 +++++++++++++++----------- app/src/main/res/values-zh/strings.xml | 3 +++ 7 files changed, 55 insertions(+), 20 deletions(-) diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index a7a3da9a7..9b2fde313 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -2,6 +2,7 @@