From cd09c81d2e0536e581e3c5600dcfc7804dcdb174 Mon Sep 17 00:00:00 2001 From: jagadeesh-18-bot Date: Sun, 7 Sep 2025 07:08:24 +0530 Subject: [PATCH 1/3] Fix #6409: Add listener call in ImageAdapter to update UI and upload button on deselection --- .../customselector/ui/adapter/ImageAdapter.kt | 30 ++----------------- 1 file changed, 3 insertions(+), 27 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 62a440ff4c..375e3c9a22 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 @@ -296,39 +296,14 @@ class ImageAdapter( private fun onThumbnailClicked( position: Int, holder: ImageViewHolder, - ) { - val sharedPreferences: SharedPreferences = - context.getSharedPreferences(CUSTOM_SELECTOR_PREFERENCE_KEY, 0) - val switchState = - sharedPreferences.getBoolean(SHOW_ALREADY_ACTIONED_IMAGES_PREFERENCE_KEY, true) - - // While switch is turned off, lets user click on image only if the position is - // added inside map - if (!switchState) { - if (actionableImagesMap.size > position) { - selectOrRemoveImage(holder, position) - } - } else { - selectOrRemoveImage(holder, position) - } - } - - /** - * Handle click event on an image, update counter on images. - */ - private fun selectOrRemoveImage( - holder: ImageViewHolder, - position: Int, ) { val sharedPreferences: SharedPreferences = context.getSharedPreferences(CUSTOM_SELECTOR_PREFERENCE_KEY, 0) val showAlreadyActionedImages = sharedPreferences.getBoolean(SHOW_ALREADY_ACTIONED_IMAGES_PREFERENCE_KEY, true) - // Getting clicked index from all images index when show_already_actioned_images - // switch is on + // Single Selection mode if (singleSelection) { - // If single selection mode, clear previous selection and select only the new one if (selectedImages.isNotEmpty() && (selectedImages[0] != images[position])) { val prevIndex = images.indexOf(selectedImages[0]) selectedImages.clear() @@ -348,6 +323,7 @@ class ImageAdapter( numberOfSelectedImagesMarkedAsNotForUpload-- } notifyItemChanged(position, ImageUnselected()) + imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload) // Added this line to notify the activity } else { val image = images[position] scope.launch(ioDispatcher) { @@ -632,4 +608,4 @@ class ImageAdapter( fun setSingleSelection(single: Boolean) { singleSelection = single } -} +} \ No newline at end of file From 633210dbab1ddb577013888f08a2ce3472f5b290 Mon Sep 17 00:00:00 2001 From: jagadeesh-18-bot Date: Sat, 13 Sep 2025 17:01:23 +0530 Subject: [PATCH 2/3] Fix image deselection issue in ImageAdapter to update UI correctly (#6409) --- .../customselector/ui/adapter/ImageAdapter.kt | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 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 375e3c9a22..7d4b085c6f 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 @@ -165,13 +165,8 @@ class ImageAdapter( val selectedIndex: Int = if (showAlreadyActionedImages) { ImageHelper.getIndex(selectedImages, image) - - // Getting selected index when switch is off } else if (actionableImagesMap.size > position) { - ImageHelper - .getIndex(selectedImages, ArrayList(actionableImagesMap.values)[position]) - - // For any other case return -1 + ImageHelper.getIndex(selectedImages, ArrayList(actionableImagesMap.values)[position]) } else { -1 } @@ -204,8 +199,6 @@ class ImageAdapter( uploadingContributionList ) _isLoadingImages.value = false - // If the position is already visited, that means the image is already present - // inside map, so it will fetch the image from the map and load in the holder } else { val actionableImages: List = ArrayList(actionableImagesMap.values) if (actionableImages.size > position) { @@ -217,9 +210,6 @@ class ImageAdapter( .into(holder.image) } } - - // If switch is turned off, it just fetches the image from all images without any - // further operations } else { Glide .with(holder.image) @@ -263,10 +253,6 @@ class ImageAdapter( // finding next actionable image will start from this position if (next > -1) { nextImagePosition = next + 1 - - // If map doesn't contains the next actionable image, that means it's a - // new actionable image, it will put it to the map as actionable images - // and it will load the new image in the view holder if (!actionableImagesMap.containsKey(next)) { actionableImagesMap[next] = allImages[next] alreadyAddedPositions.add(imagePositionAsPerIncreasingOrder) @@ -280,9 +266,6 @@ class ImageAdapter( notifyItemInserted(position) notifyItemRangeChanged(position, itemCount + 1) } - - // If next actionable image is not found, that means searching is - // complete till end, and it will stop searching. } else { reachedEndOfFolder = true notifyItemRemoved(position) @@ -296,14 +279,39 @@ class ImageAdapter( private fun onThumbnailClicked( position: Int, holder: ImageViewHolder, + ) { + val sharedPreferences: SharedPreferences = + context.getSharedPreferences(CUSTOM_SELECTOR_PREFERENCE_KEY, 0) + val switchState = + sharedPreferences.getBoolean(SHOW_ALREADY_ACTIONED_IMAGES_PREFERENCE_KEY, true) + + // While switch is turned off, lets user click on image only if the position is + // added inside map + if (!switchState) { + if (actionableImagesMap.size > position) { + selectOrRemoveImage(holder, position) + } + } else { + selectOrRemoveImage(holder, position) + } + } + + /** + * Handle click event on an image, update counter on images. + */ + private fun selectOrRemoveImage( + holder: ImageViewHolder, + position: Int, ) { val sharedPreferences: SharedPreferences = context.getSharedPreferences(CUSTOM_SELECTOR_PREFERENCE_KEY, 0) val showAlreadyActionedImages = sharedPreferences.getBoolean(SHOW_ALREADY_ACTIONED_IMAGES_PREFERENCE_KEY, true) - // Single Selection mode + // Getting clicked index from all images index when show_already_actioned_images + // switch is on if (singleSelection) { + // If single selection mode, clear previous selection and select only the new one if (selectedImages.isNotEmpty() && (selectedImages[0] != images[position])) { val prevIndex = images.indexOf(selectedImages[0]) selectedImages.clear() @@ -323,7 +331,8 @@ class ImageAdapter( numberOfSelectedImagesMarkedAsNotForUpload-- } notifyItemChanged(position, ImageUnselected()) - imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload) // Added this line to notify the activity + // Notify listener of deselection to update UI + imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload) } else { val image = images[position] scope.launch(ioDispatcher) { @@ -349,7 +358,6 @@ class ImageAdapter( } selectedImages.add(image) notifyItemChanged(position, ImageSelectedOrUpdated()) - imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload) } } @@ -431,7 +439,6 @@ class ImageAdapter( } else { val iterator = actionableImagesMap.entries.iterator() var index = 0 - while (iterator.hasNext()) { val entry = iterator.next() if (entry.value == image) { @@ -459,17 +466,10 @@ class ImageAdapter( val showAlreadyActionedImages = sharedPreferences.getBoolean(SHOW_ALREADY_ACTIONED_IMAGES_PREFERENCE_KEY, true) - // While switch is on initializes the holder with all images size return if (showAlreadyActionedImages) { allImages.size - - // While switch is off and searching for next actionable has ended, initializes the holder - // with size of all actionable images } else if (actionableImagesMap.size == allImages.size || reachedEndOfFolder) { actionableImagesMap.size - - // While switch is off, initializes the holder with and extra view holder so that finding - // and addition of the next actionable image in the adapter can be continued } else { actionableImagesMap.size + 1 } From a1d37e1d69a7eaf3dce1cc50112d200266dbaddc Mon Sep 17 00:00:00 2001 From: jagadeesh-18-bot Date: Sun, 14 Sep 2025 00:54:39 +0530 Subject: [PATCH 3/3] Prevent duplicate image selections on multiple taps in ImageAdapter when showAlreadyActionedImages is off (#6409) --- .../customselector/ui/adapter/ImageAdapter.kt | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) 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 7d4b085c6f..c3ef4a7848 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 @@ -165,8 +165,12 @@ class ImageAdapter( val selectedIndex: Int = if (showAlreadyActionedImages) { ImageHelper.getIndex(selectedImages, image) + + // Getting selected index when switch is off } else if (actionableImagesMap.size > position) { ImageHelper.getIndex(selectedImages, ArrayList(actionableImagesMap.values)[position]) + + // For any other case return -1 } else { -1 } @@ -199,6 +203,8 @@ class ImageAdapter( uploadingContributionList ) _isLoadingImages.value = false + // If the position is already visited, that means the image is already present + // inside map, so it will fetch the image from the map and load in the holder } else { val actionableImages: List = ArrayList(actionableImagesMap.values) if (actionableImages.size > position) { @@ -210,6 +216,9 @@ class ImageAdapter( .into(holder.image) } } + + // If switch is turned off, it just fetches the image from all images without any + // further operations } else { Glide .with(holder.image) @@ -253,6 +262,10 @@ class ImageAdapter( // finding next actionable image will start from this position if (next > -1) { nextImagePosition = next + 1 + + // If map doesn't contains the next actionable image, that means it's a + // new actionable image, it will put it to the map as actionable images + // and it will load the new image in the view holder if (!actionableImagesMap.containsKey(next)) { actionableImagesMap[next] = allImages[next] alreadyAddedPositions.add(imagePositionAsPerIncreasingOrder) @@ -266,6 +279,9 @@ class ImageAdapter( notifyItemInserted(position) notifyItemRangeChanged(position, itemCount + 1) } + + // If next actionable image is not found, that means searching is + // complete till end, and it will stop searching. } else { reachedEndOfFolder = true notifyItemRemoved(position) @@ -334,7 +350,11 @@ class ImageAdapter( // Notify listener of deselection to update UI imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload) } else { - val image = images[position] + // Prevent adding the same image multiple times + val image = if (showAlreadyActionedImages) images[position] else ArrayList(actionableImagesMap.values)[position] + if (selectedImages.contains(image)) { + return // Image already selected, ignore additional clicks + } scope.launch(ioDispatcher) { val imageSHA1 = imageLoader.getSHA1(image, defaultDispatcher) withContext(Dispatchers.Main) { @@ -439,6 +459,7 @@ class ImageAdapter( } else { val iterator = actionableImagesMap.entries.iterator() var index = 0 + while (iterator.hasNext()) { val entry = iterator.next() if (entry.value == image) { @@ -466,10 +487,17 @@ class ImageAdapter( val showAlreadyActionedImages = sharedPreferences.getBoolean(SHOW_ALREADY_ACTIONED_IMAGES_PREFERENCE_KEY, true) + // While switch is on initializes the holder with all images size return if (showAlreadyActionedImages) { allImages.size + + // While switch is off and searching for next actionable has ended, initializes the holder + // with size of all actionable images } else if (actionableImagesMap.size == allImages.size || reachedEndOfFolder) { actionableImagesMap.size + + // While switch is off, initializes the holder with and extra view holder so that finding + // and addition of the next actionable image in the adapter can be continued } else { actionableImagesMap.size + 1 }