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 new file mode 100644 index 000000000000..a18dadecb94e --- /dev/null +++ b/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt @@ -0,0 +1,170 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.client.jobs.gallery + +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.owncloud.android.MainApp +import com.owncloud.android.R +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.datamodel.OCFile +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.Dispatchers +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit +import kotlinx.coroutines.withContext + +class GalleryImageGenerationJob(private val user: User, private val storageManager: FileDataStorageManager) { + companion object { + private const val TAG = "GalleryImageGenerationJob" + private val semaphore = Semaphore( + maxOf( + 3, + Runtime.getRuntime().availableProcessors() / 2 + ) + ) + } + + @Suppress("TooGenericExceptionCaught") + suspend fun run( + file: OCFile, + imageView: ImageView, + imageDimension: Pair, + listener: GalleryImageGenerationListener + ) { + try { + var newImage = false + + if (file.remoteId == null && !file.isPreviewAvailable) { + listener.onError() + return + } + + val bitmap: Bitmap? = getBitmap(imageView, file, imageDimension, onThumbnailGeneration = { + newImage = true + }) + + if (bitmap == null) { + listener.onError() + return + } + + setThumbnail(bitmap, file, imageView, newImage, listener) + } catch (e: Exception) { + Log_OC.e(TAG, "gallery image generation job: ", e) + withContext(Dispatchers.Main) { + listener.onError() + } + } + } + + 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) + } + + 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) + } + } + + private suspend fun setThumbnail( + bitmap: Bitmap, + file: OCFile, + imageView: ImageView, + newImage: Boolean, + listener: GalleryImageGenerationListener + ) = withContext(Dispatchers.Main) { + val tagId = file.fileId.toString() + if (imageView.tag?.toString() != tagId) return@withContext + + if ("image/png".equals(file.mimeType, ignoreCase = true)) { + imageView.setBackgroundColor( + ContextCompat.getColor( + MainApp.getAppContext(), + R.color.bg_default + ) + ) + } + + if (newImage) { + listener.onNewGalleryImage() + } + + imageView.setImageBitmap(bitmap) + imageView.invalidate() + listener.onSuccess() + } + + private fun getThumbnailFromCache(file: OCFile, thumbnail: Bitmap, key: String): Bitmap { + var result = thumbnail + if (MimeTypeUtil.isVideo(file)) { + result = ThumbnailsCacheManager.addVideoOverlay(thumbnail, MainApp.getAppContext()) + } + + if (thumbnail.allocationKilobyte() > ThumbnailsCacheManager.THUMBNAIL_SIZE_IN_KB) { + result = ThumbnailsCacheManager.getScaledThumbnailAfterSave(result, key) + } + + return result + } + + @Suppress("DEPRECATION", "TooGenericExceptionCaught") + private suspend fun getThumbnailFromServerAndAddToCache(file: OCFile, thumbnail: Bitmap?): Bitmap? { + var thumbnail = thumbnail + try { + val client = withContext(Dispatchers.IO) { + OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor( + user.toOwnCloudAccount(), + MainApp.getAppContext() + ) + } + ThumbnailsCacheManager.setClient(client) + thumbnail = ThumbnailsCacheManager.doResizedImageInBackground(file, storageManager) + + if (MimeTypeUtil.isVideo(file) && thumbnail != null) { + thumbnail = ThumbnailsCacheManager.addVideoOverlay(thumbnail, MainApp.getAppContext()) + } + } catch (t: Throwable) { + Log_OC.e(TAG, "Generation of gallery image for $file failed", t) + } + + return thumbnail + } +} diff --git a/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationListener.kt b/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationListener.kt new file mode 100644 index 000000000000..e7ca171a1151 --- /dev/null +++ b/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationListener.kt @@ -0,0 +1,14 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.client.jobs.gallery + +interface GalleryImageGenerationListener { + fun onSuccess() + fun onNewGalleryImage() + fun onError() +} diff --git a/app/src/main/java/com/nextcloud/utils/OCFileUtils.kt b/app/src/main/java/com/nextcloud/utils/OCFileUtils.kt index 429de5a23bed..142c6edfa009 100644 --- a/app/src/main/java/com/nextcloud/utils/OCFileUtils.kt +++ b/app/src/main/java/com/nextcloud/utils/OCFileUtils.kt @@ -6,10 +6,18 @@ */ package com.nextcloud.utils +import android.graphics.Color +import android.graphics.drawable.BitmapDrawable +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.toBitmap +import androidx.core.graphics.drawable.toDrawable import androidx.exifinterface.media.ExifInterface +import com.owncloud.android.MainApp +import com.owncloud.android.R import com.owncloud.android.datamodel.OCFile import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.utils.BitmapUtils +import com.owncloud.android.utils.MimeTypeUtil object OCFileUtils { private const val TAG = "OCFileUtils" @@ -19,38 +27,67 @@ object OCFileUtils { try { Log_OC.d(TAG, "Getting image size for: ${ocFile.fileName}") - if (!ocFile.exists()) { - ocFile.imageDimension?.width?.let { w -> - ocFile.imageDimension?.height?.let { h -> - return w.toInt() to h.toInt() - } - } - val size = defaultThumbnailSize.toInt().coerceAtLeast(1) - return size to size + val widthFromDimension = ocFile.imageDimension?.width + val heightFromDimension = ocFile.imageDimension?.height + if (widthFromDimension != null && heightFromDimension != null) { + val width = widthFromDimension.toInt() + val height = heightFromDimension.toInt() + Log_OC.d(TAG, "Image dimensions are used, width: $width, height: $height") + return width to height } - val exif = ExifInterface(ocFile.storagePath) - val width = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0) - val height = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0) + return if (ocFile.exists()) { + val exif = ExifInterface(ocFile.storagePath) + val width = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0) + val height = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0) - if (width > 0 && height > 0) { - Log_OC.d(TAG, "Exif used width: $width and height: $height") - return width to height - } + if (width > 0 && height > 0) { + Log_OC.d(TAG, "Exif used width: $width and height: $height") + width to height + } - val (bitmapWidth, bitmapHeight) = BitmapUtils.getImageResolution(ocFile.storagePath) - .let { it[0] to it[1] } + val (bitmapWidth, bitmapHeight) = BitmapUtils.getImageResolution(ocFile.storagePath) + .let { it[0] to it[1] } - if (bitmapWidth > 0 && bitmapHeight > 0) { - Log_OC.d(TAG, "BitmapUtils.getImageResolution used width: $bitmapWidth and height: $bitmapHeight") - return bitmapWidth to bitmapHeight - } + if (bitmapWidth > 0 && bitmapHeight > 0) { + Log_OC.d(TAG, "BitmapUtils.getImageResolution used width: $bitmapWidth and height: $bitmapHeight") + bitmapWidth to bitmapHeight + } - val fallback = defaultThumbnailSize.toInt().coerceAtLeast(1) - Log_OC.d(TAG, "Default size used width: $fallback and height: $fallback") - return fallback to fallback + val fallback = defaultThumbnailSize.toInt().coerceAtLeast(1) + Log_OC.d(TAG, "Default size used width: $fallback and height: $fallback") + fallback to fallback + } else { + Log_OC.d(TAG, "Default size is used: $defaultThumbnailSize") + val size = defaultThumbnailSize.toInt().coerceAtLeast(1) + size to size + } } finally { Log_OC.d(TAG, "-----------------------------") } } + + fun getMediaPlaceholder(file: OCFile, imageDimension: Pair): BitmapDrawable { + val context = MainApp.getAppContext() + + val drawableId = if (MimeTypeUtil.isImage(file)) { + R.drawable.file_image + } else if (MimeTypeUtil.isVideo(file)) { + R.drawable.file_movie + } else { + R.drawable.file + } + + val drawable = ContextCompat.getDrawable(context, drawableId) + ?: return Color.GRAY.toDrawable().toBitmap(imageDimension.first, imageDimension.second) + .toDrawable(context.resources) + + val bitmap = BitmapUtils.drawableToBitmap( + drawable, + imageDimension.first, + imageDimension.second + ) + + return bitmap.toDrawable(context.resources) + } } diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt index 1e327395f7c7..34f46ab66922 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt @@ -31,3 +31,9 @@ fun List.limitToPersonalFiles(userId: String): List = filter { f ownerId == userId && !file.isSharedWithMe && !file.mounted() } == true } + +fun OCFile.mediaSize(defaultThumbnailSize: Float): Pair { + val width = (imageDimension?.width?.toInt() ?: defaultThumbnailSize.toInt()) + val height = (imageDimension?.height?.toInt() ?: defaultThumbnailSize.toInt()) + return width to height +} diff --git a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java index 96ab32fea907..240185d2cc18 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -106,7 +106,7 @@ public final class ThumbnailsCacheManager { private static final CompressFormat mCompressFormat = CompressFormat.JPEG; private static final int mCompressQuality = 70; private static OwnCloudClient mClient; - private static final int THUMBNAIL_SIZE_IN_KB = 512; + public static final int THUMBNAIL_SIZE_IN_KB = 512; private static final int RESIZED_IMAGE_SIZE_IN_KB = 10240; public static final Bitmap mDefaultImg = BitmapFactory.decodeResource(MainApp.getAppContext().getResources(), @@ -300,164 +300,17 @@ public static Bitmap getBitmapFromDiskCache(String key) { return null; } - public static class GalleryImageGenerationTask extends AsyncTask { - private final User user; - private final FileDataStorageManager storageManager; - private final WeakReference imageViewReference; - private OCFile file; - private final String imageKey; - private GalleryListener listener; - private final List asyncTasks; - private final int backgroundColor; - private boolean newImage = false; - - public GalleryImageGenerationTask( - ImageView imageView, - User user, - FileDataStorageManager storageManager, - List asyncTasks, - String imageKey, - int backgroundColor) { - this.user = user; - this.storageManager = storageManager; - imageViewReference = new WeakReference<>(imageView); - this.asyncTasks = asyncTasks; - this.imageKey = imageKey; - this.backgroundColor = backgroundColor; - } - - public void setListener(GalleryListener listener) { - this.listener = listener; - } - - @Override - protected Bitmap doInBackground(Object... params) { - Bitmap thumbnail; - - if (params == null || params.length == 0 || !(params[0] instanceof OCFile)) { - Log_OC.d(TAG, "Downloaded file is null or is not an instance of OCFile"); - return null; - } - - file = (OCFile) params[0]; - - if (file.getRemoteId() != null || file.isPreviewAvailable()) { - // Thumbnail in cache? - thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.getRemoteId()); - - if (thumbnail != null && !file.isUpdateThumbnailNeeded()) - return getThumbnailFromCache(thumbnail); - - return getThumbnailFromServerAndAddToCache(thumbnail); - } - - Log_OC.d(TAG, "File cannot be previewed"); - return null; - } - - @Nullable - private Bitmap getThumbnailFromServerAndAddToCache(Bitmap thumbnail) { - try { - mClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(user.toOwnCloudAccount(), - MainApp.getAppContext()); - - thumbnail = doResizedImageInBackground(file, storageManager); - newImage = true; - - if (MimeTypeUtil.isVideo(file) && thumbnail != null) { - thumbnail = addVideoOverlay(thumbnail, MainApp.getAppContext()); - } - - } catch (OutOfMemoryError oome) { - Log_OC.e(TAG, "Out of memory"); - } catch (Throwable t) { - // the app should never break due to a problem with thumbnails - Log_OC.e(TAG, "Generation of gallery image for " + file + " failed", t); - } - - return thumbnail; - } - - private Bitmap getThumbnailFromCache(Bitmap thumbnail) { - float size = (float) ThumbnailsCacheManager.getThumbnailDimension(); - - // resized dimensions - ImageDimension imageDimension = file.getImageDimension(); - if (imageDimension == null || - imageDimension.getWidth() != size || - imageDimension.getHeight() != size) { - file.setImageDimension(new ImageDimension(thumbnail.getWidth(), thumbnail.getHeight())); - storageManager.saveFile(file); - } - - Bitmap result = thumbnail; - if (MimeTypeUtil.isVideo(file)) { - result = ThumbnailsCacheManager.addVideoOverlay(thumbnail, MainApp.getAppContext()); - } - - if (BitmapExtensionsKt.allocationKilobyte(thumbnail) > THUMBNAIL_SIZE_IN_KB) { - result = getScaledThumbnailAfterSave(result); - } - - return result; - } - - private Bitmap getScaledThumbnailAfterSave(Bitmap thumbnail) { - Bitmap result = BitmapExtensionsKt.scaleUntil(thumbnail, THUMBNAIL_SIZE_IN_KB); - - synchronized (mThumbnailsDiskCacheLock) { - if (mThumbnailCache != null) { - Log_OC.d(TAG, "Scaling bitmap before caching: " + imageKey); - mThumbnailCache.put(imageKey, result); - } - } - - return result; - } - - protected void onPostExecute(Bitmap bitmap) { - if (bitmap != null && imageViewReference.get() != null) { - final ImageView imageView = imageViewReference.get(); - final GalleryImageGenerationTask bitmapWorkerTask = getGalleryImageGenerationTask(imageView); - - if (this == bitmapWorkerTask) { - String tagId = String.valueOf(file.getFileId()); - - if (String.valueOf(imageView.getTag()).equals(tagId)) { - if ("image/png".equalsIgnoreCase(file.getMimeType())) { - imageView.setBackgroundColor(backgroundColor); - } - - if (newImage && listener != null) { - listener.onNewGalleryImage(); - } - imageView.setImageBitmap(bitmap); - imageView.invalidate(); - } - } + public static Bitmap getScaledThumbnailAfterSave(Bitmap thumbnail, String imageKey) { + Bitmap result = BitmapExtensionsKt.scaleUntil(thumbnail, THUMBNAIL_SIZE_IN_KB); - if (listener != null) { - listener.onSuccess(); - } - } else { - if (listener != null) { - listener.onError(); - } - } - - if (asyncTasks != null) { - asyncTasks.remove(this); + synchronized (mThumbnailsDiskCacheLock) { + if (mThumbnailCache != null) { + Log_OC.d(TAG, "Scaling bitmap before caching: " + imageKey); + mThumbnailCache.put(imageKey, result); } } - public interface GalleryListener { - void onSuccess(); - - void onNewGalleryImage(); - - void onError(); - } + return result; } public static class ResizedImageGenerationTask extends AsyncTask { @@ -1217,16 +1070,6 @@ private static ResizedImageGenerationTask getResizedImageGenerationWorkerTask(Im return null; } - private static GalleryImageGenerationTask getGalleryImageGenerationTask(ImageView imageView) { - if (imageView != null) { - final Drawable drawable = imageView.getDrawable(); - if (drawable instanceof AsyncGalleryImageDrawable asyncDrawable) { - return asyncDrawable.getBitmapWorkerTask(); - } - } - return null; - } - public static Bitmap addVideoOverlay(Bitmap thumbnail, Context context) { Drawable playButtonDrawable = ResourcesCompat.getDrawable(MainApp.getAppContext().getResources(), @@ -1285,19 +1128,6 @@ private ResizedImageGenerationTask getBitmapWorkerTask() { } } - public static class AsyncGalleryImageDrawable extends BitmapDrawable { - private final WeakReference bitmapWorkerTaskReference; - - public AsyncGalleryImageDrawable(Resources res, Bitmap bitmap, GalleryImageGenerationTask bitmapWorkerTask) { - super(res, bitmap); - bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); - } - - private GalleryImageGenerationTask getBitmapWorkerTask() { - return bitmapWorkerTaskReference.get(); - } - } - public static class AsyncMediaThumbnailDrawable extends BitmapDrawable { public AsyncMediaThumbnailDrawable(Resources res, Bitmap bitmap) { @@ -1416,7 +1246,11 @@ public static void clearCache() { mThumbnailCache = null; } - private static Bitmap doResizedImageInBackground(OCFile file, FileDataStorageManager storageManager) { + public static void setClient(OwnCloudClient client) { + mClient = client; + } + + public static Bitmap doResizedImageInBackground(OCFile file, FileDataStorageManager storageManager) { Bitmap thumbnail; String imageKey = PREFIX_RESIZED_IMAGE + file.getRemoteId(); diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryRowHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryRowHolder.kt index 124c3b8f13d2..877ed965f7e0 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryRowHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryRowHolder.kt @@ -14,22 +14,20 @@ import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import androidx.core.content.ContextCompat -import androidx.core.content.res.ResourcesCompat import androidx.core.view.get import com.afollestad.sectionedrecyclerview.SectionedViewHolder import com.elyeproj.loaderviewlibrary.LoaderImageView import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.utils.OCFileUtils import com.nextcloud.utils.extensions.makeRounded +import com.nextcloud.utils.extensions.mediaSize import com.nextcloud.utils.extensions.setVisibleIf import com.owncloud.android.R import com.owncloud.android.databinding.GalleryRowBinding import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.GalleryRow import com.owncloud.android.datamodel.OCFile -import com.owncloud.android.datamodel.ThumbnailsCacheManager import com.owncloud.android.lib.resources.files.model.ImageDimension -import com.owncloud.android.utils.BitmapUtils import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.theme.ViewThemeUtils @@ -53,12 +51,6 @@ class GalleryRowHolder( private val standardMargin by lazy { context.resources.getDimension(R.dimen.standard_margin) } private val checkBoxMargin by lazy { context.resources.getDimension(R.dimen.standard_quarter_padding) } - private val defaultBitmap by lazy { - val fileDrawable = ResourcesCompat.getDrawable(context.resources, R.drawable.file_image, null) - val thumbnailSize = defaultThumbnailSize.toInt() - BitmapUtils.drawableToBitmap(fileDrawable, thumbnailSize, thumbnailSize) - } - private val checkedDrawable by lazy { ContextCompat.getDrawable(context, R.drawable.ic_checkbox_marked)?.also { viewThemeUtils.platform.tintDrawable(context, it, ColorRole.PRIMARY) @@ -79,7 +71,10 @@ class GalleryRowHolder( // Only rebuild if file count changed if (lastFileCount != requiredCount) { binding.rowLayout.removeAllViews() - repeat(requiredCount) { binding.rowLayout.addView(getRowLayout()) } + for (file in row.files) { + val rowLayout = getRowLayout(file) + binding.rowLayout.addView(rowLayout) + } lastFileCount = requiredCount } @@ -94,7 +89,7 @@ class GalleryRowHolder( bind(currentRow) } - private fun getRowLayout(): FrameLayout { + private fun getRowLayout(file: OCFile): FrameLayout { val checkbox = ImageView(context).apply { visibility = View.GONE layoutParams = FrameLayout.LayoutParams( @@ -107,21 +102,21 @@ class GalleryRowHolder( } } + val mediaSize = file.mediaSize(defaultThumbnailSize) + val (width, height) = mediaSize + val shimmer = LoaderImageView(context).apply { setImageResource(R.drawable.background) resetLoader() - invalidate() + layoutParams = FrameLayout.LayoutParams(width, height) } - val drawable = ThumbnailsCacheManager.AsyncGalleryImageDrawable( - context.resources, - defaultBitmap, - null - ) + val drawable = OCFileUtils.getMediaPlaceholder(file, mediaSize) val rowCellImageView = ImageView(context).apply { setImageDrawable(drawable) adjustViewBounds = true scaleType = ImageView.ScaleType.FIT_XY + layoutParams = FrameLayout.LayoutParams(width, height) } return FrameLayout(context).apply { @@ -181,12 +176,12 @@ class GalleryRowHolder( adjustRowCell(thumbnail, isChecked) adjustCheckBox(checkBoxImageView, isChecked) - ocFileListDelegate.bindGalleryRowThumbnail( + ocFileListDelegate.bindGalleryRow( shimmer, thumbnail, file, this, - width + width to height ) // Update layout params only if they differ 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 10e1ddb5e026..aaab266e3679 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 @@ -9,17 +9,15 @@ package com.owncloud.android.ui.adapter import android.content.Context import android.content.res.Configuration -import android.graphics.Color -import android.os.AsyncTask import android.view.View import android.widget.ImageView import androidx.core.content.ContextCompat -import androidx.core.content.res.ResourcesCompat -import androidx.core.graphics.drawable.toDrawable import com.elyeproj.loaderviewlibrary.LoaderImageView import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.client.account.User import com.nextcloud.client.jobs.download.FileDownloadHelper +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.extensions.getSubfiles @@ -31,17 +29,14 @@ import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile import com.owncloud.android.datamodel.SyncedFolderProvider import com.owncloud.android.datamodel.ThumbnailsCacheManager -import com.owncloud.android.datamodel.ThumbnailsCacheManager.GalleryImageGenerationTask.GalleryListener import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.ui.activity.ComponentsGetter import com.owncloud.android.ui.activity.FolderPickerActivity import com.owncloud.android.ui.fragment.GalleryFragment import com.owncloud.android.ui.fragment.SearchType import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface -import com.owncloud.android.utils.BitmapUtils import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.EncryptionUtils -import com.owncloud.android.utils.MimeTypeUtil import com.owncloud.android.utils.theme.ViewThemeUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -70,8 +65,8 @@ class OCFileListDelegate( private var highlightedItem: OCFile? = null var isMultiSelect = false private val asyncTasks: MutableList = ArrayList() - private val asyncGalleryTasks: MutableList = ArrayList() private val ioScope = CoroutineScope(Dispatchers.IO) + private val galleryImageGenerationJob = GalleryImageGenerationJob(user, storageManager) fun setHighlightedItem(highlightedItem: OCFile?) { this.highlightedItem = highlightedItem @@ -104,22 +99,39 @@ class OCFileListDelegate( checkedFiles.clear() } - fun bindGalleryRowThumbnail( + fun bindGalleryRow( shimmer: LoaderImageView?, imageView: ImageView, file: OCFile, galleryRowHolder: GalleryRowHolder, - width: Int + imageDimension: Pair ) { imageView.tag = file.fileId - setGalleryImage( - file, - imageView, - shimmer, - galleryRowHolder, - width - ) + 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) + } + + override fun onNewGalleryImage() { + Log_OC.d(tag, "setGalleryImage.updateRowVisuals()") + galleryRowHolder.updateRowVisuals() + } + + override fun onError() { + Log_OC.d(tag, "setGalleryImage.onError()") + DisplayUtils.stopShimmer(shimmer, imageView) + } + } + ) + } imageView.setOnClickListener { ocFileListFragmentInterface.onItemClicked(file) @@ -138,83 +150,6 @@ class OCFileListDelegate( } } - private fun getGalleryDrawable( - file: OCFile, - width: Int, - task: ThumbnailsCacheManager.GalleryImageGenerationTask - ): ThumbnailsCacheManager.AsyncGalleryImageDrawable { - val drawable = MimeTypeUtil.getFileTypeIcon(file.mimeType, file.fileName, context, viewThemeUtils) - ?: ResourcesCompat.getDrawable(context.resources, R.drawable.file_image, null) - ?: Color.GRAY.toDrawable() - - val thumbnail = BitmapUtils.drawableToBitmap(drawable, width / 2, width / 2) - - return ThumbnailsCacheManager.AsyncGalleryImageDrawable(context.resources, thumbnail, task) - } - - @Suppress("ComplexMethod") - private fun setGalleryImage( - file: OCFile, - thumbnailView: ImageView, - shimmerThumbnail: LoaderImageView?, - galleryRowHolder: GalleryRowHolder, - width: Int - ) { - if (!ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, thumbnailView)) { - Log_OC.d(tag, "setGalleryImage.cancelPotentialThumbnailWork()") - return - } - - for (task in asyncTasks) { - if (file.remoteId != null && task.imageKey != null && file.remoteId == task.imageKey) { - return - } - } - - try { - val task = ThumbnailsCacheManager.GalleryImageGenerationTask( - thumbnailView, - user, - storageManager, - asyncGalleryTasks, - file.remoteId, - ContextCompat.getColor(context, R.color.bg_default) - ) - - val asyncDrawable = getGalleryDrawable(file, width, task) - - if (shimmerThumbnail != null) { - Log_OC.d(tag, "setGalleryImage.startShimmer()") - DisplayUtils.startShimmer(shimmerThumbnail, thumbnailView) - } - - task.setListener(object : GalleryListener { - override fun onSuccess() { - galleryRowHolder.binding.rowLayout.invalidate() - Log_OC.d(tag, "setGalleryImage.onSuccess()") - DisplayUtils.stopShimmer(shimmerThumbnail, thumbnailView) - } - - override fun onNewGalleryImage() { - Log_OC.d(tag, "updateRowVisuals") - galleryRowHolder.updateRowVisuals() - } - - override fun onError() { - Log_OC.d(tag, "setGalleryImage.onError()") - DisplayUtils.stopShimmer(shimmerThumbnail, thumbnailView) - } - }) - - thumbnailView.setImageDrawable(asyncDrawable) - - asyncGalleryTasks.add(task) - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, file) - } catch (e: IllegalArgumentException) { - Log_OC.d(tag, "ThumbnailGenerationTask : " + e.message) - } - } - fun setThumbnail(thumbnail: ImageView, shimmerThumbnail: LoaderImageView?, file: OCFile) { DisplayUtils.setThumbnail( file,