This commit is contained in:
VoidRaven 2025-10-25 20:27:29 +11:00 committed by GitHub
commit ad69041a9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 63 additions and 54 deletions

View file

@ -10,4 +10,5 @@ object CustomSelectorConstants {
const val NEW_SELECTED_IMAGES = "new_selected_images" const val NEW_SELECTED_IMAGES = "new_selected_images"
const val SHOULD_REFRESH = "should_refresh" const val SHOULD_REFRESH = "should_refresh"
const val FULL_SCREEN_MODE_FIRST_LUNCH = "full_screen_mode_first_launch" const val FULL_SCREEN_MODE_FIRST_LUNCH = "full_screen_mode_first_launch"
const val MAX_IMAGE_COUNT = 20
} }

View file

@ -16,6 +16,7 @@ import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.customselector.helper.ImageHelper import fr.free.nrw.commons.customselector.helper.ImageHelper
import fr.free.nrw.commons.customselector.helper.ImageHelper.CUSTOM_SELECTOR_PREFERENCE_KEY import fr.free.nrw.commons.customselector.helper.ImageHelper.CUSTOM_SELECTOR_PREFERENCE_KEY
import fr.free.nrw.commons.customselector.helper.ImageHelper.SHOW_ALREADY_ACTIONED_IMAGES_PREFERENCE_KEY import fr.free.nrw.commons.customselector.helper.ImageHelper.SHOW_ALREADY_ACTIONED_IMAGES_PREFERENCE_KEY
import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants.MAX_IMAGE_COUNT
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.Image import fr.free.nrw.commons.customselector.model.Image
import fr.free.nrw.commons.customselector.ui.selector.ImageLoader import fr.free.nrw.commons.customselector.ui.selector.ImageLoader
@ -188,55 +189,38 @@ class ImageAdapter(
defaultDispatcher, defaultDispatcher,
uploadingContributionList, uploadingContributionList,
) )
scope.launch {
val sharedPreferences: SharedPreferences =
context.getSharedPreferences(CUSTOM_SELECTOR_PREFERENCE_KEY, 0)
val showAlreadyActionedImages =
sharedPreferences.getBoolean(SHOW_ALREADY_ACTIONED_IMAGES_PREFERENCE_KEY, true)
if (!showAlreadyActionedImages) {
// If the position is not already visited, that means the position is new then
// finds the next actionable image position from all images
if (!alreadyAddedPositions.contains(position)) {
processThumbnailForActionedImage(
holder,
position,
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<Image> = ArrayList(actionableImagesMap.values)
if (actionableImages.size > position) {
image = actionableImages[position]
Glide
.with(holder.image)
.load(image.uri)
.thumbnail(0.3f)
.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)
.load(image.uri)
.thumbnail(0.3f)
.into(holder.image)
}
}
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener {
onThumbnailClicked(position, holder) if (!holder.isItemUploaded() && !holder.isItemNotForUpload()) {
if (selectedImages.size >= MAX_IMAGE_COUNT && !isSelected) { //enforce the 20-image limit
Toast.makeText(context, "Cannot select more than 20 images", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (isSelected) {
selectedImages.removeAt(selectedIndex)
holder.itemUnselected()
notifyItemChanged(position, ImageUnselected())
imageSelectListener.onSelectedImagesChanged(selectedImages, selectedImages.size)
} else {
selectedImages.add(image)
holder.itemSelected()
notifyItemChanged(position, ImageSelectedOrUpdated())
imageSelectListener.onSelectedImagesChanged(selectedImages, selectedImages.size)
}
}
} }
// launch media preview on long click.
holder.itemView.setOnLongClickListener { holder.itemView.setOnLongClickListener {
imageSelectListener.onLongPress(images.indexOf(image), images, selectedImages) imageSelectListener.onLongPress(position, images, ArrayList(selectedImages))
true true
} }
//handle close button click for deselection
holder.closeButton.setOnClickListener {
if (isSelected) {
selectedImages.removeAt(selectedIndex)
holder.itemUnselected()
notifyItemChanged(position, ImageUnselected())
imageSelectListener.onSelectedImagesChanged(selectedImages, selectedImages.size)
}
}
} }
} }
@ -417,7 +401,7 @@ class ImageAdapter(
* Set new selected images * Set new selected images
*/ */
fun setSelectedImages(newSelectedImages: ArrayList<Image>) { fun setSelectedImages(newSelectedImages: ArrayList<Image>) {
selectedImages = ArrayList(newSelectedImages) selectedImages = ArrayList(newSelectedImages.take(MAX_IMAGE_COUNT)) // enforce 20-image limit
imageSelectListener.onSelectedImagesChanged(selectedImages, 0) imageSelectListener.onSelectedImagesChanged(selectedImages, 0)
} }
@ -431,7 +415,7 @@ class ImageAdapter(
) { ) {
numberOfSelectedImagesMarkedAsNotForUpload = 0 numberOfSelectedImagesMarkedAsNotForUpload = 0
images.clear() images.clear()
selectedImages = arrayListOf() selectedImages = ArrayList(selectedImages.take(5)) // enforce the 5-image limit
init(newImages, fixedImages, TreeMap(), uploadingImages) init(newImages, fixedImages, TreeMap(), uploadingImages)
notifyDataSetChanged() notifyDataSetChanged()
} }
@ -523,12 +507,14 @@ class ImageAdapter(
private val uploadingGroup: Group = itemView.findViewById(R.id.uploading_group) private val uploadingGroup: Group = itemView.findViewById(R.id.uploading_group)
private val notForUploadGroup: Group = itemView.findViewById(R.id.not_for_upload_group) private val notForUploadGroup: Group = itemView.findViewById(R.id.not_for_upload_group)
private val selectedGroup: Group = itemView.findViewById(R.id.selected_group) private val selectedGroup: Group = itemView.findViewById(R.id.selected_group)
val closeButton: ImageView = itemView.findViewById(R.id.close_button) // Added for close button
/** /**
* Item selected view. * Item selected view.
*/ */
fun itemSelected() { fun itemSelected() {
selectedGroup.visibility = View.VISIBLE selectedGroup.visibility = View.VISIBLE
closeButton.visibility = View.VISIBLE // Show close button when selected
} }
/** /**
@ -536,6 +522,7 @@ class ImageAdapter(
*/ */
fun itemUnselected() { fun itemUnselected() {
selectedGroup.visibility = View.GONE selectedGroup.visibility = View.GONE
closeButton.visibility = View.GONE // Hide close button when unselected
} }
/** /**

View file

@ -47,6 +47,7 @@ import fr.free.nrw.commons.customselector.database.NotForUploadStatus
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants
import fr.free.nrw.commons.customselector.helper.FolderDeletionHelper import fr.free.nrw.commons.customselector.helper.FolderDeletionHelper
import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants.MAX_IMAGE_COUNT
import fr.free.nrw.commons.customselector.listeners.FolderClickListener import fr.free.nrw.commons.customselector.listeners.FolderClickListener
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.Image import fr.free.nrw.commons.customselector.model.Image
@ -107,7 +108,7 @@ class CustomSelectorActivity :
/** /**
* Maximum number of images that can be selected. * Maximum number of images that can be selected.
*/ */
private var uploadLimit: Int = 20 private var uploadLimit: Int = MAX_IMAGE_COUNT // changed to 20 asper the issue #3101
/** /**
* Flag that is marked true when the amount * Flag that is marked true when the amount
@ -214,7 +215,7 @@ class CustomSelectorActivity :
) )
// Check for single selection extra // Check for single selection extra
uploadLimit = if (intent.getBooleanExtra(EXTRA_SINGLE_SELECTION, false)) 1 else 20 uploadLimit = if (intent.getBooleanExtra(EXTRA_SINGLE_SELECTION, false)) 1 else MAX_IMAGE_COUNT
setupViews() setupViews()
@ -650,8 +651,12 @@ class CustomSelectorActivity :
finishPickImages(arrayListOf()) finishPickImages(arrayListOf())
return return
} }
if (selectedImages.size > uploadLimit) {
displayUploadLimitWarning() // shows the warning dialog if >20 images
return
}
scope.launch(ioDispatcher) { scope.launch(ioDispatcher) {
val uniqueImages = selectedImages.distinctBy { image -> val uniqueImages = selectedImages.take(uploadLimit).distinctBy { image -> //enforce limit
CustomSelectorUtils.getImageSHA1( CustomSelectorUtils.getImageSHA1(
image.uri, image.uri,
ioDispatcher, ioDispatcher,

View file

@ -10,6 +10,7 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.facebook.drawee.view.SimpleDraweeView import com.facebook.drawee.view.SimpleDraweeView
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants.MAX_IMAGE_COUNT
import fr.free.nrw.commons.databinding.ItemUploadThumbnailBinding import fr.free.nrw.commons.databinding.ItemUploadThumbnailBinding
import fr.free.nrw.commons.filepicker.UploadableFile import fr.free.nrw.commons.filepicker.UploadableFile
import java.io.File import java.io.File
@ -23,7 +24,7 @@ internal class ThumbnailsAdapter(private val callback: Callback) :
var onThumbnailDeletedListener: OnThumbnailDeletedListener? = null var onThumbnailDeletedListener: OnThumbnailDeletedListener? = null
var uploadableFiles: List<UploadableFile> = emptyList() var uploadableFiles: List<UploadableFile> = emptyList()
set(value) { set(value) {
field = value field = value.take(MAX_IMAGE_COUNT) //enforce 20-image limit
notifyDataSetChanged() notifyDataSetChanged()
} }

View file

@ -27,6 +27,7 @@ import fr.free.nrw.commons.R
import fr.free.nrw.commons.auth.LoginActivity import fr.free.nrw.commons.auth.LoginActivity
import fr.free.nrw.commons.auth.SessionManager import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.contributions.ContributionController import fr.free.nrw.commons.contributions.ContributionController
import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants.MAX_IMAGE_COUNT
import fr.free.nrw.commons.databinding.ActivityUploadBinding import fr.free.nrw.commons.databinding.ActivityUploadBinding
import fr.free.nrw.commons.filepicker.Constants.RequestCodes import fr.free.nrw.commons.filepicker.Constants.RequestCodes
import fr.free.nrw.commons.filepicker.UploadableFile import fr.free.nrw.commons.filepicker.UploadableFile
@ -734,8 +735,8 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
intent.getParcelableArrayListExtra<UploadableFile>(EXTRA_FILES) intent.getParcelableArrayListExtra<UploadableFile>(EXTRA_FILES)
} }
// Convert to mutable list or return empty list if null // Convert to mutable list,takes up to 20 files, or return empty list if null
files?.toMutableList() ?: run { files?.toMutableList()?.take(MAX_IMAGE_COUNT)?.toMutableList() ?: run { //enforce 20-image limit
Timber.w("Files array was null") Timber.w("Files array was null")
mutableListOf() mutableListOf()
} }
@ -752,6 +753,9 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
Timber.d("File $index path: ${file.getFilePath()}") Timber.d("File $index path: ${file.getFilePath()}")
} }
//update thumbnails adapter with limited files
thumbnailsAdapter?.uploadableFiles = uploadableFiles
// Handle other extras with null safety // Handle other extras with null safety
place = try { place = try {
if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) { if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {

View file

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
@ -45,14 +44,24 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/> app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/close_button"
android:layout_width="@dimen/dimen_20"
android:layout_height="@dimen/dimen_20"
app:layout_constraintDimensionRatio="H,1:1"
android:src="@drawable/ic_cancel_white"
android:layout_margin="@dimen/dimen_6"
android:background="@drawable/circle_shape"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"/>
<androidx.constraintlayout.widget.Group <androidx.constraintlayout.widget.Group
android:id="@+id/selected_group" android:id="@+id/selected_group"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone" android:visibility="gone"
app:constraint_referenced_ids="selected_overlay,selected_image"/> app:constraint_referenced_ids="selected_overlay,selected_image,close_button"/>
<View <View
android:id="@+id/uploaded_overlay" android:id="@+id/uploaded_overlay"

View file

@ -887,4 +887,6 @@ Upload your first media by tapping on the add button.</string>
<string name="image_tag_line_created_and_uploaded_by">Created and uploaded by: %1$s</string> <string name="image_tag_line_created_and_uploaded_by">Created and uploaded by: %1$s</string>
<string name="image_tag_line_created_by_and_uploaded_by">Created by %1$s and uploaded by %2$s</string> <string name="image_tag_line_created_by_and_uploaded_by">Created by %1$s and uploaded by %2$s</string>
<string name="nominated_for_deletion_btn">Nominated for Deletion</string> <string name="nominated_for_deletion_btn">Nominated for Deletion</string>
<string name="custom_selector_limit_warning_message">You can only select up to 20 images.</string>
</resources> </resources>