Skip to content

Commit 2d333a2

Browse files
Custom picker: Show differently pictures that are currently being uploaded (#5618)
* Custom picker: Show differently pictures that are currently being uploaded * fix tests * fix test * fix crash * handle all cases * handle all cases * fix * Hide Images When showAlreadyActioned is disabled * Fix Tests * cleanup
1 parent 72cdb5d commit 2d333a2

File tree

9 files changed

+129
-39
lines changed

9 files changed

+129
-39
lines changed

app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView
1313
import com.bumptech.glide.Glide
1414
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
1515
import fr.free.nrw.commons.R
16+
import fr.free.nrw.commons.contributions.Contribution
1617
import fr.free.nrw.commons.customselector.helper.ImageHelper
1718
import fr.free.nrw.commons.customselector.helper.ImageHelper.CUSTOM_SELECTOR_PREFERENCE_KEY
1819
import fr.free.nrw.commons.customselector.helper.ImageHelper.SHOW_ALREADY_ACTIONED_IMAGES_PREFERENCE_KEY
@@ -85,6 +86,8 @@ class ImageAdapter(
8586
*/
8687
private var actionableImagesMap: TreeMap<Int, Image> = TreeMap()
8788

89+
private var uploadingContributionList: List<Contribution> = ArrayList()
90+
8891
/**
8992
* Stores already added positions of actionable images
9093
*/
@@ -156,9 +159,8 @@ class ImageAdapter(
156159
} else {
157160
holder.itemUnselected()
158161
}
159-
160162
imageLoader.queryAndSetView(
161-
holder, image, ioDispatcher, defaultDispatcher
163+
holder, image, ioDispatcher, defaultDispatcher ,uploadingContributionList
162164
)
163165
scope.launch {
164166
val sharedPreferences: SharedPreferences =
@@ -169,7 +171,7 @@ class ImageAdapter(
169171
// If the position is not already visited, that means the position is new then
170172
// finds the next actionable image position from all images
171173
if (!alreadyAddedPositions.contains(position)) {
172-
processThumbnailForActionedImage(holder, position)
174+
processThumbnailForActionedImage(holder, position, uploadingContributionList)
173175

174176
// If the position is already visited, that means the image is already present
175177
// inside map, so it will fetch the image from the map and load in the holder
@@ -207,11 +209,12 @@ class ImageAdapter(
207209
*/
208210
suspend fun processThumbnailForActionedImage(
209211
holder: ImageViewHolder,
210-
position: Int
212+
position: Int,
213+
uploadingContributionList: List<Contribution>
211214
) {
212215
val next = imageLoader.nextActionableImage(
213216
allImages, ioDispatcher, defaultDispatcher,
214-
nextImagePosition
217+
nextImagePosition, uploadingContributionList
215218
)
216219

217220
// If next actionable image is found, saves it, as the the search for
@@ -331,12 +334,13 @@ class ImageAdapter(
331334
/**
332335
* Initialize the data set.
333336
*/
334-
fun init(newImages: List<Image>, fixedImages: List<Image>, emptyMap: TreeMap<Int, Image>) {
337+
fun init(newImages: List<Image>, fixedImages: List<Image>, emptyMap: TreeMap<Int, Image>, uploadedImages: List<Contribution> = ArrayList()) {
335338
allImages = fixedImages
336339
val oldImageList:ArrayList<Image> = images
337340
val newImageList:ArrayList<Image> = ArrayList(newImages)
338341
actionableImagesMap = emptyMap
339342
alreadyAddedPositions = ArrayList()
343+
uploadingContributionList = uploadedImages
340344
nextImagePosition = 0
341345
reachedEndOfFolder = false
342346
selectedImages = ArrayList()
@@ -358,11 +362,11 @@ class ImageAdapter(
358362
/**
359363
* Refresh the data in the adapter
360364
*/
361-
fun refresh(newImages: List<Image>, fixedImages: List<Image>) {
365+
fun refresh(newImages: List<Image>, fixedImages: List<Image>, uploadingImages: List<Contribution> = ArrayList()) {
362366
numberOfSelectedImagesMarkedAsNotForUpload = 0
363367
images.clear()
364368
selectedImages = arrayListOf()
365-
init(newImages, fixedImages, TreeMap())
369+
init(newImages, fixedImages, TreeMap(),uploadingImages)
366370
notifyDataSetChanged()
367371
}
368372

@@ -452,6 +456,7 @@ class ImageAdapter(
452456
class ImageViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
453457
val image: ImageView = itemView.findViewById(R.id.image_thumbnail)
454458
private val uploadedGroup: Group = itemView.findViewById(R.id.uploaded_group)
459+
private val uploadingGroup: Group = itemView.findViewById(R.id.uploading_group)
455460
private val notForUploadGroup: Group = itemView.findViewById(R.id.not_for_upload_group)
456461
private val selectedGroup: Group = itemView.findViewById(R.id.selected_group)
457462

@@ -476,6 +481,13 @@ class ImageAdapter(
476481
uploadedGroup.visibility = View.VISIBLE
477482
}
478483

484+
/**
485+
* Item is uploading
486+
*/
487+
fun itemUploading() {
488+
uploadingGroup.visibility = View.VISIBLE
489+
}
490+
479491
/**
480492
* Item is not for upload view
481493
*/
@@ -494,6 +506,13 @@ class ImageAdapter(
494506
return notForUploadGroup.visibility == View.VISIBLE
495507
}
496508

509+
/**
510+
* Item is not uploading
511+
*/
512+
fun itemNotUploading() {
513+
uploadingGroup.visibility = View.GONE
514+
}
515+
497516
/**
498517
* Item Not Uploaded view.
499518
*/

app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class FolderFragment : CommonsDaggerSupportFragment() {
9393
*/
9494
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
9595
_binding = FragmentCustomSelectorBinding.inflate(inflater, container, false)
96-
folderAdapter = FolderAdapter(activity!!, activity as FolderClickListener)
96+
folderAdapter = FolderAdapter(requireActivity(), activity as FolderClickListener)
9797
gridLayoutManager = GridLayoutManager(context, columnCount())
9898
selectorRV = binding?.selectorRv
9999
loader = binding?.loader
@@ -159,4 +159,4 @@ class FolderFragment : CommonsDaggerSupportFragment() {
159159
return 2
160160
// todo change column count depending on the orientation of the device.
161161
}
162-
}
162+
}

app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import androidx.lifecycle.Observer
1515
import androidx.lifecycle.ViewModelProvider
1616
import androidx.recyclerview.widget.GridLayoutManager
1717
import androidx.recyclerview.widget.RecyclerView
18+
import fr.free.nrw.commons.contributions.Contribution
19+
import fr.free.nrw.commons.contributions.ContributionDao
1820
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
1921
import fr.free.nrw.commons.customselector.database.UploadedStatusDao
2022
import fr.free.nrw.commons.customselector.helper.ImageHelper
@@ -34,6 +36,7 @@ import fr.free.nrw.commons.media.MediaClient
3436
import fr.free.nrw.commons.theme.BaseActivity
3537
import fr.free.nrw.commons.upload.FileProcessor
3638
import fr.free.nrw.commons.upload.FileUtilsWrapper
39+
import io.reactivex.schedulers.Schedulers
3740
import java.util.*
3841
import javax.inject.Inject
3942

@@ -134,6 +137,9 @@ class ImageFragment : CommonsDaggerSupportFragment(), RefreshUIListener, PassDat
134137
@Inject
135138
lateinit var mediaClient: MediaClient
136139

140+
@Inject
141+
lateinit var contributionDao: ContributionDao
142+
137143
companion object {
138144

139145
/**
@@ -236,7 +242,8 @@ class ImageFragment : CommonsDaggerSupportFragment(), RefreshUIListener, PassDat
236242
editor.apply()
237243
}
238244

239-
imageAdapter.init(allImages, allImages, TreeMap())
245+
val uploadingContributions = getUploadingContributions()
246+
imageAdapter.init(allImages, allImages, TreeMap(), uploadingContributions)
240247
imageAdapter.notifyDataSetChanged()
241248
}
242249

@@ -258,10 +265,12 @@ class ImageFragment : CommonsDaggerSupportFragment(), RefreshUIListener, PassDat
258265
private fun handleResult(result: Result) {
259266
if (result.status is CallbackStatus.SUCCESS) {
260267
val images = result.images
268+
269+
val uploadingContributions = getUploadingContributions()
261270
if (images.isNotEmpty()) {
262271
filteredImages = ImageHelper.filterImages(images, bucketId)
263272
allImages = ArrayList(filteredImages)
264-
imageAdapter.init(filteredImages, allImages, TreeMap())
273+
imageAdapter.init(filteredImages, allImages, TreeMap(), uploadingContributions)
265274
selectorRV?.let {
266275
it.visibility = View.VISIBLE
267276
lastItemId?.let { pos ->
@@ -336,7 +345,7 @@ class ImageFragment : CommonsDaggerSupportFragment(), RefreshUIListener, PassDat
336345
}
337346

338347
override fun refresh() {
339-
imageAdapter.refresh(filteredImages, allImages)
348+
imageAdapter.refresh(filteredImages, allImages, getUploadingContributions())
340349
}
341350

342351
/**
@@ -359,8 +368,10 @@ class ImageFragment : CommonsDaggerSupportFragment(), RefreshUIListener, PassDat
359368
override fun passSelectedImages(selectedImages: ArrayList<Image>, shouldRefresh: Boolean) {
360369
imageAdapter.setSelectedImages(selectedImages)
361370

371+
val uploadingContributions = getUploadingContributions()
372+
362373
if (!showAlreadyActionedImages && shouldRefresh) {
363-
imageAdapter.init(filteredImages, allImages, TreeMap())
374+
imageAdapter.init(filteredImages, allImages, TreeMap(), uploadingContributions)
364375
imageAdapter.setSelectedImages(selectedImages)
365376
}
366377
}
@@ -384,4 +395,11 @@ class ImageFragment : CommonsDaggerSupportFragment(), RefreshUIListener, PassDat
384395
}
385396
}
386397

398+
private fun getUploadingContributions(): List<Contribution> {
399+
400+
return contributionDao.getContribution(
401+
listOf(Contribution.STATE_IN_PROGRESS, Contribution.STATE_FAILED, Contribution.STATE_QUEUED, Contribution.STATE_PAUSED)
402+
)?.subscribeOn(Schedulers.io())?.blockingGet() ?: emptyList()
403+
}
404+
387405
}

app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package fr.free.nrw.commons.customselector.ui.selector
33
import android.content.Context
44
import android.content.SharedPreferences
55
import android.net.Uri
6+
import fr.free.nrw.commons.contributions.Contribution
67
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
78
import fr.free.nrw.commons.customselector.database.UploadedStatus
89
import fr.free.nrw.commons.customselector.database.UploadedStatusDao
@@ -75,7 +76,8 @@ class ImageLoader @Inject constructor(
7576
holder: ImageViewHolder,
7677
image: Image,
7778
ioDispatcher: CoroutineDispatcher,
78-
defaultDispatcher: CoroutineDispatcher
79+
defaultDispatcher: CoroutineDispatcher,
80+
uploadedContributionsList : List<Contribution>
7981
) {
8082

8183
/**
@@ -84,6 +86,7 @@ class ImageLoader @Inject constructor(
8486
mapHolderImage[holder] = image
8587
holder.itemNotUploaded()
8688
holder.itemForUpload()
89+
holder.itemNotUploading()
8790

8891
scope.launch {
8992
var result: Result = Result.NOTFOUND
@@ -214,6 +217,17 @@ class ImageLoader @Inject constructor(
214217
holder.itemNotForUpload()
215218
} else holder.itemForUpload()
216219
}
220+
221+
if (uploadedContributionsList.isNotEmpty()) {
222+
for (contribution in uploadedContributionsList ) {
223+
if (contribution.contentUri == image.uri && showAlreadyActionedImages) {
224+
holder.itemUploading()
225+
break
226+
} else {
227+
holder.itemNotUploading()
228+
}
229+
}
230+
}
217231
}
218232
}
219233

@@ -223,17 +237,22 @@ class ImageLoader @Inject constructor(
223237
suspend fun nextActionableImage(
224238
allImages: List<Image>, ioDispatcher: CoroutineDispatcher,
225239
defaultDispatcher: CoroutineDispatcher,
226-
nextImagePosition: Int
240+
nextImagePosition: Int,
241+
currentlyUploadingImages: List<Contribution>
227242
): Int {
228243
var next: Int
229-
230244
// Traversing from given position to the end
231245
for (i in nextImagePosition until allImages.size){
232-
val it = allImages[i]
233-
val imageSHA1: String = when (mapImageSHA1[it.uri] != null) {
234-
true -> mapImageSHA1[it.uri]!!
246+
val currentImage = allImages[i]
247+
248+
if (currentlyUploadingImages.any { it.contentUri == currentImage.uri }) {
249+
continue // Skip this image as it's currently being uploaded
250+
}
251+
252+
val imageSHA1: String = when (mapImageSHA1[currentImage.uri] != null) {
253+
true -> mapImageSHA1[currentImage.uri]!!
235254
else -> CustomSelectorUtils.getImageSHA1(
236-
it.uri,
255+
currentImage.uri,
237256
ioDispatcher,
238257
fileUtilsWrapper,
239258
context.contentResolver
@@ -253,7 +272,7 @@ class ImageLoader @Inject constructor(
253272
// If the image is not present in the already uploaded table, checks for its
254273
// modified SHA1 in already uploaded table
255274
if (next <= 0) {
256-
val modifiedImageSha1 = getSHA1(it, defaultDispatcher)
275+
val modifiedImageSha1 = getSHA1(currentImage, defaultDispatcher)
257276
next = uploadedStatusDao.findByModifiedImageSHA1(
258277
modifiedImageSha1,
259278
true
@@ -360,4 +379,4 @@ class ImageLoader @Inject constructor(
360379
const val INVALIDATE_DAY_COUNT: Long = 7
361380
}
362381

363-
}
382+
}

app/src/main/res/layout/item_custom_selector_image.xml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@
6161
android:alpha="0.15"
6262
android:background="@color/black"/>
6363

64+
<View
65+
android:id="@+id/uploading_overlay"
66+
android:layout_width="match_parent"
67+
android:layout_height="match_parent"
68+
android:alpha="0.15"
69+
android:background="@color/black"/>
70+
6471

6572
<ImageView
6673
android:id="@+id/uploaded_overlay_icon"
@@ -71,13 +78,29 @@
7178
app:srcCompat="@drawable/commons"
7279
/>
7380

81+
<ImageView
82+
android:id="@+id/uploading_overlay_icon"
83+
android:layout_width="@dimen/dimen_50"
84+
android:layout_height="@dimen/dimen_50"
85+
android:rotationX="180"
86+
app:layout_constraintBottom_toBottomOf="parent"
87+
app:layout_constraintEnd_toEndOf="parent"
88+
app:srcCompat="@drawable/menu_ic_download_24dp" />
89+
7490
<androidx.constraintlayout.widget.Group
7591
android:id="@+id/uploaded_group"
7692
android:layout_width="wrap_content"
7793
android:layout_height="wrap_content"
7894
android:visibility="gone"
7995
app:constraint_referenced_ids="uploaded_overlay,uploaded_overlay_icon"/>
8096

97+
<androidx.constraintlayout.widget.Group
98+
android:id="@+id/uploading_group"
99+
android:layout_width="wrap_content"
100+
android:layout_height="wrap_content"
101+
android:visibility="gone"
102+
app:constraint_referenced_ids="uploading_overlay,uploading_overlay_icon"/>
103+
81104
<ImageView
82105
android:id="@+id/not_for_upload_overlay_icon"
83106
android:layout_width="@dimen/dimen_50"
@@ -98,4 +121,4 @@
98121

99122
</androidx.constraintlayout.widget.ConstraintLayout>
100123
</androidx.cardview.widget.CardView>
101-
</androidx.constraintlayout.widget.ConstraintLayout>
124+
</androidx.constraintlayout.widget.ConstraintLayout>

app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapterTest.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,8 @@ class ImageAdapterTest {
128128
fun processThumbnailForActionedImage() = runBlocking {
129129
Whitebox.setInternalState(imageAdapter, "allImages", listOf(image))
130130
whenever(imageLoader.nextActionableImage(listOf(image), Dispatchers.IO, Dispatchers.Default,
131-
0)).thenReturn(0)
132-
imageAdapter.processThumbnailForActionedImage(holder, 0)
131+
0, emptyList())).thenReturn(0)
132+
imageAdapter.processThumbnailForActionedImage(holder, 0, emptyList())
133133
}
134134

135135
/**
@@ -138,8 +138,8 @@ class ImageAdapterTest {
138138
@Test
139139
fun `processThumbnailForActionedImage when reached end of the folder`() = runBlocking {
140140
whenever(imageLoader.nextActionableImage(ArrayList(), Dispatchers.IO, Dispatchers.Default,
141-
0)).thenReturn(-1)
142-
imageAdapter.processThumbnailForActionedImage(holder, 0)
141+
0, emptyList())).thenReturn(-1)
142+
imageAdapter.processThumbnailForActionedImage(holder, 0, emptyList())
143143
}
144144

145145
/**
@@ -243,4 +243,4 @@ class ImageAdapterTest {
243243
imageAdapter.init(listOf(image), listOf(image), TreeMap())
244244
Assertions.assertEquals(1, imageAdapter.getImageIdAt(0))
245245
}
246-
}
246+
}

0 commit comments

Comments
 (0)