diff --git a/app/src/androidTest/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapterIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapterIT.kt index f3fff0c9742a..8025c9d41a08 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapterIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapterIT.kt @@ -23,7 +23,10 @@ import com.owncloud.android.ui.unifiedsearch.ProviderID import com.owncloud.android.ui.unifiedsearch.UnifiedSearchSection import com.owncloud.android.utils.MimeType import com.owncloud.android.utils.ScreenshotTest -import com.owncloud.android.utils.overlay.OverlayManager +import com.nextcloud.utils.thumbnail.FileThumbnailGenerator +import com.nextcloud.utils.thumbnail.FolderThumbnailGenerator +import com.nextcloud.utils.thumbnail.ThumbnailGenerator +import javax.inject.Provider import org.junit.Before import org.junit.Test @@ -32,7 +35,7 @@ class UnifiedSearchListAdapterIT : AbstractIT() { private val testClassName = "com.owncloud.android.ui.adapter.UnifiedSearchListAdapterIT" - private lateinit var overlayManager: OverlayManager + private lateinit var thumbnailGenerator: ThumbnailGenerator private lateinit var preferences: AppPreferences @Suppress("DEPRECATION") @@ -134,11 +137,20 @@ class UnifiedSearchListAdapterIT : AbstractIT() { ): UnifiedSearchListAdapter { val accountManager = UserAccountManagerImpl.fromContext(targetContext) - overlayManager = OverlayManager( - preferences = preferences, - viewThemeUtils = sut.viewThemeUtils, - context = targetContext, - accountManager = accountManager + thumbnailGenerator = ThumbnailGenerator( + fileThumbnailGenerator = FileThumbnailGenerator( + storageManager = Provider { sut.storageManager }, + preferences = preferences, + viewThemeUtils = sut.viewThemeUtils, + context = targetContext, + accountManager = accountManager + ), + folderThumbnailGenerator = FolderThumbnailGenerator( + preferences = preferences, + viewThemeUtils = sut.viewThemeUtils, + context = targetContext, + accountManager = accountManager + ) ) val adapter = UnifiedSearchListAdapter( @@ -157,7 +169,7 @@ class UnifiedSearchListAdapterIT : AbstractIT() { viewThemeUtils = sut.viewThemeUtils, appPreferences = preferences, currentDirItemAction = noopCurrentDirAction, - overlayManager = overlayManager + thumbnailGenerator = thumbnailGenerator ) adapter.shouldShowFooters(true) diff --git a/app/src/androidTest/java/com/owncloud/android/ui/fragment/GalleryFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/fragment/GalleryFragmentIT.kt index 07a6443365b3..3a68ff53e31c 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/fragment/GalleryFragmentIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/fragment/GalleryFragmentIT.kt @@ -29,11 +29,12 @@ import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withId import com.nextcloud.test.Flaky import com.nextcloud.test.TestActivity +import com.nextcloud.utils.extensions.getBigThumbnail +import com.nextcloud.utils.extensions.getBigThumbnailKey import com.owncloud.android.AbstractIT import com.owncloud.android.R import com.owncloud.android.datamodel.OCFile import com.owncloud.android.datamodel.ThumbnailsCacheManager -import com.owncloud.android.datamodel.ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.lib.resources.files.model.ImageDimension import com.owncloud.android.ui.adapter.GalleryRowHolder @@ -221,9 +222,9 @@ class GalleryFragmentIT : AbstractIT() { drawRGB(random.nextInt(256), random.nextInt(256), random.nextInt(256)) drawCircle(w / 2f, h / 2f, w.coerceAtMost(h) / 2f, Paint().apply { color = Color.BLACK }) } - ThumbnailsCacheManager.addBitmapToCache(PREFIX_RESIZED_IMAGE + file.remoteId, bitmap) + ThumbnailsCacheManager.addBitmapToCache(file.getBigThumbnailKey(), bitmap) - assertNotNull(ThumbnailsCacheManager.getBitmapFromDiskCache(PREFIX_RESIZED_IMAGE + file.remoteId)) + assertNotNull(file.getBigThumbnail()) Log_OC.d("Gallery_thumbnail", "created $id with ${bitmap.width} x ${bitmap.height}") } diff --git a/app/src/main/java/com/nextcloud/client/di/AppModule.java b/app/src/main/java/com/nextcloud/client/di/AppModule.java index 8748efcfaa4a..481ef6719d28 100644 --- a/app/src/main/java/com/nextcloud/client/di/AppModule.java +++ b/app/src/main/java/com/nextcloud/client/di/AppModule.java @@ -57,7 +57,7 @@ import com.owncloud.android.ui.activities.data.files.FilesServiceApiImpl; import com.owncloud.android.ui.activities.data.files.RemoteFilesRepository; import com.owncloud.android.ui.dialog.setupEncryption.CertificateValidator; -import com.owncloud.android.utils.overlay.OverlayManager; +import com.nextcloud.utils.thumbnail.FolderThumbnailGenerator; import com.owncloud.android.utils.theme.ViewThemeUtils; import org.greenrobot.eventbus.EventBus; @@ -272,11 +272,12 @@ CertificateValidator certificateValidator() { @Provides @Singleton - OverlayManager overlayManager( + FolderThumbnailGenerator folderThumbnailGenerator( AppPreferences appPreferences, ViewThemeUtils viewThemeUtils, Context context, UserAccountManager accountManager) { - return new OverlayManager(appPreferences, viewThemeUtils, context, accountManager); + return new FolderThumbnailGenerator(appPreferences, viewThemeUtils, context, accountManager); } + } 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 5ca2202ea0aa..7baaf488154b 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 @@ -17,6 +17,9 @@ import android.view.WindowManager import android.widget.ImageView import androidx.core.content.ContextCompat import com.nextcloud.client.account.User +import com.nextcloud.utils.extensions.getBigThumbnail +import com.nextcloud.utils.extensions.getBigThumbnailKey +import com.nextcloud.utils.extensions.getSmallThumbnail import com.nextcloud.utils.extensions.isPNG import com.nextcloud.utils.extensions.toFile import com.owncloud.android.MainApp @@ -110,28 +113,30 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag } private suspend fun getBitmap(file: OCFile, onNewThumbnail: () -> Unit): Bitmap? = withContext(Dispatchers.IO) { - val cacheKey = ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId - - val cached = ThumbnailsCacheManager.getBitmapFromDiskCache(cacheKey) + val cached = file.getBigThumbnail() if (cached != null && !file.isUpdateThumbnailNeeded) { return@withContext applyVideoOverlayIfNeeded(file, cached) } - onNewThumbnail() - if (file.isDown) { val local = decodeLocalThumbnail(file) if (local != null) { - ThumbnailsCacheManager.addBitmapToCache(cacheKey, local) + ThumbnailsCacheManager.addBitmapToCache(file.getBigThumbnailKey(), local) + onNewThumbnail() return@withContext applyVideoOverlayIfNeeded(file, local) } } val remote = semaphore.withPermit { fetchFromServer(file) } if (remote != null) { + onNewThumbnail() return@withContext applyVideoOverlayIfNeeded(file, remote) } + file.getSmallThumbnail()?.let { small -> + return@withContext applyVideoOverlayIfNeeded(file, small) + } + null } @@ -149,7 +154,7 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag val pxW = p.x val pxH = p.y - val cacheKey = ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId + val cacheKey = file.getBigThumbnailKey() var bitmap = BitmapUtils.decodeSampledBitmapFromFile(file.storagePath, pxW, pxH) ?: return null diff --git a/app/src/main/java/com/nextcloud/ui/fileInfo/ImageDetailInfo.kt b/app/src/main/java/com/nextcloud/ui/fileInfo/ImageDetailInfo.kt index 00baceba14d9..76f5511b93aa 100644 --- a/app/src/main/java/com/nextcloud/ui/fileInfo/ImageDetailInfo.kt +++ b/app/src/main/java/com/nextcloud/ui/fileInfo/ImageDetailInfo.kt @@ -19,11 +19,11 @@ import androidx.lifecycle.lifecycleScope import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.client.NominatimClient import com.nextcloud.ui.fileInfo.model.ImageMetadata +import com.nextcloud.utils.extensions.getSmallThumbnail import com.owncloud.android.MainApp import com.owncloud.android.R import com.owncloud.android.databinding.FileInfoFragmentBinding import com.owncloud.android.datamodel.OCFile -import com.owncloud.android.datamodel.ThumbnailsCacheManager import com.owncloud.android.utils.BitmapUtils import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.theme.ViewThemeUtils @@ -259,9 +259,7 @@ class ImageDetailInfo(private val fragment: FileInfoFragment, private val viewTh private fun imagePinDrawable(context: Context, file: OCFile): LayerDrawable = (ContextCompat.getDrawable(context, R.drawable.photo_pin) as LayerDrawable).apply { - val bitmap = ThumbnailsCacheManager.getBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.remoteId - ) + val bitmap = file.getSmallThumbnail() BitmapUtils.bitmapToCircularBitmapDrawable(fragment.resources, bitmap)?.let { setDrawable(1, it) } diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt index 733b87b1693a..b689a3c404b8 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt @@ -41,13 +41,12 @@ import com.owncloud.android.databinding.FileActionsBottomSheetItemBinding 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.lib.resources.files.model.FileLockType import com.owncloud.android.ui.activity.ComponentsGetter import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener import com.owncloud.android.utils.FileStorageUtils -import com.owncloud.android.utils.overlay.OverlayManager +import com.nextcloud.utils.thumbnail.ThumbnailGenerator import com.owncloud.android.utils.theme.ViewThemeUtils import javax.inject.Inject @@ -71,7 +70,7 @@ class FileActionsBottomSheet : lateinit var syncedFolderProvider: SyncedFolderProvider @Inject - lateinit var overlayManager: OverlayManager + lateinit var thumbnailGenerator: ThumbnailGenerator private lateinit var viewModel: FileActionsViewModel @@ -81,8 +80,6 @@ class FileActionsBottomSheet : private lateinit var componentsGetter: ComponentsGetter - private val thumbnailAsyncTasks = mutableListOf() - private var endpoints: List? = mutableListOf() private lateinit var clientIntegration: ClientIntegration @@ -149,18 +146,10 @@ class FileActionsBottomSheet : private fun loadFileThumbnail(titleFile: OCFile?) { titleFile?.let { - DisplayUtils.setThumbnail( + thumbnailGenerator.setThumbnail( it, binding.thumbnailLayout.thumbnail, - currentUserProvider.user, - storageManager, - thumbnailAsyncTasks, - false, - context, - binding.thumbnailLayout.thumbnailShimmer, - syncedFolderProvider.preferences, - viewThemeUtils, - overlayManager + shimmer = binding.thumbnailLayout.thumbnailShimmer ) } } diff --git a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt index 89573ec5f443..6b51f44360b9 100644 --- a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt +++ b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt @@ -32,10 +32,9 @@ import com.owncloud.android.databinding.FileActionsBottomSheetBinding import com.owncloud.android.databinding.FileActionsBottomSheetItemBinding import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.SyncedFolderProvider -import com.owncloud.android.datamodel.ThumbnailsCacheManager import com.owncloud.android.lib.resources.trashbin.model.TrashbinFile import com.owncloud.android.utils.DisplayUtils -import com.owncloud.android.utils.overlay.OverlayManager +import com.nextcloud.utils.thumbnail.ThumbnailGenerator import com.owncloud.android.utils.theme.ViewThemeUtils import javax.inject.Inject @@ -59,7 +58,7 @@ class TrashbinFileActionsBottomSheet : lateinit var syncedFolderProvider: SyncedFolderProvider @Inject - lateinit var overlayManager: OverlayManager + lateinit var thumbnailGenerator: ThumbnailGenerator private lateinit var viewModel: TrashbinFileActionsViewModel @@ -67,8 +66,6 @@ class TrashbinFileActionsBottomSheet : val binding get() = _binding!! - private val thumbnailAsyncTasks = mutableListOf() - fun interface ResultListener { fun onResult(@IdRes actionId: Int) } @@ -122,18 +119,10 @@ class TrashbinFileActionsBottomSheet : private fun loadFileThumbnail(titleFile: TrashbinFile?) { titleFile?.let { - DisplayUtils.setThumbnail( + thumbnailGenerator.setThumbnail( it.toOCFile(), binding.thumbnailLayout.thumbnail, - currentUserProvider.user, - storageManager, - thumbnailAsyncTasks, - false, - context, - binding.thumbnailLayout.thumbnailShimmer, - syncedFolderProvider.preferences, - viewThemeUtils, - overlayManager + shimmer = binding.thumbnailLayout.thumbnailShimmer ) } } diff --git a/app/src/main/java/com/nextcloud/utils/ShortcutUtil.kt b/app/src/main/java/com/nextcloud/utils/ShortcutUtil.kt index f614e50aa4ba..e89bb9849933 100644 --- a/app/src/main/java/com/nextcloud/utils/ShortcutUtil.kt +++ b/app/src/main/java/com/nextcloud/utils/ShortcutUtil.kt @@ -22,11 +22,11 @@ import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toDrawable import com.nextcloud.client.account.User +import com.nextcloud.utils.extensions.getSmallThumbnail import com.owncloud.android.R import com.owncloud.android.datamodel.OCFile import com.owncloud.android.datamodel.SyncedFolderObserver import com.owncloud.android.datamodel.SyncedFolderProvider -import com.owncloud.android.datamodel.ThumbnailsCacheManager import com.owncloud.android.ui.activity.FileActivity import com.owncloud.android.ui.activity.FileDisplayActivity import com.owncloud.android.utils.MimeTypeUtil @@ -84,10 +84,7 @@ class ShortcutUtil @Inject constructor(private val mContext: Context) { user: User, syncedFolderProvider: SyncedFolderProvider ): IconCompat { - val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.remoteId - ) - + val thumbnail = file.getSmallThumbnail() return when { thumbnail != null -> IconCompat.createWithAdaptiveBitmap(bitmapToAdaptiveBitmap(thumbnail)) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/FileExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/FileExtensions.kt index b7fedf75857c..f1a64c7b18c6 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/FileExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/FileExtensions.kt @@ -7,9 +7,12 @@ package com.nextcloud.utils.extensions +import android.graphics.Bitmap import androidx.exifinterface.media.ExifInterface import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.datamodel.ThumbnailsCacheManager import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.files.model.ServerFileInterface import com.owncloud.android.utils.DisplayUtils import java.io.File import java.nio.file.Path @@ -22,6 +25,21 @@ fun OCFile?.logFileSize(tag: String) { Log_OC.d(tag, "onSaveInstanceState: $size, raw byte $rawByte") } +fun ServerFileInterface.getThumbnailKeys(): List = listOf(getBigThumbnailKey(), getSmallThumbnailKey()) + +fun ServerFileInterface.getBigThumbnail(): Bitmap? = ThumbnailsCacheManager.getBitmapFromDiskCache(getBigThumbnailKey()) + +fun ServerFileInterface.getBigThumbnailKey(): String = ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + remoteId + +fun ServerFileInterface.getSmallThumbnail(): Bitmap? = + ThumbnailsCacheManager.getBitmapFromDiskCache(getSmallThumbnailKey()) + +fun ServerFileInterface.getSmallThumbnailKey(): String = ThumbnailsCacheManager.PREFIX_THUMBNAIL + remoteId + +fun File?.getSmallThumbnail(): Bitmap? = ThumbnailsCacheManager.getBitmapFromDiskCache(getSmallThumbnailKey()) + +fun File?.getSmallThumbnailKey(): String = ThumbnailsCacheManager.PREFIX_THUMBNAIL + hashCode() + fun File?.logFileSize(tag: String) { val size = DisplayUtils.bytesToHumanReadable(this?.length() ?: -1) val rawByte = this?.length() ?: -1 diff --git a/app/src/main/java/com/nextcloud/utils/extensions/ImageViewExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/ImageViewExtensions.kt index 896d26dca84e..4f66f3fe17e7 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/ImageViewExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/ImageViewExtensions.kt @@ -10,13 +10,29 @@ package com.nextcloud.utils.extensions import android.content.Context import android.graphics.drawable.GradientDrawable import android.util.TypedValue +import android.view.View import android.view.ViewOutlineProvider import android.widget.ImageView import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import androidx.core.content.ContextCompat +import com.elyeproj.loaderviewlibrary.LoaderImageView import com.owncloud.android.R +fun ImageView.startShimmer(shimmer: LoaderImageView) { + shimmer.setImageResource(R.drawable.background) + shimmer.resetLoader() + setVisibility(View.GONE) + shimmer.setVisibility(View.VISIBLE) +} + +fun ImageView.stopShimmer(shimmer: LoaderImageView?) { + shimmer?.let { + it.visibility = View.GONE + } + setVisibility(View.VISIBLE) +} + @JvmOverloads fun ImageView.makeRoundedWithIcon( context: Context, diff --git a/app/src/main/java/com/nextcloud/utils/thumbnail/FileThumbnailGenerator.kt b/app/src/main/java/com/nextcloud/utils/thumbnail/FileThumbnailGenerator.kt new file mode 100644 index 000000000000..82351d23e6a3 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/thumbnail/FileThumbnailGenerator.kt @@ -0,0 +1,256 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.thumbnail + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.os.AsyncTask +import android.widget.FrameLayout +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.client.account.UserAccountManager +import com.nextcloud.client.preferences.AppPreferences +import com.nextcloud.model.OfflineOperationType +import com.nextcloud.utils.extensions.getSmallThumbnail +import com.nextcloud.utils.extensions.startShimmer +import com.nextcloud.utils.extensions.stopShimmer +import com.nextcloud.utils.extensions.toFile +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.datamodel.ThumbnailsCacheManager.AsyncThumbnailDrawable +import com.owncloud.android.datamodel.ThumbnailsCacheManager.ThumbnailGenerationTask +import com.owncloud.android.datamodel.ThumbnailsCacheManager.ThumbnailGenerationTaskObject +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.files.model.ServerFileInterface +import com.owncloud.android.utils.BitmapUtils +import com.owncloud.android.utils.MimeType +import com.owncloud.android.utils.MimeTypeUtil +import com.owncloud.android.utils.theme.ViewThemeUtils +import java.util.Collections +import java.util.concurrent.Executors +import javax.inject.Inject +import javax.inject.Provider +import javax.inject.Singleton +import kotlin.math.roundToInt + +@Singleton +class FileThumbnailGenerator @Inject constructor( + private val storageManager: Provider, + private val preferences: AppPreferences, + private val viewThemeUtils: ViewThemeUtils, + private val context: Context, + private val accountManager: UserAccountManager +) { + + companion object { + private const val TAG = "FileThumbnailGenerator" + private const val SHIMMER_DELAY_MS = 100L + private const val MIN_THREADS = 3 + private const val CORES_PER_THREAD = 2 + private const val OFFLINE_ICON_SIZE = 105 + private const val OFFLINE_ICON_ALPHA = 100 + } + + private val executor = Executors.newFixedThreadPool( + maxOf(MIN_THREADS, Runtime.getRuntime().availableProcessors() / CORES_PER_THREAD) + ) + + private val tasks = Collections.synchronizedList(mutableListOf()) + + fun setThumbnail(file: OCFile, view: ImageView, isGrid: Boolean, shimmer: LoaderImageView?) { + if (file.remoteId == null) { + setLocalThumbnail(file, view, isGrid, shimmer) + return + } + + if (!file.isPreviewAvailable) { + generate(file, view, isGrid, shimmer) + return + } + + val cached = file.getSmallThumbnail() + if (cached == null || file.isUpdateThumbnailNeeded) { + generate(file, view, isGrid, shimmer) + } else { + show(cached, file, view, isGrid, shimmer) + } + + applyPngBackground(file, view) + } + + fun setOfflineOperationThumbnail(file: OCFile, view: ImageView) { + if (file.isFolder) { + view.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_folder_offline)) + return + } + + file.offlineCreateFileBitmap()?.let { bitmap -> + view.setImageBitmap(BitmapUtils.addColorFilter(bitmap, Color.GRAY, OFFLINE_ICON_ALPHA)) + } + } + + private fun OCFile.offlineCreateFileBitmap(): Bitmap? { + val operation = storageManager.get().offlineOperationDao.getByPath(decryptedRemotePath) + val localPath = (operation?.type as? OfflineOperationType.CreateFile)?.localPath ?: return null + + return BitmapUtils.decodeSampledBitmapFromFile(localPath, OFFLINE_ICON_SIZE, OFFLINE_ICON_SIZE) + } + + fun cancelPendingTasks() { + synchronized(tasks) { + tasks.forEach { task -> + task.cancel(true) + task.getMethod?.abort() + } + tasks.clear() + } + } + + private fun show(bitmap: Bitmap, file: OCFile, view: ImageView, isGrid: Boolean, shimmer: LoaderImageView?) { + view.stopShimmer(shimmer) + + if (MimeTypeUtil.isVideo(file)) { + view.setImageBitmap(ThumbnailsCacheManager.addVideoOverlay(bitmap, context)) + } else { + BitmapUtils.setRoundedBitmapAccordingToListType(isGrid, bitmap, view) + } + } + + private fun setLocalThumbnail(file: OCFile, view: ImageView, isGrid: Boolean, shimmer: LoaderImageView?) { + val localFile = file.storagePath.toFile() + + if (localFile == null || !MimeTypeUtil.isImageOrVideo(file)) { + view.stopShimmer(shimmer) + view.setImageDrawable(file.mimeIcon()) + } else if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(localFile, view)) { + startTask(file, view, isGrid, shimmer, ThumbnailGenerationTaskObject(localFile, null), localFile.hashCode()) + } + } + + @Suppress("DEPRECATION") + private fun generate(file: OCFile, view: ImageView, isGrid: Boolean, shimmer: LoaderImageView?) { + if (!ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, view)) { + return + } + + val cached = file.getSmallThumbnail() + if (cached != null) { + view.setImageBitmap(cached) + view.stopShimmer(shimmer) + return + } + + val alreadyRunning = synchronized(tasks) { + tasks.removeAll { it.isCancelled || it.status == AsyncTask.Status.FINISHED } + tasks.any { it.imageKey == file.remoteId } + } + + if (!alreadyRunning) { + startTask(file, view, isGrid, shimmer, ThumbnailGenerationTaskObject(file, file.remoteId), file.fileId) + } + } + + @Suppress("TooGenericExceptionCaught", "LongParameterList", "DEPRECATION") + private fun startTask( + file: OCFile, + view: ImageView, + isGrid: Boolean, + shimmer: LoaderImageView?, + target: ThumbnailGenerationTaskObject, + tag: Any + ) { + view.tag = tag + + try { + val task = newTask(file, view, isGrid, shimmer) + view.setImageDrawable(AsyncThumbnailDrawable(context.resources, file.placeholder(), task)) + startShimmerLater(view, isGrid, shimmer) + tasks.add(task) + task.executeOnExecutor(executor, target) + view.invalidate() + } catch (e: Exception) { + Log_OC.d(TAG, "ThumbnailGenerationTask: ${e.message}") + } + } + + private fun newTask( + file: OCFile, + view: ImageView, + isGrid: Boolean, + shimmer: LoaderImageView? + ): ThumbnailGenerationTask = ThumbnailGenerationTask( + view, + storageManager.get(), + accountManager.user, + tasks, + isGrid, + file.remoteId + ).apply { + setListener(object : ThumbnailGenerationTask.Listener { + override fun onSuccess() = view.stopShimmer(shimmer) + + override fun onError() { + view.stopShimmer(shimmer) + view.setImageDrawable(file.mimeIcon()) + view.invalidate() + Log_OC.w(TAG, "setting thumbnail failed, using icon from mime type") + } + }) + } + + private fun startShimmerLater(view: ImageView, isGrid: Boolean, shimmer: LoaderImageView?) { + shimmer?.postDelayed({ + if (view.drawable != null) { + return@postDelayed + } + + if (isGrid) { + shimmer.resizeToGridCell(preferences.gridColumns) + } + + view.startShimmer(shimmer) + }, SHIMMER_DELAY_MS) + } + + private fun LoaderImageView.resizeToGridCell(columns: Float) { + val current = layoutParams as? FrameLayout.LayoutParams ?: return + val horizontalMargin = current.leftMargin + current.rightMargin + val screenWidth = context.resources.displayMetrics.widthPixels + val size = (screenWidth / columns - horizontalMargin).roundToInt() + + layoutParams = FrameLayout.LayoutParams(size, size).apply { + setMargins(current.leftMargin, current.topMargin, current.rightMargin, current.bottomMargin) + } + } + + private fun OCFile.mimeIcon(): Drawable? = MimeTypeUtil.getFileTypeIcon(mimeType, fileName, context, viewThemeUtils) + + private fun OCFile.placeholder(): Bitmap { + val drawable = mimeIcon() + ?: ResourcesCompat.getDrawable(context.resources, R.drawable.file_image, null) + ?: Color.GRAY.toDrawable() + val size = ThumbnailsCacheManager.getThumbnailDimension() + + return BitmapUtils.drawableToBitmap(drawable, size, size) + } + + private fun applyPngBackground(file: ServerFileInterface, view: ImageView) { + if (!MimeType.PNG.equals(file.mimeType, ignoreCase = true)) { + return + } + + view.setBackgroundColor(ContextCompat.getColor(context, R.color.bg_default)) + } +} diff --git a/app/src/main/java/com/owncloud/android/utils/overlay/OverlayManager.kt b/app/src/main/java/com/nextcloud/utils/thumbnail/FolderThumbnailGenerator.kt similarity index 89% rename from app/src/main/java/com/owncloud/android/utils/overlay/OverlayManager.kt rename to app/src/main/java/com/nextcloud/utils/thumbnail/FolderThumbnailGenerator.kt index b26a41d17c38..4e5f0547d273 100644 --- a/app/src/main/java/com/owncloud/android/utils/overlay/OverlayManager.kt +++ b/app/src/main/java/com/nextcloud/utils/thumbnail/FolderThumbnailGenerator.kt @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.utils.overlay +package com.nextcloud.utils.thumbnail import android.content.Context import android.view.View @@ -15,15 +15,14 @@ import com.elyeproj.loaderviewlibrary.LoaderImageView import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.di.Injectable import com.nextcloud.client.preferences.AppPreferences +import com.nextcloud.utils.extensions.stopShimmer import com.owncloud.android.datamodel.OCFile import com.owncloud.android.datamodel.SyncedFolderObserver -import com.owncloud.android.datamodel.SyncedFolderProvider -import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.MimeTypeUtil import com.owncloud.android.utils.theme.ViewThemeUtils import javax.inject.Inject -class OverlayManager @Inject constructor( +class FolderThumbnailGenerator @Inject constructor( private val preferences: AppPreferences, private val viewThemeUtils: ViewThemeUtils, private val context: Context, @@ -52,7 +51,7 @@ class OverlayManager @Inject constructor( fun setFolderThumbnail(folder: OCFile?, imageView: ImageView, loaderImageView: LoaderImageView?) { if (folder == null || !folder.isFolder) return - DisplayUtils.stopShimmer(loaderImageView, imageView) + imageView.stopShimmer(loaderImageView) val isAutoUploadFolder = SyncedFolderObserver.isAutoUploadFolder(folder, accountManager.user) diff --git a/app/src/main/java/com/nextcloud/utils/thumbnail/ThumbnailGenerator.kt b/app/src/main/java/com/nextcloud/utils/thumbnail/ThumbnailGenerator.kt new file mode 100644 index 000000000000..4f521e59f8f3 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/thumbnail/ThumbnailGenerator.kt @@ -0,0 +1,34 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.thumbnail + +import android.widget.ImageView +import com.elyeproj.loaderviewlibrary.LoaderImageView +import com.owncloud.android.datamodel.OCFile +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ThumbnailGenerator @Inject constructor( + val fileThumbnailGenerator: FileThumbnailGenerator, + val folderThumbnailGenerator: FolderThumbnailGenerator +) { + + @JvmOverloads + fun setThumbnail(ocFile: OCFile?, view: ImageView?, isGrid: Boolean = false, shimmer: LoaderImageView? = null) { + if (ocFile == null || view == null) { + return + } + + when { + ocFile.isOfflineOperation -> fileThumbnailGenerator.setOfflineOperationThumbnail(ocFile, view) + ocFile.isFolder -> folderThumbnailGenerator.setFolderThumbnail(ocFile, view, shimmer) + else -> fileThumbnailGenerator.setThumbnail(ocFile, view, isGrid, shimmer) + } + } +} diff --git a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java index 0e0fe3af993f..eac2dc488c9f 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java +++ b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java @@ -32,6 +32,7 @@ import com.owncloud.android.lib.resources.shares.ShareeUser; import com.owncloud.android.lib.resources.tags.Tag; import com.owncloud.android.utils.MimeType; +import com.owncloud.android.utils.sort.AlphanumericComparator; import java.io.File; import java.util.ArrayList; @@ -42,7 +43,6 @@ import androidx.annotation.Nullable; import androidx.core.content.FileProvider; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import com.owncloud.android.utils.sort.AlphanumericComparator; public class OCFile implements Parcelable, Comparable, ServerFileInterface { 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 66b215e733dc..43fe8bb1814d 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -38,6 +38,7 @@ import com.nextcloud.client.account.User; import com.nextcloud.client.network.ConnectivityService; import com.nextcloud.utils.BitmapExtensionsKt; +import com.nextcloud.utils.extensions.FileExtensionsKt; import com.nextcloud.utils.extensions.OCFileExtensionsKt; import com.nextcloud.utils.extensions.OwnCloudClientExtensionsKt; import com.owncloud.android.MainApp; @@ -89,7 +90,10 @@ public final class ThumbnailsCacheManager { private static final int READ_TIMEOUT = 40000; private static final int CONNECTION_TIMEOUT = 5000; + /** Cache key prefix {@code }; sized from {@link #getScreenDimension()}. */ public static final String PREFIX_RESIZED_IMAGE = "r"; + + /** Cache key prefix {@code }; square, sized from {@link #getThumbnailDimension()}. */ public static final String PREFIX_THUMBNAIL = "t"; private static final String TAG = ThumbnailsCacheManager.class.getSimpleName(); @@ -217,14 +221,12 @@ public static void removeFromCache(@Nullable OCFile file) { return; } - final var keys = new String[] { PREFIX_RESIZED_IMAGE + file.getRemoteId(), PREFIX_THUMBNAIL + file.getRemoteId() }; - synchronized (mThumbnailsDiskCacheLock) { if (mThumbnailCache == null) { return; } - for (String key: keys) { + for (String key: FileExtensionsKt.getThumbnailKeys(file)) { mThumbnailCache.removeKey(key); } } @@ -575,7 +577,7 @@ public void setListener(Listener listener){ private Bitmap doThumbnailFromOCFileInBackground() { Bitmap thumbnail; ServerFileInterface file = (ServerFileInterface) mFile; - String imageKey = PREFIX_THUMBNAIL + file.getRemoteId(); + String imageKey = FileExtensionsKt.getSmallThumbnailKey(file); boolean updateEnforced = (file instanceof OCFile && ((OCFile) file).isUpdateThumbnailNeeded()); @@ -622,7 +624,7 @@ private Bitmap doThumbnailFromOCFileInBackground() { // Check resized version in disk cache if still null if (thumbnail == null) { - String resizedImageKey = PREFIX_RESIZED_IMAGE + file.getRemoteId(); + String resizedImageKey = FileExtensionsKt.getBigThumbnailKey(file); Bitmap resizedImage = null; if (!updateEnforced) { @@ -637,8 +639,14 @@ private Bitmap doThumbnailFromOCFileInBackground() { } } + boolean serverHasPreview = !(file instanceof OCFile) || ((OCFile) file).isPreviewAvailable(); + + if (thumbnail == null && !serverHasPreview) { + Log_OC.d(TAG, "Server reports no preview for file: " + file.getFileName()); + } + // Download thumbnail from server if still null - if (thumbnail == null && mClient != null) { + if (thumbnail == null && mClient != null && serverHasPreview) { Log_OC.d(TAG, "Attempting to download thumbnail from server for file: " + file.getFileName()); GetMethod getMethod = null; @@ -718,7 +726,7 @@ private Bitmap doFileInBackground() { final String imageKey = Objects.requireNonNullElseGet(mImageKey, () -> String.valueOf(file.hashCode())); // local file should always generate a thumbnail - mImageKey = PREFIX_THUMBNAIL + mImageKey; + mImageKey = FileExtensionsKt.getSmallThumbnailKey(file); // Check disk cache in background thread Bitmap thumbnail = getBitmapFromDiskCache(imageKey); @@ -1190,7 +1198,7 @@ public static void generateResizedImage(OCFile file) { Point p = getScreenDimension(); int pxW = p.x; int pxH = p.y; - String imageKey = PREFIX_RESIZED_IMAGE + file.getRemoteId(); + String imageKey = FileExtensionsKt.getBigThumbnailKey(file); Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(file.getStoragePath(), pxW, pxH); @@ -1208,7 +1216,7 @@ public static void generateThumbnailFromOCFile(OCFile file, User user, Context c int pxW; int pxH; pxW = pxH = getThumbnailDimension(); - String imageKey = PREFIX_THUMBNAIL + file.getRemoteId(); + String imageKey = FileExtensionsKt.getSmallThumbnailKey(file); GetMethod getMethod = null; @@ -1275,7 +1283,7 @@ public static void setClient(OwnCloudClient client) { public static Bitmap doResizedImageInBackground(OCFile file, FileDataStorageManager storageManager) { Bitmap thumbnail; - String imageKey = PREFIX_RESIZED_IMAGE + file.getRemoteId(); + String imageKey = FileExtensionsKt.getBigThumbnailKey(file); // Check disk cache in background thread thumbnail = getBitmapFromDiskCache(imageKey); diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index be27f80f040c..d6a5d7461c11 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -1785,6 +1785,7 @@ private void updateOCFile(OCFile file, RemoteFile remoteFile) { file.setRemoteId(remoteFile.getRemoteId()); file.setPermissions(remoteFile.getPermissions()); file.setUploadTimestamp(remoteFile.getUploadTimestamp()); + file.setPreviewAvailable(remoteFile.isHasPreview()); } public interface OnRenameListener { diff --git a/app/src/main/java/com/owncloud/android/providers/DiskLruImageCacheFileProvider.java b/app/src/main/java/com/owncloud/android/providers/DiskLruImageCacheFileProvider.java index 2ec6a718abbc..97b2f0188979 100644 --- a/app/src/main/java/com/owncloud/android/providers/DiskLruImageCacheFileProvider.java +++ b/app/src/main/java/com/owncloud/android/providers/DiskLruImageCacheFileProvider.java @@ -19,6 +19,7 @@ import com.nextcloud.client.account.User; import com.nextcloud.client.account.UserAccountManager; +import com.nextcloud.utils.extensions.FileExtensionsKt; import com.owncloud.android.MainApp; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; @@ -62,13 +63,11 @@ public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) thr } public static ParcelFileDescriptor getParcelFileDescriptorForOCFile(OCFile ocFile) throws FileNotFoundException { - Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + ocFile.getRemoteId()); + Bitmap thumbnail = FileExtensionsKt.getBigThumbnail(ocFile); // fallback to thumbnail if (thumbnail == null) { - thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_THUMBNAIL + ocFile.getRemoteId()); + thumbnail = FileExtensionsKt.getSmallThumbnail(ocFile); } // fallback to default image diff --git a/app/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java b/app/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java index 2097e025e23a..8aacf15a18b3 100644 --- a/app/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java +++ b/app/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java @@ -35,6 +35,7 @@ import com.nextcloud.client.preferences.AppPreferencesImpl; import com.nextcloud.client.utils.HashUtil; import com.nextcloud.utils.extensions.ContextExtensionsKt; +import com.nextcloud.utils.extensions.FileExtensionsKt; import com.nextcloud.utils.fileNameValidator.FileNameValidator; import com.owncloud.android.MainApp; import com.owncloud.android.R; @@ -382,8 +383,7 @@ public AssetFileDescriptor openDocumentThumbnail(String documentId, Document document = toDocument(documentId); OCFile file = document.getFile(); - boolean exists = ThumbnailsCacheManager.containsBitmap(ThumbnailsCacheManager.PREFIX_THUMBNAIL - + file.getRemoteId()); + boolean exists = ThumbnailsCacheManager.containsBitmap(FileExtensionsKt.getSmallThumbnailKey(file)); if (!exists) { ThumbnailsCacheManager.generateThumbnailFromOCFile(file, document.getUser(), getContext()); } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java b/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java index 4721bbbe434f..50c15d7675af 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java @@ -24,6 +24,7 @@ import com.google.android.material.snackbar.Snackbar; import com.nextcloud.android.common.ui.theme.utils.ColorRole; import com.nextcloud.client.account.User; +import com.nextcloud.utils.extensions.FileExtensionsKt; import com.nextcloud.utils.extensions.IntentExtensionsKt; import com.owncloud.android.R; import com.owncloud.android.databinding.RichdocumentsWebviewBinding; @@ -258,8 +259,7 @@ protected void setThumbnailView(final User user) { } else { if ((MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file)) && file.getRemoteId() != null) { // Thumbnail in cache? - Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.getRemoteId()); + Bitmap thumbnail = FileExtensionsKt.getSmallThumbnail(file); if (thumbnail != null && !file.isUpdateThumbnailNeeded()) { if (MimeTypeUtil.isVideo(file)) { diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.kt index d03e13f708a6..b60f3997eb6d 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.kt @@ -34,7 +34,7 @@ import com.owncloud.android.ui.fragment.FileDetailSharingFragment import com.owncloud.android.ui.fragment.FileDetailsSharingProcessFragment import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.MimeTypeUtil -import com.owncloud.android.utils.overlay.OverlayManager +import com.nextcloud.utils.thumbnail.ThumbnailGenerator import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -48,7 +48,7 @@ class ShareActivity : lateinit var syncedFolderProvider: SyncedFolderProvider @Inject - lateinit var overlayManager: OverlayManager + lateinit var thumbnailGenerator: ThumbnailGenerator override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -139,19 +139,7 @@ class ShareActivity : ) ) } else { - DisplayUtils.setThumbnail( - file, - binding.shareFileIcon, - user, - storageManager, - mutableListOf(), - false, - this, - null, - preferences, - viewThemeUtils, - overlayManager - ) + thumbnailGenerator.setThumbnail(file, binding.shareFileIcon) } } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.kt index b6ef36356a6b..33a63a157dc7 100755 --- a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.kt @@ -41,6 +41,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation import com.owncloud.android.operations.CheckCurrentCredentialsOperation +import com.nextcloud.utils.thumbnail.ThumbnailGenerator import com.owncloud.android.operations.factory.UploadFileOperationFactory import com.owncloud.android.ui.adapter.uploadList.UploadListAdapter import com.owncloud.android.ui.adapter.uploadList.helper.ConflictHandlingResult @@ -73,6 +74,8 @@ class UploadListActivity : @Inject lateinit var uploadFileOperationFactory: UploadFileOperationFactory + @Inject lateinit var thumbnailGenerator: ThumbnailGenerator + private var uploadWarningCard: UploadWarningCard? = null private var swipeListRefreshLayout: SwipeRefreshLayout? = null @@ -112,13 +115,15 @@ class UploadListActivity : adapterHelper = UploadListAdapterHelper(this) uploadListAdapter = UploadListAdapter( this, + storageManager, uploadsStorageManager, userAccountManager, connectivityService, powerManagementService, viewThemeUtils, this, - adapterHelper + adapterHelper, + thumbnailGenerator ) binding?.autoUploadBatterySaverWarningCard?.let { diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt index 41df5a06cb99..af897486404f 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt @@ -24,6 +24,7 @@ import com.afollestad.sectionedrecyclerview.SectionedViewHolder import com.nextcloud.client.account.User import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.utils.extensions.toGalleryItems +import com.nextcloud.utils.thumbnail.ThumbnailGenerator import com.owncloud.android.databinding.GalleryHeaderBinding import com.owncloud.android.databinding.GalleryRowBinding import com.owncloud.android.datamodel.FileDataStorageManager @@ -46,7 +47,8 @@ class GalleryAdapter( transferServiceGetter: ComponentsGetter, private val viewThemeUtils: ViewThemeUtils, var columns: Int, - private val defaultThumbnailSize: Int + private val defaultThumbnailSize: Int, + private val thumbnailGenerator: ThumbnailGenerator ) : SectionedRecyclerViewAdapter(), CommonOCFileListAdapterInterface, PopupTextProvider { @@ -83,7 +85,6 @@ class GalleryAdapter( user, storageManager, false, - preferences, true, transferServiceGetter, showMetadata = false, @@ -231,7 +232,7 @@ class GalleryAdapter( override fun isMultiSelect(): Boolean = ocFileListDelegate.isMultiSelect override fun cancelAllPendingTasks() { - ocFileListDelegate.cancelAllPendingTasks() + thumbnailGenerator.fileThumbnailGenerator.cancelPendingTasks() } override fun addCheckedFile(file: OCFile) { diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java index 72208f34f20c..a99327456c5b 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java @@ -26,6 +26,7 @@ import com.nextcloud.android.common.ui.theme.utils.ColorRole; import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.utils.FileHelper; +import com.nextcloud.utils.extensions.FileExtensionsKt; import com.owncloud.android.R; import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.lib.common.utils.Log_OC; @@ -310,9 +311,7 @@ public static void setThumbnail(File file, // get Thumbnail if file is image if (MimeTypeUtil.isImage(file)) { // Thumbnail in Cache? - Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.hashCode() - ); + Bitmap thumbnail = FileExtensionsKt.getSmallThumbnail(file); if (thumbnail != null) { thumbnailView.setImageBitmap(thumbnail); } else { diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java index e68ba687ec1f..0f07a706c60c 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -35,6 +35,7 @@ import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.model.OfflineOperationType; import com.nextcloud.utils.e2ee.E2EVersionHelper; +import com.nextcloud.utils.extensions.ImageViewExtensionsKt; import com.nextcloud.utils.extensions.ViewExtensionsKt; import com.nextcloud.utils.mdm.MDMConfig; import com.owncloud.android.MainApp; @@ -67,7 +68,7 @@ import com.owncloud.android.utils.FileSortOrder; import com.owncloud.android.utils.FileStorageUtils; import com.owncloud.android.utils.MimeTypeUtil; -import com.owncloud.android.utils.overlay.OverlayManager; +import com.nextcloud.utils.thumbnail.ThumbnailGenerator; import com.owncloud.android.utils.theme.CapabilityUtils; import com.owncloud.android.utils.theme.ViewThemeUtils; @@ -135,7 +136,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter recommendedFiles = new ArrayList<>(); private RecommendedFilesAdapter recommendedFilesAdapter; private final OCFileListAdapterHelper helper = new OCFileListAdapterHelper(); - private final OverlayManager overlayManager; + private final ThumbnailGenerator thumbnailGenerator; public OCFileListAdapter( Activity activity, @@ -147,8 +148,8 @@ public OCFileListAdapter( boolean argHideItemOptions, boolean gridView, final ViewThemeUtils viewThemeUtils, - OverlayManager overlayManager) { - this.overlayManager = overlayManager; + ThumbnailGenerator thumbnailGenerator) { + this.thumbnailGenerator = thumbnailGenerator; this.ocFileListFragmentInterface = ocFileListFragmentInterface; this.activity = activity; this.preferences = preferences; @@ -179,7 +180,6 @@ public OCFileListAdapter( user, mStorageManager, hideItemOptions, - preferences, gridView, transferServiceGetter, true, @@ -494,7 +494,7 @@ public void bindRecommendedFilesHolder(OCFileListRecommendedItemViewHolder holde } private void bindHolder(@NonNull RecyclerView.ViewHolder holder, ListViewHolder viewHolder, OCFile file) { - ocFileListDelegate.bindViewHolder(viewHolder, file, currentDirectory, searchType, overlayManager); + ocFileListDelegate.bindViewHolder(viewHolder, file, currentDirectory, searchType, thumbnailGenerator); if (holder instanceof ListItemViewHolder itemViewHolder) { bindListItemViewHolder(itemViewHolder, file); @@ -982,7 +982,7 @@ public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { super.onViewRecycled(holder); if (holder instanceof ListViewHolder listViewHolder) { LoaderImageView thumbnailShimmer = listViewHolder.getShimmerThumbnail(); - DisplayUtils.stopShimmer(thumbnailShimmer, listViewHolder.getThumbnail()); + ImageViewExtensionsKt.stopShimmer(listViewHolder.getThumbnail(), thumbnailShimmer); } } @@ -1009,7 +1009,7 @@ public void setHighlightedItem(OCFile file) { } public void cancelAllPendingTasks() { - ocFileListDelegate.cancelAllPendingTasks(); + thumbnailGenerator.getFileThumbnailGenerator().cancelPendingTasks(); } public void setGridView(boolean bool) { 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 d871787a4d96..64e317ad2078 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 @@ -18,11 +18,14 @@ 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.OCFileUtils +import com.nextcloud.utils.extensions.getBigThumbnail +import com.nextcloud.utils.extensions.getSmallThumbnail import com.nextcloud.utils.extensions.makeRounded import com.nextcloud.utils.extensions.setVisibleIf +import com.nextcloud.utils.extensions.stopShimmer import com.nextcloud.utils.mdm.MDMConfig +import com.nextcloud.utils.thumbnail.ThumbnailGenerator import com.owncloud.android.R import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile @@ -33,10 +36,8 @@ import com.owncloud.android.ui.activity.ComponentsGetter import com.owncloud.android.ui.activity.FolderPickerActivity import com.owncloud.android.ui.fragment.SearchType import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface -import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.EncryptionUtils import com.owncloud.android.utils.MimeTypeUtil -import com.owncloud.android.utils.overlay.OverlayManager import com.owncloud.android.utils.theme.ViewThemeUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -53,7 +54,6 @@ class OCFileListDelegate( private val user: User, private val storageManager: FileDataStorageManager, private val hideItemOptions: Boolean, - private val preferences: AppPreferences, private val gridView: Boolean, private val transferServiceGetter: ComponentsGetter, private val showMetadata: Boolean, @@ -65,7 +65,6 @@ class OCFileListDelegate( private val checkedFiles: MutableSet = HashSet() private var highlightedItem: OCFile? = null var isMultiSelect = false - private val asyncTasks: MutableList = ArrayList() private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val galleryImageGenerationJob = GalleryImageGenerationJob(user, storageManager) @@ -113,8 +112,7 @@ class OCFileListDelegate( imageView.tag = file.fileId // set placeholder before async job - val cacheKey = ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId - val cachedBitmap = ThumbnailsCacheManager.getBitmapFromDiskCache(cacheKey) + val cachedBitmap = file.getBigThumbnail() ?: file.getSmallThumbnail() if (cachedBitmap != null) { val overlay = if (MimeTypeUtil.isVideo(file)) { ThumbnailsCacheManager.addVideoOverlay(cachedBitmap, context) @@ -136,7 +134,7 @@ class OCFileListDelegate( if (imageView.tag == file.fileId) { Log_OC.d(tag, "setGalleryImage.onSuccess()") galleryRowHolder.binding.rowLayout.invalidate() - DisplayUtils.stopShimmer(shimmer, imageView) + imageView.stopShimmer(shimmer) } } @@ -150,7 +148,7 @@ class OCFileListDelegate( override fun onError() { if (imageView.tag == file.fileId) { Log_OC.d(tag, "setGalleryImage.onError()") - DisplayUtils.stopShimmer(shimmer, imageView) + imageView.stopShimmer(shimmer) } } } @@ -179,34 +177,13 @@ class OCFileListDelegate( } } - fun setThumbnail( - thumbnail: ImageView, - shimmerThumbnail: LoaderImageView?, - file: OCFile, - overlayManager: OverlayManager - ) { - DisplayUtils.setThumbnail( - file, - thumbnail, - user, - storageManager, - asyncTasks, - gridView, - context, - shimmerThumbnail, - preferences, - viewThemeUtils, - overlayManager - ) - } - @Suppress("MagicNumber") fun bindViewHolder( viewHolder: ListViewHolder, file: OCFile, currentDirectory: OCFile?, searchType: SearchType?, - overlayManager: OverlayManager + thumbnailGenerator: ThumbnailGenerator ) { // thumbnail viewHolder.imageFileName?.text = file.fileName @@ -221,7 +198,8 @@ class OCFileListDelegate( viewHolder.thumbnail.setPadding(padding, padding, padding, padding) } } - setThumbnail(viewHolder.thumbnail, viewHolder.shimmerThumbnail, file, overlayManager) + + thumbnailGenerator.setThumbnail(file, viewHolder.thumbnail, gridView, viewHolder.shimmerThumbnail) // item layout + click listeners bindGridItemLayout(file, viewHolder) @@ -428,17 +406,6 @@ class OCFileListDelegate( } } - fun cancelAllPendingTasks() { - for (task in asyncTasks) { - task.cancel(true) - if (task.getMethod != null) { - Log_OC.d(TAG, "cancel: abort get method directly") - task.getMethod.abort() - } - } - asyncTasks.clear() - } - fun setShowShareAvatar(bool: Boolean) { showShareAvatar = bool } @@ -453,9 +420,6 @@ class OCFileListDelegate( Log_OC.e(TAG, "exception: ", e) } - // cancel async tasks from ThumbnailsCacheManager - cancelAllPendingTasks() - Log_OC.d(TAG, "background jobs cancelled") } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java index 3f3e15870adb..3f281d0704d9 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java @@ -20,6 +20,7 @@ import com.nextcloud.android.common.ui.theme.utils.ColorRole; import com.nextcloud.client.account.User; import com.nextcloud.client.preferences.AppPreferences; +import com.nextcloud.utils.extensions.FileExtensionsKt; import com.nextcloud.utils.extensions.ViewExtensionsKt; import com.owncloud.android.R; import com.owncloud.android.databinding.ListFooterBinding; @@ -256,10 +257,7 @@ private void setThumbnail(TrashbinFile file, ImageView thumbnailView) { } else { if ((MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file)) && file.getRemoteId() != null) { // Thumbnail in cache? - Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.getRemoteId() - ); - + Bitmap thumbnail = FileExtensionsKt.getSmallThumbnail(file); if (thumbnail != null) { if (MimeTypeUtil.isVideo(file)) { Bitmap withOverlay = ThumbnailsCacheManager.addVideoOverlay(thumbnail, context); diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchCurrentDirItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchCurrentDirItemViewHolder.kt index d4bc6d68114a..3b805ad2a5bb 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchCurrentDirItemViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchCurrentDirItemViewHolder.kt @@ -10,28 +10,21 @@ package com.owncloud.android.ui.adapter import android.content.Context import android.view.View import com.afollestad.sectionedrecyclerview.SectionedViewHolder -import com.nextcloud.client.account.User -import com.nextcloud.client.preferences.AppPreferences +import com.nextcloud.utils.thumbnail.ThumbnailGenerator import com.owncloud.android.databinding.UnifiedSearchCurrentDirectoryItemBinding import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile import com.owncloud.android.ui.interfaces.UnifiedSearchCurrentDirItemAction -import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.FileStorageUtils -import com.owncloud.android.utils.overlay.OverlayManager -import com.owncloud.android.utils.theme.ViewThemeUtils @Suppress("LongParameterList") class UnifiedSearchCurrentDirItemViewHolder( val binding: UnifiedSearchCurrentDirectoryItemBinding, val context: Context, - private val viewThemeUtils: ViewThemeUtils, private val storageManager: FileDataStorageManager, private val isRTL: Boolean, - private val user: User, - private val appPreferences: AppPreferences, private val action: UnifiedSearchCurrentDirItemAction, - private val overlayManager: OverlayManager + private val thumbnailGenerator: ThumbnailGenerator ) : SectionedViewHolder(binding.unifiedSearchCurrentDirItemLayout) { fun bind(file: OCFile) { @@ -48,19 +41,7 @@ class UnifiedSearchCurrentDirItemViewHolder( binding.filename.text = filename } - DisplayUtils.setThumbnail( - file, - binding.thumbnail, - user, - storageManager, - listOf(), - false, - context, - binding.thumbnailShimmer, - appPreferences, - viewThemeUtils, - overlayManager - ) + thumbnailGenerator.setThumbnail(file, binding.thumbnail, shimmer = binding.thumbnailShimmer) binding.more.setOnClickListener { action.openFile(file.decryptedRemotePath, true) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt index 97c32785c018..36f1ad40db83 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt @@ -30,9 +30,8 @@ import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile import com.owncloud.android.lib.common.SearchResultEntry import com.owncloud.android.ui.interfaces.UnifiedSearchListInterface -import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.MimeTypeUtil -import com.owncloud.android.utils.overlay.OverlayManager +import com.nextcloud.utils.thumbnail.ThumbnailGenerator import com.owncloud.android.utils.theme.ViewThemeUtils @Suppress("LongParameterList") @@ -44,7 +43,7 @@ class UnifiedSearchItemViewHolder( private val filesAction: FilesAction, val context: Context, private val viewThemeUtils: ViewThemeUtils, - private val overlayManager: OverlayManager, + private val thumbnailGenerator: ThumbnailGenerator, private val user: User, private val preferences: AppPreferences ) : SectionedViewHolder(binding.root) { @@ -102,7 +101,7 @@ class UnifiedSearchItemViewHolder( setImageDrawable(ContextCompat.getDrawable(context, R.drawable.folder)) viewThemeUtils.platform.colorImageView(this, ColorRole.PRIMARY) } - overlayManager.setFolderOverlayIcon(file, binding.thumbnailOverlayIcon) + thumbnailGenerator.folderThumbnailGenerator.setFolderOverlayIcon(file, binding.thumbnailOverlayIcon) } private fun bindLocalFileThumbnail(file: OCFile) { @@ -114,18 +113,7 @@ class UnifiedSearchItemViewHolder( ImageViewCompat.setImageTintList(this, null) } } else { - DisplayUtils.setThumbnailFromCache( - file, - binding.thumbnail, - storageManager, - listOf(), - false, - binding.thumbnailShimmer, - user, - preferences, - context, - viewThemeUtils - ) + thumbnailGenerator.setThumbnail(file, binding.thumbnail, shimmer = binding.thumbnailShimmer) } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapter.kt index 6224f610a154..c127c34fad7f 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapter.kt @@ -32,7 +32,7 @@ import com.owncloud.android.ui.interfaces.UnifiedSearchCurrentDirItemAction import com.owncloud.android.ui.interfaces.UnifiedSearchListInterface import com.owncloud.android.ui.unifiedsearch.UnifiedSearchSection import com.owncloud.android.utils.DisplayUtils -import com.owncloud.android.utils.overlay.OverlayManager +import com.nextcloud.utils.thumbnail.ThumbnailGenerator import com.owncloud.android.utils.theme.ViewThemeUtils /** @@ -49,7 +49,7 @@ class UnifiedSearchListAdapter( private val viewThemeUtils: ViewThemeUtils, private val appPreferences: AppPreferences, private val currentDirItemAction: UnifiedSearchCurrentDirItemAction, - private val overlayManager: OverlayManager + private val thumbnailGenerator: ThumbnailGenerator ) : SectionedRecyclerViewAdapter() { companion object { private const val VIEW_TYPE_EMPTY = Int.MAX_VALUE @@ -137,7 +137,7 @@ class UnifiedSearchListAdapter( filesAction, context, viewThemeUtils, - overlayManager, + thumbnailGenerator, user, appPreferences ) @@ -149,13 +149,10 @@ class UnifiedSearchListAdapter( UnifiedSearchCurrentDirItemViewHolder( binding, context, - viewThemeUtils, storageManager, isRTL, - user, - appPreferences, currentDirItemAction, - overlayManager + thumbnailGenerator ) } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt index c218bea7d16b..e1f34a37e5f9 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt @@ -15,7 +15,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.PopupMenu -import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter import com.afollestad.sectionedrecyclerview.SectionedViewHolder @@ -30,12 +29,12 @@ import com.nextcloud.utils.extensions.isLastResultConflictError import com.nextcloud.utils.extensions.setVisibleIf import com.nextcloud.utils.extensions.sortedByUploadOrder import com.nextcloud.utils.extensions.toFile +import com.nextcloud.utils.thumbnail.ThumbnailGenerator import com.owncloud.android.R import com.owncloud.android.databinding.UploadListHeaderBinding import com.owncloud.android.databinding.UploadListItemBinding import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile -import com.owncloud.android.datamodel.ThumbnailsCacheManager import com.owncloud.android.datamodel.UploadsStorageManager import com.owncloud.android.db.OCUpload import com.owncloud.android.db.UploadResult @@ -47,7 +46,6 @@ import com.owncloud.android.ui.adapter.uploadList.helper.UploadListItemOnClick import com.owncloud.android.ui.adapter.uploadList.model.UploadListSection import com.owncloud.android.ui.adapter.uploadList.model.UploadListType import com.owncloud.android.utils.DisplayUtils -import com.owncloud.android.utils.MimeTypeUtil import com.owncloud.android.utils.theme.ViewThemeUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -67,13 +65,15 @@ import java.util.function.Consumer ) class UploadListAdapter( private val activity: FileActivity, + private val fileDataStorageManager: FileDataStorageManager, private val uploadsStorageManager: UploadsStorageManager, private val accountManager: UserAccountManager, private val connectivityService: ConnectivityService, private val powerManagementService: PowerManagementService, private val viewThemeUtils: ViewThemeUtils, private val itemOnClick: UploadListItemOnClick, - private val helper: UploadListAdapterHelper + private val helper: UploadListAdapterHelper, + private val thumbnailGenerator: ThumbnailGenerator ) : SectionedRecyclerViewAdapter() { private val uploadListSections = UploadListSection.sections() @@ -89,7 +89,9 @@ class UploadListAdapter( internal class HeaderViewHolder(val binding: UploadListHeaderBinding) : SectionedViewHolder(binding.root) - internal class ItemViewHolder(val binding: UploadListItemBinding) : SectionedViewHolder(binding.root) + internal class ItemViewHolder(val binding: UploadListItemBinding) : SectionedViewHolder(binding.root) { + var boundUploadId: Long = -1 + } override fun getSectionCount(): Int = uploadListSections.size @@ -487,111 +489,35 @@ class UploadListAdapter( private fun bindItemThumbnail(holder: ItemViewHolder, item: OCUpload) { holder.binding.thumbnail.setImageResource(R.drawable.file) + holder.boundUploadId = item.uploadId - val fakeFile = OCFile(item.remotePath).apply { - setStoragePath(item.localPath) - mimeType = item.mimeType - } - - val allowedToCreateNewThumbnail = - ThumbnailsCacheManager.cancelPotentialThumbnailWork(fakeFile, holder.binding.thumbnail) - - val optionalUser = accountManager.getUser(item.accountName) - val fileName = File(item.remotePath).name.takeIf { it.isNotEmpty() } ?: File.separator + activity.lifecycleScope.launch { + val ocFile = withContext(Dispatchers.IO) { item.toOCFile() } - when { - MimeTypeUtil.isImage(fakeFile) && fakeFile.remoteId != null && - item.uploadStatus == UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED -> - bindRemoteThumbnail(holder, item, fakeFile, allowedToCreateNewThumbnail) - - MimeTypeUtil.isImage(fakeFile) -> - bindLocalThumbnail(holder, item, allowedToCreateNewThumbnail) - - optionalUser.isPresent -> { - val icon = MimeTypeUtil.getFileTypeIcon(item.mimeType, fileName, activity, viewThemeUtils) - holder.binding.thumbnail.setImageDrawable(icon) - } - } - } - - private fun bindRemoteThumbnail( - holder: ItemViewHolder, - item: OCUpload, - fakeFile: OCFile, - allowedToCreateNewThumbnail: Boolean - ) { - val cacheKey = fakeFile.remoteId.toString() - var thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(cacheKey) - - if (thumbnail != null && !fakeFile.isUpdateThumbnailNeeded) { - holder.binding.thumbnail.setImageBitmap(thumbnail) - } else if (allowedToCreateNewThumbnail) { - val user = activity.user - if (user.isPresent) { - val task = ThumbnailsCacheManager.ThumbnailGenerationTask( - holder.binding.thumbnail, - activity.storageManager, - user.get() - ) - thumbnail = thumbnail ?: if (MimeTypeUtil.isVideo(fakeFile)) { - ThumbnailsCacheManager.mDefaultVideo - } else { - ThumbnailsCacheManager.mDefaultImg - } - holder.binding.thumbnail.setImageDrawable( - ThumbnailsCacheManager.AsyncThumbnailDrawable(activity.resources, thumbnail, task) - ) - task.execute(ThumbnailsCacheManager.ThumbnailGenerationTaskObject(fakeFile, null)) + if (holder.boundUploadId != item.uploadId) { + return@launch } - } - if (item.mimeType == "image/png") { - holder.binding.thumbnail.setBackgroundColor(ContextCompat.getColor(activity, R.color.bg_default)) + bindItemThumbnail(holder, ocFile) } } - private fun bindLocalThumbnail(holder: ItemViewHolder, item: OCUpload, allowedToCreateNewThumbnail: Boolean) { - val file = File(item.localPath) - val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(file.hashCode().toString()) - - if (thumbnail != null) { - holder.binding.thumbnail.setImageBitmap(thumbnail) - } else if (allowedToCreateNewThumbnail) { - getThumbnailFromFileTypeAndSetIcon(item.localPath, holder) - val task = ThumbnailsCacheManager.ThumbnailGenerationTask(holder.binding.thumbnail) - val defaultThumbnail = if (MimeTypeUtil.isVideo(file)) { - ThumbnailsCacheManager.mDefaultVideo - } else { - ThumbnailsCacheManager.mDefaultImg - } - val asyncDrawable = - ThumbnailsCacheManager.AsyncThumbnailDrawable(activity.resources, defaultThumbnail, task) - task.execute(ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, null)) - task.setListener(object : ThumbnailsCacheManager.ThumbnailGenerationTask.Listener { - override fun onSuccess() { - holder.binding.thumbnail.setImageDrawable(asyncDrawable) - } - - override fun onError() { - getThumbnailFromFileTypeAndSetIcon(item.localPath, holder) - } - }) - Log_OC.v(TAG, "Executing task to generate a new thumbnail") + private fun OCUpload.toOCFile(): OCFile = + fileDataStorageManager.getFileByDecryptedRemotePath(remotePath) ?: OCFile(remotePath).apply { + setStoragePath(localPath) + mimeType = this@toOCFile.mimeType } - if (item.mimeType.equals("image/png", ignoreCase = true)) { - holder.binding.thumbnail.setBackgroundColor(ContextCompat.getColor(activity, R.color.bg_default)) - } + private fun bindItemThumbnail(holder: ItemViewHolder, ocFile: OCFile) { + holder.binding.thumbnail.tag = ocFile.fileId + + thumbnailGenerator.setThumbnail(ocFile, holder.binding.thumbnail) } + // endregion override fun onBindFooterViewHolder(holder: SectionedViewHolder?, section: Int) = Unit - private fun getThumbnailFromFileTypeAndSetIcon(localPath: String?, itemViewHolder: ItemViewHolder) { - val drawable = MimeTypeUtil.getIcon(localPath, activity, viewThemeUtils) ?: return - itemViewHolder.binding.thumbnail.setImageDrawable(drawable) - } - private fun showItemConflictPopup(item: OCUpload, view: View) { PopupMenu(activity, view).apply { inflate(R.menu.upload_list_item_file_conflict) diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.kt b/app/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.kt index db515bb7aa30..ef8eebc6cecd 100644 --- a/app/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.kt +++ b/app/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.kt @@ -26,14 +26,12 @@ import com.owncloud.android.databinding.ConflictResolveDialogBinding import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile import com.owncloud.android.datamodel.SyncedFolderProvider -import com.owncloud.android.datamodel.ThumbnailsCacheManager.ThumbnailGenerationTask -import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.ui.adapter.LocalFileListAdapter import com.owncloud.android.ui.dialog.parcel.ConflictDialogData import com.owncloud.android.ui.dialog.parcel.ConflictFileData import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.MimeTypeUtil -import com.owncloud.android.utils.overlay.OverlayManager +import com.nextcloud.utils.thumbnail.ThumbnailGenerator import com.owncloud.android.utils.theme.ViewThemeUtils import java.io.File import javax.inject.Inject @@ -47,7 +45,6 @@ class ConflictsResolveDialog : private lateinit var binding: ConflictResolveDialogBinding var listener: OnConflictDecisionMadeListener? = null - private val asyncTasks: MutableList = ArrayList() private var positiveButton: MaterialButton? = null private var data: ConflictDialogData? = null @@ -65,7 +62,7 @@ class ConflictsResolveDialog : lateinit var fileDataStorageManager: FileDataStorageManager @Inject - lateinit var overlayManager: OverlayManager + lateinit var thumbnailGenerator: ThumbnailGenerator enum class Decision { CANCEL, @@ -225,19 +222,7 @@ class ConflictsResolveDialog : viewThemeUtils ) - DisplayUtils.setThumbnail( - rightDataFile, - binding.rightThumbnail, - user, - fileDataStorageManager, - asyncTasks, - false, - context, - null, - syncedFolderProvider.preferences, - viewThemeUtils, - overlayManager - ) + thumbnailGenerator.setThumbnail(rightDataFile, binding.rightThumbnail) } private fun setOnClickListeners() { @@ -280,18 +265,6 @@ class ConflictsResolveDialog : fun conflictDecisionMade(decision: Decision?) } - override fun onStop() { - super.onStop() - - asyncTasks.forEach { - it.cancel(true) - Log_OC.d(this, "cancel: abort get method directly") - it.getMethod?.abort() - } - - asyncTasks.clear() - } - companion object { private const val ARG_CONFLICT_DATA = "CONFLICT_DATA" private const val ARG_LEFT_FILE = "LEFT_FILE" diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java index 4774dfc5f996..c45076cd078e 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -36,6 +36,7 @@ import com.nextcloud.ui.fileactions.FileActionsBottomSheet; import com.nextcloud.utils.MenuUtils; import com.nextcloud.utils.extensions.BundleExtensionsKt; +import com.nextcloud.utils.extensions.FileExtensionsKt; import com.nextcloud.utils.mdm.MDMConfig; import com.owncloud.android.MainApp; import com.owncloud.android.R; @@ -666,17 +667,14 @@ private void setFilePreview(OCFile file) { Bitmap resizedImage; if (toolbarActivity != null && MimeTypeUtil.isImage(file)) { - String tagId = ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + getFile().getRemoteId(); - resizedImage = ThumbnailsCacheManager.getBitmapFromDiskCache(tagId); + resizedImage = FileExtensionsKt.getBigThumbnail(file); if (resizedImage != null && !file.isUpdateThumbnailNeeded()) { toolbarActivity.setPreviewImageBitmap(resizedImage); previewLoaded = true; } else { // show thumbnail while loading resized image - Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_THUMBNAIL + getFile().getRemoteId()); - + Bitmap thumbnail = FileExtensionsKt.getSmallThumbnail(file); if (thumbnail != null) { toolbarActivity.setPreviewImageBitmap(thumbnail); } else { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.kt index 86d298bb69f3..3b12ee6bf401 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.kt @@ -194,7 +194,8 @@ class GalleryFragment : mContainerActivity, viewThemeUtils, this.columnsCount, - ThumbnailsCacheManager.getThumbnailDimension() + ThumbnailsCacheManager.getThumbnailDimension(), + thumbnailGenerator ) adapter?.setHasStableIds(true) setRecyclerViewAdapter(adapter) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 1ce195ff29b8..c84ed7a12c84 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -121,7 +121,7 @@ import com.owncloud.android.utils.FileStorageUtils; import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.PermissionUtil; -import com.owncloud.android.utils.overlay.OverlayManager; +import com.nextcloud.utils.thumbnail.ThumbnailGenerator; import com.owncloud.android.utils.theme.ThemeUtils; import org.apache.commons.httpclient.HttpStatus; @@ -209,7 +209,7 @@ public class OCFileListFragment extends ExtendedListFragment implements @Inject ShortcutUtil shortcutUtil; @Inject SyncedFolderProvider syncedFolderProvider; @Inject AppScanOptionalFeature appScanOptionalFeature; - @Inject OverlayManager overlayManager; + @Inject ThumbnailGenerator thumbnailGenerator; protected FileFragment.ContainerActivity mContainerActivity; @@ -483,7 +483,7 @@ protected void setAdapter(Bundle args) { hideItemOptions, isGridViewPreferred, viewThemeUtils, - overlayManager + thumbnailGenerator ); setRecyclerViewAdapter(mAdapter); diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt index 31abab9602fc..ae30732e9729 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt @@ -62,7 +62,7 @@ import com.owncloud.android.ui.unifiedsearch.UnifiedSearchViewModel import com.owncloud.android.ui.unifiedsearch.filterOutHiddenFiles import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.PermissionUtil -import com.owncloud.android.utils.overlay.OverlayManager +import com.nextcloud.utils.thumbnail.ThumbnailGenerator import com.owncloud.android.utils.theme.ViewThemeUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -108,7 +108,7 @@ class UnifiedSearchFragment : } @Inject - lateinit var overlayManager: OverlayManager + lateinit var thumbnailGenerator: ThumbnailGenerator @Inject lateinit var vmFactory: ViewModelFactory @@ -382,7 +382,7 @@ class UnifiedSearchFragment : viewThemeUtils, appPreferences, this@UnifiedSearchFragment, - overlayManager + thumbnailGenerator ) adapter.shouldShowFooters(true) diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt index 9326613dd511..e180a0cdef07 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt @@ -50,7 +50,9 @@ import com.nextcloud.client.network.ConnectivityService import com.nextcloud.ui.fileactions.FileAction import com.nextcloud.ui.fileactions.FileActionsBottomSheet.Companion.newInstance import com.nextcloud.utils.extensions.clickWithDebounce +import com.nextcloud.utils.extensions.getBigThumbnailKey import com.nextcloud.utils.extensions.getParcelableArgument +import com.nextcloud.utils.extensions.getSmallThumbnail import com.owncloud.android.MainApp import com.owncloud.android.R import com.owncloud.android.databinding.PreviewImageFragmentBinding @@ -241,7 +243,7 @@ class PreviewImageFragment : binding.image.visibility = View.GONE binding.emptyListProgress.visibility = View.VISIBLE - var thumbnail = getThumbnailBitmap(file) + var thumbnail = file.getSmallThumbnail() if (thumbnail != null) { binding.shimmer.visibility = View.VISIBLE binding.shimmerThumbnail.setImageBitmap(thumbnail) @@ -313,11 +315,11 @@ class PreviewImageFragment : while (i < 3 && cachedImage == null) { try { cachedImage = ThumbnailsCacheManager.getScaledBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId, + file.getBigThumbnailKey(), scaledWidth, scaledHeight ) - } catch (e: OutOfMemoryError) { + } catch (_: OutOfMemoryError) { scaledWidth /= 2 scaledHeight /= 2 } @@ -327,9 +329,6 @@ class PreviewImageFragment : return cachedImage } - private fun getThumbnailBitmap(file: OCFile): Bitmap? = - ThumbnailsCacheManager.getBitmapFromDiskCache(ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.remoteId) - override fun onStop() { Log_OC.d(TAG, "onStop starts") loadBitmapTask?.cancel(true) diff --git a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java index ff8fd1e9d408..9519fdbd4d07 100644 --- a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java +++ b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java @@ -28,13 +28,9 @@ import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Color; import android.graphics.Point; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; -import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.text.Spannable; @@ -44,29 +40,18 @@ import android.text.style.StyleSpan; import android.util.DisplayMetrics; import android.view.View; -import android.view.WindowManager; -import android.widget.FrameLayout; -import android.widget.ImageView; -import com.elyeproj.loaderviewlibrary.LoaderImageView; import com.google.android.material.snackbar.Snackbar; import com.nextcloud.client.account.User; -import com.nextcloud.client.preferences.AppPreferences; -import com.nextcloud.model.OfflineOperationType; import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.lib.resources.files.model.ServerFileInterface; import com.owncloud.android.ui.TextDrawable; import com.owncloud.android.ui.dialog.SortingOrderDialogFragment; -import com.owncloud.android.utils.overlay.OverlayManager; -import com.owncloud.android.utils.theme.ViewThemeUtils; import java.io.BufferedReader; import java.io.IOException; @@ -80,9 +65,7 @@ import java.nio.charset.Charset; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Date; -import java.util.List; import java.util.Locale; import java.util.TimeZone; @@ -737,270 +720,4 @@ public static String getDateByPattern(long timestamp, @Nullable Context context, return df.format(timestamp); } - - /** - * Sets a thumbnail for a offline file, file or folder with various display options and states. - *

- * - * This method handles multiple thumbnail scenarios: - * - *

- * - * 1. Offline Files: - * - For folders: Shows an offline folder icon - * - For files: Loads thumbnail from local path with a gray filter - *

- * - * 2. Folders: - * - Displays folder icon with overlays - *

- * - * 3. Files: - * - Checks disk cache for existing thumbnails - * - Handles preview availability and updates - * - Supports grid and list view display modes - * - Generates new thumbnails if needed - * - Sets background color for PNG files - * - *

- * - * The method uses a shimmer effect while loading thumbnails, which is stopped - * once the thumbnail is successfully loaded or if an error occurs. - * - */ - public static void setThumbnail(OCFile file, - ImageView thumbnailView, - User user, - FileDataStorageManager storageManager, - List asyncTasks, - boolean gridView, - Context context, - LoaderImageView shimmerThumbnail, - AppPreferences preferences, - ViewThemeUtils viewThemeUtils, - OverlayManager overlayManager) { - if (file == null || thumbnailView == null || context == null) { - return; - } - - if (file.isOfflineOperation()) { - setThumbnailForOfflineOperation(file, thumbnailView, storageManager, context); - return; - } - - if (file.isFolder()) { - overlayManager.setFolderThumbnail(file, thumbnailView, shimmerThumbnail); - return; - } - - if (file.getRemoteId() == null || !file.isPreviewAvailable()) { - setThumbnailFirstTimeForFile(file, thumbnailView, storageManager, asyncTasks, gridView, shimmerThumbnail, user, preferences, context, viewThemeUtils); - return; - } - - setThumbnailFromCache(file, thumbnailView, storageManager, asyncTasks, gridView, shimmerThumbnail, user, preferences, context, viewThemeUtils); - } - - private static void setThumbnailFirstTimeForFile(OCFile file, ImageView thumbnailView, FileDataStorageManager storageManager, List asyncTasks, boolean gridView, LoaderImageView shimmerThumbnail, User user, AppPreferences preferences, Context context, ViewThemeUtils viewThemeUtils) { - if (file.getRemoteId() != null) { - generateNewThumbnail(file, thumbnailView, user, storageManager, new ArrayList<>(asyncTasks), gridView, context, shimmerThumbnail, preferences, viewThemeUtils); - return; - } - - stopShimmer(shimmerThumbnail, thumbnailView); - final var icon = MimeTypeUtil.getFileTypeIcon(file.getMimeType(), file.getFileName(), context, viewThemeUtils); - thumbnailView.setImageDrawable(icon); - } - - private static void setThumbnailForOfflineOperation(OCFile file, ImageView thumbnailView, FileDataStorageManager storageManager, Context context) { - if (file.isFolder()) { - thumbnailView.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_folder_offline)); - return; - } - - final var entity = storageManager.offlineOperationDao.getByPath(file.getDecryptedRemotePath()); - if (entity == null) { - return; - } - - if (entity.getType() instanceof OfflineOperationType.CreateFile createFileOperation) { - final var bitmap = BitmapUtils.decodeSampledBitmapFromFile(createFileOperation.getLocalPath(), 105, 105); - if (bitmap == null) { - return; - } - - final var thumbnail = BitmapUtils.addColorFilter(bitmap, Color.GRAY, 100); - thumbnailView.setImageBitmap(thumbnail); - } - } - - public static void setThumbnailFromCache(OCFile file, ImageView thumbnailView, FileDataStorageManager storageManager, List asyncTasks, boolean gridView, LoaderImageView shimmerThumbnail, User user, AppPreferences preferences, Context context, ViewThemeUtils viewThemeUtils) { - final var thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.getRemoteId()); - if (thumbnail == null || file.isUpdateThumbnailNeeded()) { - generateNewThumbnail(file, thumbnailView, user, storageManager, new ArrayList<>(asyncTasks), gridView, context, shimmerThumbnail, preferences, viewThemeUtils); - setThumbnailBackgroundForPNGFileIfNeeded(file, context, thumbnailView); - return; - } - - stopShimmer(shimmerThumbnail, thumbnailView); - - if (MimeTypeUtil.isVideo(file)) { - final var withOverlay = ThumbnailsCacheManager.addVideoOverlay(thumbnail, context); - thumbnailView.setImageBitmap(withOverlay); - } else { - BitmapUtils.setRoundedBitmapAccordingToListType(gridView, thumbnail, thumbnailView); - } - - setThumbnailBackgroundForPNGFileIfNeeded(file, context, thumbnailView); - } - - private static void setThumbnailBackgroundForPNGFileIfNeeded(ServerFileInterface file, Context context, ImageView thumbnailView) { - if ("image/png".equalsIgnoreCase(file.getMimeType())) { - final var color = ContextCompat.getColor(context, R.color.bg_default); - thumbnailView.setBackgroundColor(color); - } - } - - private static void generateNewThumbnail(OCFile file, - ImageView thumbnailView, - User user, - FileDataStorageManager storageManager, - ArrayList asyncTasks, - boolean gridView, - Context context, - LoaderImageView shimmerThumbnail, - AppPreferences preferences, - ViewThemeUtils viewThemeUtils) { - if (!ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, thumbnailView)) { - return; - } - - Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.getRemoteId()); - - if (thumbnail != null) { - // If thumbnail is already in cache, display it immediately - thumbnailView.setImageBitmap(thumbnail); - stopShimmer(shimmerThumbnail, thumbnailView); - return; - } - - for (ThumbnailsCacheManager.ThumbnailGenerationTask task : asyncTasks) { - if (file.getRemoteId() != null && task.getImageKey() != null && - file.getRemoteId().equals(task.getImageKey())) { - return; - } - } - - thumbnailView.setTag(file.getFileId()); - - try { - final ThumbnailsCacheManager.ThumbnailGenerationTask task = - new ThumbnailsCacheManager.ThumbnailGenerationTask(thumbnailView, - storageManager, - user, - asyncTasks, - gridView, - file.getRemoteId()); - Drawable drawable = MimeTypeUtil.getFileTypeIcon(file.getMimeType(), - file.getFileName(), - context, - viewThemeUtils); - if (drawable == null) { - drawable = ResourcesCompat.getDrawable(context.getResources(), - R.drawable.file_image, - null); - } - if (drawable == null) { - drawable = new ColorDrawable(Color.GRAY); - } - - int px = ThumbnailsCacheManager.getThumbnailDimension(); - thumbnail = BitmapUtils.drawableToBitmap(drawable, px, px); - final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable = - new ThumbnailsCacheManager.AsyncThumbnailDrawable(context.getResources(), - thumbnail, task); - - if (shimmerThumbnail != null) { - shimmerThumbnail.postDelayed(() -> { - if (thumbnailView.getDrawable() == null) { - if (gridView) { - configShimmerGridImageSize(shimmerThumbnail, preferences.getGridColumns()); - } - startShimmer(shimmerThumbnail, thumbnailView); - } - }, 100); - } - - task.setListener(new ThumbnailsCacheManager.ThumbnailGenerationTask.Listener() { - @Override - public void onSuccess() { - stopShimmer(shimmerThumbnail, thumbnailView); - } - - @Override - public void onError() { - stopShimmer(shimmerThumbnail, thumbnailView); - final var icon = MimeTypeUtil.getFileTypeIcon(file.getMimeType(), file.getFileName(), context, viewThemeUtils); - thumbnailView.setImageDrawable(icon); - thumbnailView.invalidate(); - Log_OC.w(TAG, "setting thumbnail failed, using icon from mime type"); - } - }); - - thumbnailView.setImageDrawable(asyncDrawable); - asyncTasks.add(task); - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, - new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, - file.getRemoteId())); - thumbnailView.invalidate(); - } catch (Exception e) { - Log_OC.d(TAG, "ThumbnailGenerationTask : " + e.getMessage()); - } - } - - public static void startShimmer(LoaderImageView thumbnailShimmer, ImageView thumbnailView) { - thumbnailShimmer.setImageResource(R.drawable.background); - thumbnailShimmer.resetLoader(); - thumbnailView.setVisibility(View.GONE); - thumbnailShimmer.setVisibility(View.VISIBLE); - } - - public static void stopShimmer(@Nullable LoaderImageView thumbnailShimmer, ImageView thumbnailView) { - if (thumbnailShimmer != null) { - thumbnailShimmer.setVisibility(View.GONE); - } - - thumbnailView.setVisibility(View.VISIBLE); - } - - private static void configShimmerGridImageSize(LoaderImageView thumbnailShimmer, float gridColumns) { - try { - FrameLayout.LayoutParams targetLayoutParams = (FrameLayout.LayoutParams) thumbnailShimmer.getLayoutParams(); - - final Point screenSize = getScreenSize(thumbnailShimmer.getContext()); - final int marginLeftAndRight = targetLayoutParams.leftMargin + targetLayoutParams.rightMargin; - final int size = Math.round(screenSize.x / gridColumns - marginLeftAndRight); - - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(size, size); - params.setMargins(targetLayoutParams.leftMargin, - targetLayoutParams.topMargin, - targetLayoutParams.rightMargin, - targetLayoutParams.bottomMargin); - thumbnailShimmer.setLayoutParams(params); - } catch (Exception exception) { - Log_OC.e("ConfigShimmer", exception.getMessage()); - } - } - - private static Point getScreenSize(Context context) throws Exception { - final WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - if (windowManager != null) { - final Point displaySize = new Point(); - windowManager.getDefaultDisplay().getSize(displaySize); - return displaySize; - } else { - throw new Exception("WindowManager not found"); - } - } } diff --git a/app/src/test/java/com/owncloud/android/ui/adapter/GalleryAdapterTest.kt b/app/src/test/java/com/owncloud/android/ui/adapter/GalleryAdapterTest.kt index 2b7bb193a56e..4e52500c06df 100644 --- a/app/src/test/java/com/owncloud/android/ui/adapter/GalleryAdapterTest.kt +++ b/app/src/test/java/com/owncloud/android/ui/adapter/GalleryAdapterTest.kt @@ -12,6 +12,7 @@ import android.text.TextUtils import com.nextcloud.client.account.User import com.nextcloud.client.jobs.upload.FileUploadHelper import com.nextcloud.client.preferences.AppPreferences +import com.nextcloud.utils.thumbnail.ThumbnailGenerator import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.GalleryItems import com.owncloud.android.datamodel.GalleryRow @@ -57,6 +58,9 @@ class GalleryAdapterTest { @Mock lateinit var viewThemeUtils: ViewThemeUtils + @Mock + lateinit var thumbnailGenerator: ThumbnailGenerator + private lateinit var mocks: AutoCloseable @Before @@ -89,7 +93,8 @@ class GalleryAdapterTest { transferServiceGetter, viewThemeUtils, 5, - thumbnailSize + thumbnailSize, + thumbnailGenerator ) val list = listOf( @@ -121,7 +126,8 @@ class GalleryAdapterTest { transferServiceGetter, viewThemeUtils, 5, - thumbnailSize + thumbnailSize, + thumbnailGenerator ) val rows = mutableListOf()