diff --git a/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt b/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt index 6b284d868b04..7138193a430c 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt @@ -11,7 +11,6 @@ import android.graphics.Bitmap import android.widget.ImageView import androidx.core.content.ContextCompat import com.nextcloud.client.account.User -import com.nextcloud.utils.OCFileUtils import com.nextcloud.utils.allocationKilobyte import com.nextcloud.utils.extensions.isPNG import com.owncloud.android.MainApp @@ -22,10 +21,13 @@ import com.owncloud.android.datamodel.ThumbnailsCacheManager import com.owncloud.android.lib.common.OwnCloudClientManagerFactory import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.utils.MimeTypeUtil +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.withContext +import java.util.WeakHashMap class GalleryImageGenerationJob(private val user: User, private val storageManager: FileDataStorageManager) { companion object { @@ -36,26 +38,44 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag Runtime.getRuntime().availableProcessors() / 2 ) ) + private val activeJobs = WeakHashMap() + + fun removeActiveJob(imageView: ImageView, job: CoroutineScope) { + if (isActiveJob(imageView, job)) { + removeJob(imageView) + } + } + + fun isActiveJob(imageView: ImageView, job: CoroutineScope): Boolean = activeJobs[imageView] === job + + fun storeJob(job: Job, imageView: ImageView) { + activeJobs[imageView] = job + } + + fun cancelPreviousJob(imageView: ImageView) { + activeJobs[imageView]?.cancel() + removeJob(imageView) + } + + fun removeJob(imageView: ImageView) { + activeJobs.remove(imageView) + } } @Suppress("TooGenericExceptionCaught") - suspend fun run( - file: OCFile, - imageView: ImageView, - imageDimension: Pair, - listener: GalleryImageGenerationListener - ) { + suspend fun run(file: OCFile, imageView: ImageView, listener: GalleryImageGenerationListener) { try { var newImage = false if (file.remoteId == null && !file.isPreviewAvailable) { + Log_OC.e(TAG, "file has no remoteId and no preview") withContext(Dispatchers.Main) { listener.onError() } return } - val bitmap: Bitmap? = getBitmap(imageView, file, imageDimension, onThumbnailGeneration = { + val bitmap: Bitmap? = getBitmap(file, onThumbnailGeneration = { newImage = true }) @@ -75,40 +95,25 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag } } - private suspend fun getBitmap( - imageView: ImageView, - file: OCFile, - imageDimension: Pair, - onThumbnailGeneration: () -> Unit - ): Bitmap? = withContext(Dispatchers.IO) { - if (file.remoteId == null && !file.isPreviewAvailable) { - Log_OC.w(TAG, "file has no remoteId and no preview") - return@withContext null - } - - val key = file.remoteId - val cachedThumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId - ) - if (cachedThumbnail != null && !file.isUpdateThumbnailNeeded) { - Log_OC.d(TAG, "cached thumbnail is used for: ${file.fileName}") - return@withContext getThumbnailFromCache(file, cachedThumbnail, key) - } + private suspend fun getBitmap(file: OCFile, onThumbnailGeneration: () -> Unit): Bitmap? = + withContext(Dispatchers.IO) { + val key = file.remoteId + val cachedThumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( + ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId + ) + if (cachedThumbnail != null && !file.isUpdateThumbnailNeeded) { + Log_OC.d(TAG, "cached thumbnail is used for: ${file.fileName}") + return@withContext getThumbnailFromCache(file, cachedThumbnail, key) + } - Log_OC.d(TAG, "generating new thumbnail for: ${file.fileName}") + Log_OC.d(TAG, "generating new thumbnail for: ${file.fileName}") - // only add placeholder if new thumbnail will be generated because cached image will appear so quickly - withContext(Dispatchers.Main) { - val placeholderDrawable = OCFileUtils.getMediaPlaceholder(file, imageDimension) - imageView.setImageDrawable(placeholderDrawable) + onThumbnailGeneration() + semaphore.withPermit { + return@withContext getThumbnailFromServerAndAddToCache(file, cachedThumbnail) + } } - onThumbnailGeneration() - semaphore.withPermit { - return@withContext getThumbnailFromServerAndAddToCache(file, cachedThumbnail) - } - } - private suspend fun setThumbnail( bitmap: Bitmap, file: OCFile, @@ -117,24 +122,25 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag listener: GalleryImageGenerationListener ) = withContext(Dispatchers.Main) { val tagId = file.fileId.toString() - if (imageView.tag?.toString() != tagId) return@withContext - if (file.isPNG()) { - imageView.setBackgroundColor( - ContextCompat.getColor( - MainApp.getAppContext(), - R.color.bg_default + if (imageView.tag.toString() == tagId) { + if (file.isPNG()) { + imageView.setBackgroundColor( + ContextCompat.getColor( + MainApp.getAppContext(), + R.color.bg_default + ) ) - ) - } + } - if (newImage) { - listener.onNewGalleryImage() - } + if (newImage) { + listener.onNewGalleryImage() + } - if (imageView.isAttachedToWindow) { - imageView.setImageBitmap(bitmap) - imageView.invalidate() + if (imageView.isAttachedToWindow) { + imageView.setImageBitmap(bitmap) + imageView.invalidate() + } } listener.onSuccess() diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt index aaab266e3679..4190f148ba9e 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt @@ -20,6 +20,7 @@ import com.nextcloud.client.jobs.gallery.GalleryImageGenerationJob import com.nextcloud.client.jobs.gallery.GalleryImageGenerationListener import com.nextcloud.client.jobs.upload.FileUploadHelper import com.nextcloud.client.preferences.AppPreferences +import com.nextcloud.utils.OCFileUtils import com.nextcloud.utils.extensions.getSubfiles import com.nextcloud.utils.extensions.makeRounded import com.nextcloud.utils.extensions.setVisibleIf @@ -106,33 +107,57 @@ class OCFileListDelegate( galleryRowHolder: GalleryRowHolder, imageDimension: Pair ) { + // Cancel previous job for this ImageView + GalleryImageGenerationJob.cancelPreviousJob(imageView) + imageView.tag = file.fileId - ioScope.launch { - galleryImageGenerationJob.run( - file, - imageView, - imageDimension, - object : GalleryImageGenerationListener { - override fun onSuccess() { - galleryRowHolder.binding.rowLayout.invalidate() - Log_OC.d(tag, "setGalleryImage.onSuccess()") - DisplayUtils.stopShimmer(shimmer, imageView) - } + // set placeholder before async job + val cached = ThumbnailsCacheManager.getBitmapFromDiskCache( + ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId + ) + if (cached != null) { + imageView.setImageBitmap(cached) + } else { + imageView.setImageDrawable(OCFileUtils.getMediaPlaceholder(file, imageDimension)) + } - override fun onNewGalleryImage() { - Log_OC.d(tag, "setGalleryImage.updateRowVisuals()") - galleryRowHolder.updateRowVisuals() - } + val job = ioScope.launch { + try { + galleryImageGenerationJob.run( + file, + imageView, + object : GalleryImageGenerationListener { + override fun onSuccess() { + if (imageView.tag == file.fileId) { + Log_OC.d(tag, "setGalleryImage.onSuccess()") + galleryRowHolder.binding.rowLayout.invalidate() + DisplayUtils.stopShimmer(shimmer, imageView) + } + } - override fun onError() { - Log_OC.d(tag, "setGalleryImage.onError()") - DisplayUtils.stopShimmer(shimmer, imageView) + override fun onNewGalleryImage() { + if (imageView.tag == file.fileId) { + Log_OC.d(tag, "setGalleryImage.updateRowVisuals()") + galleryRowHolder.updateRowVisuals() + } + } + + override fun onError() { + if (imageView.tag == file.fileId) { + Log_OC.d(tag, "setGalleryImage.onError()") + DisplayUtils.stopShimmer(shimmer, imageView) + } + } } - } - ) + ) + } finally { + GalleryImageGenerationJob.removeActiveJob(imageView, this) + } } + GalleryImageGenerationJob.storeJob(job, imageView) + imageView.setOnClickListener { ocFileListFragmentInterface.onItemClicked(file) GalleryFragment.setLastMediaItemPosition(galleryRowHolder.absoluteAdapterPosition)