diff --git a/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt b/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt
index 4ac0b4d0f..9e8c9bf5e 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt
@@ -39,7 +39,7 @@ import android.widget.CheckBox
import androidx.appcompat.app.AlertDialog
import androidx.core.content.pm.PackageInfoCompat
import eu.opencloud.android.data.providers.implementation.OCSharedPreferencesProvider
-import eu.opencloud.android.datamodel.ThumbnailsCacheManager
+
import eu.opencloud.android.db.PreferenceManager
import eu.opencloud.android.dependecyinjection.commonModule
import eu.opencloud.android.dependecyinjection.localDataSourceModule
@@ -107,8 +107,7 @@ class MainApp : Application() {
SingleSessionManager.setUserAgent(userAgent)
- // initialise thumbnails cache on background thread
- ThumbnailsCacheManager.InitDiskCacheTask().execute()
+
initDependencyInjection()
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/datamodel/ThumbnailsCacheManager.java b/opencloudApp/src/main/java/eu/opencloud/android/datamodel/ThumbnailsCacheManager.java
deleted file mode 100644
index d4147ce3e..000000000
--- a/opencloudApp/src/main/java/eu/opencloud/android/datamodel/ThumbnailsCacheManager.java
+++ /dev/null
@@ -1,473 +0,0 @@
-/**
- * openCloud Android client application
- *
- * @author Tobias Kaminsky
- * @author David A. Velasco
- * @author Christian Schabesberger
- * Copyright (C) 2020 ownCloud GmbH.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package eu.opencloud.android.datamodel;
-
-import android.accounts.Account;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.media.ThumbnailUtils;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.widget.ImageView;
-
-import androidx.core.content.ContextCompat;
-import eu.opencloud.android.MainApp;
-import eu.opencloud.android.R;
-import eu.opencloud.android.domain.files.model.OCFile;
-import eu.opencloud.android.domain.files.usecases.DisableThumbnailsForFileUseCase;
-import eu.opencloud.android.domain.files.usecases.GetWebDavUrlForSpaceUseCase;
-import eu.opencloud.android.domain.spaces.model.SpaceSpecial;
-import eu.opencloud.android.lib.common.OpenCloudAccount;
-import eu.opencloud.android.lib.common.OpenCloudClient;
-import eu.opencloud.android.lib.common.SingleSessionManager;
-import eu.opencloud.android.lib.common.accounts.AccountUtils;
-import eu.opencloud.android.lib.common.http.HttpConstants;
-import eu.opencloud.android.lib.common.http.methods.nonwebdav.GetMethod;
-import eu.opencloud.android.ui.adapter.DiskLruImageCache;
-import eu.opencloud.android.utils.BitmapUtils;
-import kotlin.Lazy;
-import org.jetbrains.annotations.NotNull;
-import timber.log.Timber;
-
-import java.io.File;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.net.URL;
-import java.util.Locale;
-
-import static org.koin.java.KoinJavaComponent.inject;
-
-/**
- * Manager for concurrent access to thumbnails cache.
- */
-public class ThumbnailsCacheManager {
-
- private static final String CACHE_FOLDER = "thumbnailCache";
-
- private static final Object mThumbnailsDiskCacheLock = new Object();
- private static DiskLruImageCache mThumbnailCache = null;
- private static boolean mThumbnailCacheStarting = true;
-
- private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
- private static final CompressFormat mCompressFormat = CompressFormat.JPEG;
- private static final int mCompressQuality = 70;
- private static OpenCloudClient mClient = null;
-
- private static final String PREVIEW_URI = "%s%s?x=%d&y=%d&c=%s&preview=1";
- private static final String SPACE_SPECIAL_URI = "%s?scalingup=0&a=1&x=%d&y=%d&c=%s&preview=1";
-
- public static Bitmap mDefaultImg =
- BitmapFactory.decodeResource(
- MainApp.Companion.getAppContext().getResources(),
- R.drawable.file_image
- );
-
- public static class InitDiskCacheTask extends AsyncTask {
-
- @Override
- protected Void doInBackground(File... params) {
- synchronized (mThumbnailsDiskCacheLock) {
- mThumbnailCacheStarting = true;
-
- if (mThumbnailCache == null) {
- try {
- // Check if media is mounted or storage is built-in, if so,
- // try and use external cache dir; otherwise use internal cache dir
- final String cachePath =
- MainApp.Companion.getAppContext().getExternalCacheDir().getPath() +
- File.separator + CACHE_FOLDER;
- Timber.d("create dir: %s", cachePath);
- final File diskCacheDir = new File(cachePath);
- mThumbnailCache = new DiskLruImageCache(
- diskCacheDir,
- DISK_CACHE_SIZE,
- mCompressFormat,
- mCompressQuality
- );
- } catch (Exception e) {
- Timber.e(e, "Thumbnail cache could not be opened ");
- mThumbnailCache = null;
- }
- }
- mThumbnailCacheStarting = false; // Finished initialization
- mThumbnailsDiskCacheLock.notifyAll(); // Wake any waiting threads
- }
- return null;
- }
- }
-
- public static void addBitmapToCache(String key, Bitmap bitmap) {
- synchronized (mThumbnailsDiskCacheLock) {
- if (mThumbnailCache != null) {
- mThumbnailCache.put(key, bitmap);
- }
- }
- }
-
- public static void removeBitmapFromCache(String key) {
- synchronized (mThumbnailsDiskCacheLock) {
- if (mThumbnailCache != null) {
- mThumbnailCache.removeKey(key);
- }
- }
- }
-
- public static Bitmap getBitmapFromDiskCache(String key) {
- synchronized (mThumbnailsDiskCacheLock) {
- // Wait while disk cache is started from background thread
- while (mThumbnailCacheStarting) {
- try {
- mThumbnailsDiskCacheLock.wait();
- } catch (InterruptedException e) {
- Timber.e(e, "Wait in mThumbnailsDiskCacheLock was interrupted");
- }
- }
- if (mThumbnailCache != null) {
- return mThumbnailCache.getBitmap(key);
- }
- }
- return null;
- }
-
- public static class ThumbnailGenerationTask extends AsyncTask {
- private final WeakReference mImageViewReference;
- private static Account mAccount;
- private Object mFile;
- private FileDataStorageManager mStorageManager;
-
- public ThumbnailGenerationTask(ImageView imageView, Account account) {
- // Use a WeakReference to ensure the ImageView can be garbage collected
- mImageViewReference = new WeakReference<>(imageView);
- mAccount = account;
- }
-
- public ThumbnailGenerationTask(ImageView imageView) {
- // Use a WeakReference to ensure the ImageView can be garbage collected
- mImageViewReference = new WeakReference<>(imageView);
- }
-
- @Override
- protected Bitmap doInBackground(Object... params) {
- Bitmap thumbnail = null;
-
- try {
- if (mAccount != null) {
- OpenCloudAccount ocAccount = new OpenCloudAccount(
- mAccount,
- MainApp.Companion.getAppContext()
- );
- mClient = SingleSessionManager.getDefaultSingleton().
- getClientFor(ocAccount, MainApp.Companion.getAppContext());
- }
-
- mFile = params[0];
-
- if (mFile instanceof OCFile) {
- thumbnail = doOCFileInBackground();
- } else if (mFile instanceof File) {
- thumbnail = doFileInBackground();
- } else if (mFile instanceof SpaceSpecial) {
- thumbnail = doSpaceImageInBackground();
- //} else { do nothing
- }
-
- } catch (Throwable t) {
- // the app should never break due to a problem with thumbnails
- Timber.e(t, "Generation of thumbnail for " + mFile + " failed");
- if (t instanceof OutOfMemoryError) {
- System.gc();
- }
- }
-
- return thumbnail;
- }
-
- protected void onPostExecute(Bitmap bitmap) {
- if (bitmap != null) {
- final ImageView imageView = mImageViewReference.get();
- final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
- if (this == bitmapWorkerTask) {
- String tagId = "";
- if (mFile instanceof OCFile) {
- tagId = String.valueOf(((OCFile) mFile).getId());
- } else if (mFile instanceof File) {
- tagId = String.valueOf(mFile.hashCode());
- } else if (mFile instanceof SpaceSpecial) {
- tagId = ((SpaceSpecial) mFile).getId();
- }
- if (String.valueOf(imageView.getTag()).equals(tagId)) {
- imageView.setImageBitmap(bitmap);
- }
- }
- }
- }
-
- /**
- * Add thumbnail to cache
- *
- * @param imageKey: thumb key
- * @param bitmap: image for extracting thumbnail
- * @param path: image path
- * @param px: thumbnail dp
- * @return Bitmap
- */
- private Bitmap addThumbnailToCache(String imageKey, Bitmap bitmap, String path, int px) {
-
- Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
-
- // Rotate image, obeying exif tag
- thumbnail = BitmapUtils.rotateImage(thumbnail, path);
-
- // Add thumbnail to cache
- addBitmapToCache(imageKey, thumbnail);
-
- return thumbnail;
- }
-
- /**
- * Converts size of file icon from dp to pixel
- *
- * @return int
- */
- private int getThumbnailDimension() {
- // Converts dp to pixel
- Resources r = MainApp.Companion.getAppContext().getResources();
- return Math.round(r.getDimension(R.dimen.file_icon_size_grid));
- }
-
- private String getPreviewUrl(OCFile ocFile, Account account) {
- String baseUrl = mClient.getBaseUri() + "/remote.php/dav/files/" + AccountUtils.getUserId(account, MainApp.Companion.getAppContext());
-
- if (ocFile.getSpaceId() != null) {
- Lazy getWebDavUrlForSpaceUseCaseLazy = inject(GetWebDavUrlForSpaceUseCase.class);
- baseUrl = getWebDavUrlForSpaceUseCaseLazy.getValue().invoke(
- new GetWebDavUrlForSpaceUseCase.Params(ocFile.getOwner(), ocFile.getSpaceId())
- );
-
- }
- return String.format(Locale.ROOT,
- PREVIEW_URI,
- baseUrl,
- Uri.encode(ocFile.getRemotePath(), "/"),
- getThumbnailDimension(),
- getThumbnailDimension(),
- ocFile.getEtag());
- }
-
- private Bitmap doOCFileInBackground() {
- OCFile file = (OCFile) mFile;
-
- final String imageKey = String.valueOf(file.getRemoteId());
-
- // Check disk cache in background thread
- Bitmap thumbnail = getBitmapFromDiskCache(imageKey);
-
- // Not found in disk cache
- if (thumbnail == null || file.getNeedsToUpdateThumbnail()) {
-
- int px = getThumbnailDimension();
-
- // Download thumbnail from server
- if (mClient != null) {
- GetMethod get;
- try {
- String uri = getPreviewUrl(file, mAccount);
- Timber.d("URI: %s", uri);
- get = new GetMethod(new URL(uri));
- int status = mClient.executeHttpMethod(get);
- if (status == HttpConstants.HTTP_OK) {
- InputStream inputStream = get.getResponseBodyAsStream();
- Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
- thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
-
- // Handle PNG
- if (file.getMimeType().equalsIgnoreCase("image/png")) {
- thumbnail = handlePNG(thumbnail, px);
- }
-
- // Add thumbnail to cache
- if (thumbnail != null) {
- addBitmapToCache(imageKey, thumbnail);
- }
- } else {
- mClient.exhaustResponse(get.getResponseBodyAsStream());
- }
- if (status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_NOT_FOUND) {
- @NotNull Lazy disableThumbnailsForFileUseCaseLazy = inject(DisableThumbnailsForFileUseCase.class);
- disableThumbnailsForFileUseCaseLazy.getValue().invoke(new DisableThumbnailsForFileUseCase.Params(file.getId()));
- }
- } catch (Exception e) {
- Timber.e(e);
- }
- }
- }
-
- return thumbnail;
-
- }
-
- private Bitmap handlePNG(Bitmap bitmap, int px) {
- Bitmap resultBitmap = Bitmap.createBitmap(px,
- px,
- Bitmap.Config.ARGB_8888);
- Canvas c = new Canvas(resultBitmap);
-
- c.drawColor(ContextCompat.getColor(MainApp.Companion.getAppContext(), R.color.background_color));
- c.drawBitmap(bitmap, 0, 0, null);
-
- return resultBitmap;
- }
-
- private Bitmap doFileInBackground() {
- File file = (File) mFile;
-
- final String imageKey = String.valueOf(file.hashCode());
-
- // Check disk cache in background thread
- Bitmap thumbnail = getBitmapFromDiskCache(imageKey);
-
- // Not found in disk cache
- if (thumbnail == null) {
-
- int px = getThumbnailDimension();
-
- Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
- file.getAbsolutePath(), px, px);
-
- if (bitmap != null) {
- thumbnail = addThumbnailToCache(imageKey, bitmap, file.getPath(), px);
- }
- }
- return thumbnail;
- }
-
- private String getSpaceSpecialUri(SpaceSpecial spaceSpecial) {
- // Converts dp to pixel
- Resources r = MainApp.Companion.getAppContext().getResources();
- Integer spacesThumbnailSize = Math.round(r.getDimension(R.dimen.spaces_thumbnail_height)) * 2;
- return String.format(Locale.ROOT,
- SPACE_SPECIAL_URI,
- spaceSpecial.getWebDavUrl(),
- spacesThumbnailSize,
- spacesThumbnailSize,
- spaceSpecial.getETag());
- }
-
- private Bitmap doSpaceImageInBackground() {
- SpaceSpecial spaceSpecial = (SpaceSpecial) mFile;
-
- final String imageKey = spaceSpecial.getId();
-
- // Check disk cache in background thread
- Bitmap thumbnail = getBitmapFromDiskCache(imageKey);
-
- // Not found in disk cache
- if (thumbnail == null) {
- int px = getThumbnailDimension();
-
- // Download thumbnail from server
- if (mClient != null) {
- GetMethod get;
- try {
- String uri = getSpaceSpecialUri(spaceSpecial);
- Timber.d("URI: %s", uri);
- get = new GetMethod(new URL(uri));
- int status = mClient.executeHttpMethod(get);
- if (status == HttpConstants.HTTP_OK) {
- InputStream inputStream = get.getResponseBodyAsStream();
- Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
- thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
-
- // Handle PNG
- if (spaceSpecial.getFile().getMimeType().equalsIgnoreCase("image/png")) {
- thumbnail = handlePNG(thumbnail, px);
- }
-
- // Add thumbnail to cache
- if (thumbnail != null) {
- addBitmapToCache(imageKey, thumbnail);
- }
- } else {
- mClient.exhaustResponse(get.getResponseBodyAsStream());
- }
- } catch (Exception e) {
- Timber.e(e);
- }
- }
- }
-
- return thumbnail;
-
- }
- }
-
- public static boolean cancelPotentialThumbnailWork(Object file, ImageView imageView) {
- final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
-
- if (bitmapWorkerTask != null) {
- final Object bitmapData = bitmapWorkerTask.mFile;
- // If bitmapData is not yet set or it differs from the new data
- if (bitmapData == null || bitmapData != file) {
- // Cancel previous task
- bitmapWorkerTask.cancel(true);
- Timber.v("Cancelled generation of thumbnail for a reused imageView");
- } else {
- // The same work is already in progress
- return false;
- }
- }
- // No task associated with the ImageView, or an existing task was cancelled
- return true;
- }
-
- private static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) {
- if (imageView != null) {
- final Drawable drawable = imageView.getDrawable();
- if (drawable instanceof AsyncThumbnailDrawable) {
- final AsyncThumbnailDrawable asyncDrawable = (AsyncThumbnailDrawable) drawable;
- return asyncDrawable.getBitmapWorkerTask();
- }
- }
- return null;
- }
-
- public static class AsyncThumbnailDrawable extends BitmapDrawable {
- private final WeakReference bitmapWorkerTaskReference;
-
- public AsyncThumbnailDrawable(
- Resources res, Bitmap bitmap, ThumbnailGenerationTask bitmapWorkerTask
- ) {
-
- super(res, bitmap);
- bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
- }
-
- ThumbnailGenerationTask getBitmapWorkerTask() {
- return bitmapWorkerTaskReference.get();
- }
- }
-}
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/dependecyinjection/CommonModule.kt b/opencloudApp/src/main/java/eu/opencloud/android/dependecyinjection/CommonModule.kt
index 7cbe1898b..04c978ea3 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/dependecyinjection/CommonModule.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/dependecyinjection/CommonModule.kt
@@ -21,7 +21,7 @@
package eu.opencloud.android.dependecyinjection
import androidx.work.WorkManager
-import eu.opencloud.android.presentation.avatar.AvatarManager
+
import eu.opencloud.android.providers.AccountProvider
import eu.opencloud.android.providers.ContextProvider
import eu.opencloud.android.providers.CoroutinesDispatcherProvider
@@ -35,7 +35,7 @@ import org.koin.dsl.module
val commonModule = module {
- single { AvatarManager() }
+
single { CoroutinesDispatcherProvider() }
factory { OCContextProvider(androidContext()) }
single { LogsProvider(get(), get()) }
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/operations/SyncProfileOperation.kt b/opencloudApp/src/main/java/eu/opencloud/android/operations/SyncProfileOperation.kt
index 26779d01c..ab41ef989 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/operations/SyncProfileOperation.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/operations/SyncProfileOperation.kt
@@ -23,11 +23,11 @@ import android.accounts.Account
import android.accounts.AccountManager
import eu.opencloud.android.MainApp.Companion.appContext
import eu.opencloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase
-import eu.opencloud.android.domain.user.usecases.GetUserAvatarAsyncUseCase
+
import eu.opencloud.android.domain.user.usecases.GetUserInfoAsyncUseCase
import eu.opencloud.android.domain.user.usecases.RefreshUserQuotaFromServerAsyncUseCase
import eu.opencloud.android.lib.common.accounts.AccountUtils
-import eu.opencloud.android.presentation.avatar.AvatarManager
+
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -79,12 +79,8 @@ class SyncProfileOperation(
}
val shouldFetchAvatar = storedCapabilities?.isFetchingAvatarAllowed() ?: true
if (shouldFetchAvatar) {
- val getUserAvatarAsyncUseCase: GetUserAvatarAsyncUseCase by inject()
- val userAvatarResult = getUserAvatarAsyncUseCase(GetUserAvatarAsyncUseCase.Params(account.name))
- AvatarManager().handleAvatarUseCaseResult(account, userAvatarResult)
- if (userAvatarResult.isSuccess) {
- Timber.d("Avatar synchronized for account ${account.name}")
- }
+ // Avatar fetching is now handled by Coil on demand
+ Timber.d("Avatar sync handled by Coil for account ${account.name}")
} else {
Timber.d("Avatar for this account: ${account.name} won't be synced due to capabilities ")
}
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/accounts/ManageAccountsAdapter.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/accounts/ManageAccountsAdapter.kt
index a20e971b3..9c0047c3b 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/accounts/ManageAccountsAdapter.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/accounts/ManageAccountsAdapter.kt
@@ -44,6 +44,11 @@ import eu.opencloud.android.presentation.avatar.AvatarUtils
import eu.opencloud.android.utils.DisplayUtils
import eu.opencloud.android.utils.PreferenceUtils
import timber.log.Timber
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
class ManageAccountsAdapter(
private val accountListener: AccountAdapterListener,
@@ -102,12 +107,17 @@ class ManageAccountsAdapter(
try {
val avatarUtils = AvatarUtils()
- avatarUtils.loadAvatarForAccount(
- holder.binding.icon,
- account,
- true,
- accountAvatarRadiusDimension
- )
+ holder.itemView.findViewTreeLifecycleOwner()?.lifecycleScope?.launch(Dispatchers.IO) {
+ val loader = eu.opencloud.android.presentation.thumbnails.ThumbnailsRequester.getCoilImageLoader(account)
+ withContext(Dispatchers.Main) {
+ avatarUtils.loadAvatarForAccount(
+ holder.binding.icon,
+ account,
+ accountAvatarRadiusDimension,
+ loader
+ )
+ }
+ }
} catch (e: java.lang.Exception) {
Timber.e(e, "Error calculating RGB value for account list item.")
// use user icon as a fallback
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/avatar/AvatarManager.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/avatar/AvatarManager.kt
deleted file mode 100644
index 5ed928e38..000000000
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/avatar/AvatarManager.kt
+++ /dev/null
@@ -1,146 +0,0 @@
-/**
- * openCloud Android client application
- *
- * @author Abel GarcĂa de Prada
- * Copyright (C) 2020 ownCloud GmbH.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package eu.opencloud.android.presentation.avatar
-
-import android.accounts.Account
-import android.graphics.BitmapFactory
-import android.graphics.drawable.Drawable
-import android.media.ThumbnailUtils
-import eu.opencloud.android.MainApp.Companion.appContext
-import eu.opencloud.android.R
-import eu.opencloud.android.datamodel.ThumbnailsCacheManager
-import eu.opencloud.android.domain.UseCaseResult
-import eu.opencloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase
-import eu.opencloud.android.domain.exceptions.FileNotFoundException
-import eu.opencloud.android.domain.user.model.UserAvatar
-import eu.opencloud.android.domain.user.usecases.GetUserAvatarAsyncUseCase
-import eu.opencloud.android.ui.DefaultAvatarTextDrawable
-import eu.opencloud.android.utils.BitmapUtils
-import org.koin.core.component.KoinComponent
-import org.koin.core.component.inject
-import org.koin.core.error.InstanceCreationException
-import timber.log.Timber
-import kotlin.math.roundToInt
-
-/**
- * The avatar is loaded if available in the cache and bound to the received UI element. The avatar is not
- * fetched from the server if not available, unless the parameter 'fetchFromServer' is set to 'true'.
- *
- * If there is no avatar stored and cannot be fetched, a colored icon is generated with the first
- * letter of the account username.
- *
- * If this is not possible either, a predefined user icon is bound instead.
- */
-class AvatarManager : KoinComponent {
-
- fun getAvatarForAccount(
- account: Account,
- fetchIfNotCached: Boolean,
- displayRadius: Float
- ): Drawable? {
- val imageKey = getImageKeyForAccount(account)
-
- // Check disk cache in background thread
- val avatarBitmap = ThumbnailsCacheManager.getBitmapFromDiskCache(imageKey)
- avatarBitmap?.let {
- Timber.i("Avatar retrieved from cache with imageKey: $imageKey")
- return BitmapUtils.bitmapToCircularBitmapDrawable(appContext.resources, it)
- }
-
- val shouldFetchAvatar = try {
- val getStoredCapabilitiesUseCase: GetStoredCapabilitiesUseCase by inject()
- val storedCapabilities = getStoredCapabilitiesUseCase(GetStoredCapabilitiesUseCase.Params(account.name))
- storedCapabilities?.isFetchingAvatarAllowed() ?: true
- } catch (instanceCreationException: InstanceCreationException) {
- Timber.e(instanceCreationException, "Koin may not be initialized at this point")
- true
- }
-
- // Avatar not found in disk cache, fetch from server.
- if (fetchIfNotCached && shouldFetchAvatar) {
- Timber.i("Avatar with imageKey $imageKey is not available in cache. Fetching from server...")
- val getUserAvatarAsyncUseCase: GetUserAvatarAsyncUseCase by inject()
- val useCaseResult =
- getUserAvatarAsyncUseCase(GetUserAvatarAsyncUseCase.Params(accountName = account.name))
- handleAvatarUseCaseResult(account, useCaseResult)?.let { return it }
- }
-
- // generate placeholder from user name
- try {
- Timber.i("Avatar with imageKey $imageKey is not available in cache. Generating one...")
- return DefaultAvatarTextDrawable.createAvatar(account.name, displayRadius)
-
- } catch (e: Exception) {
- // nothing to do, return null to apply default icon
- Timber.e(e, "Error calculating RGB value for active account icon.")
- }
- return null
- }
-
- /**
- * Converts size of file icon from dp to pixel
- *
- * @return int
- */
- private fun getAvatarDimension(): Int = appContext.resources.getDimension(R.dimen.file_avatar_size).roundToInt()
-
- private fun getImageKeyForAccount(account: Account) = "a_${account.name}"
-
- /**
- * If [GetUserAvatarAsyncUseCase] is success, add avatar to cache and return a circular drawable.
- * If there is no avatar available in server, remove it from cache.
- */
- fun handleAvatarUseCaseResult(
- account: Account,
- useCaseResult: UseCaseResult
- ): Drawable? {
- Timber.d("Fetch avatar use case is success: ${useCaseResult.isSuccess}")
- val imageKey = getImageKeyForAccount(account)
-
- if (useCaseResult.isSuccess) {
- val userAvatar = useCaseResult.getDataOrNull()
- userAvatar?.let {
- try {
- var bitmap = BitmapFactory.decodeByteArray(it.avatarData, 0, it.avatarData.size)
- bitmap = ThumbnailUtils.extractThumbnail(bitmap, getAvatarDimension(), getAvatarDimension())
- // Add avatar to cache
- bitmap?.let {
- ThumbnailsCacheManager.addBitmapToCache(imageKey, bitmap)
- Timber.d("User avatar saved into cache -> %s", imageKey)
- return BitmapUtils.bitmapToCircularBitmapDrawable(appContext.resources, bitmap)
- }
- } catch (t: OutOfMemoryError) {
- // the app should never break due to a problem with avatars
- Timber.e(t, "Generation of avatar for $imageKey failed")
- System.gc()
- null
- } catch (t: Throwable) {
- Timber.e(t, "Generation of avatar for $imageKey failed")
- null
- }
- }
-
- } else if (useCaseResult.getThrowableOrNull() is FileNotFoundException) {
- Timber.i("No avatar available, removing cached copy")
- ThumbnailsCacheManager.removeBitmapFromCache(imageKey)
- }
- return null
- }
-}
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/avatar/AvatarUtils.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/avatar/AvatarUtils.kt
index 47ca63760..1c9afd6da 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/avatar/AvatarUtils.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/avatar/AvatarUtils.kt
@@ -23,17 +23,13 @@ import android.accounts.Account
import android.view.MenuItem
import android.widget.ImageView
import eu.opencloud.android.R
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
+import coil.load
+import eu.opencloud.android.MainApp.Companion.appContext
+import eu.opencloud.android.presentation.thumbnails.ThumbnailsRequester
import org.koin.core.component.KoinComponent
-import org.koin.core.component.inject
class AvatarUtils : KoinComponent {
- private val avatarManager: AvatarManager by inject()
-
/**
* Show the avatar corresponding to the received account in an {@ImageView}.
*
@@ -53,45 +49,34 @@ class AvatarUtils : KoinComponent {
fun loadAvatarForAccount(
imageView: ImageView,
account: Account,
- @Suppress("UnusedParameter") fetchIfNotCached: Boolean = false,
- @Suppress("UnusedParameter") displayRadius: Float
+ @Suppress("UnusedParameter") displayRadius: Float,
+ imageLoader: coil.ImageLoader? = null
) {
- // Tech debt: Move this to a viewModel and use its viewModelScope instead
- CoroutineScope(Dispatchers.IO).launch {
- val drawable = avatarManager.getAvatarForAccount(
- account = account,
- fetchIfNotCached = fetchIfNotCached,
- displayRadius = displayRadius
- )
- withContext(Dispatchers.Main) {
- if (drawable != null) {
- imageView.setImageDrawable(drawable)
- } else {
- imageView.setImageResource(R.drawable.ic_account_circle)
- }
- }
+ val uri = ThumbnailsRequester.getAvatarUri(account)
+ val loader = imageLoader ?: ThumbnailsRequester.getCoilImageLoader(account)
+ imageView.load(uri, loader) {
+ placeholder(R.drawable.ic_account_circle)
+ error(R.drawable.ic_account_circle)
+ transformations(coil.transform.CircleCropTransformation())
}
}
fun loadAvatarForAccount(
menuItem: MenuItem,
account: Account,
- @Suppress("UnusedParameter") fetchIfNotCached: Boolean = false,
@Suppress("UnusedParameter") displayRadius: Float
) {
- CoroutineScope(Dispatchers.IO).launch {
- val drawable = avatarManager.getAvatarForAccount(
- account = account,
- fetchIfNotCached = fetchIfNotCached,
- displayRadius = displayRadius
+ val uri = ThumbnailsRequester.getAvatarUri(account)
+ val imageLoader = ThumbnailsRequester.getCoilImageLoader(account)
+ val request = coil.request.ImageRequest.Builder(appContext)
+ .data(uri)
+ .target(
+ onStart = { menuItem.setIcon(R.drawable.ic_account_circle) },
+ onSuccess = { result -> menuItem.icon = result },
+ onError = { menuItem.setIcon(R.drawable.ic_account_circle) }
)
- withContext(Dispatchers.Main) {
- if (drawable != null) {
- menuItem.icon = drawable
- } else {
- menuItem.setIcon(R.drawable.ic_account_circle)
- }
- }
- }
+ .transformations(coil.transform.CircleCropTransformation())
+ .build()
+ imageLoader.enqueue(request)
}
}
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/details/FileDetailsFragment.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/details/FileDetailsFragment.kt
index 6aaed24c1..49e58c90c 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/details/FileDetailsFragment.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/details/FileDetailsFragment.kt
@@ -24,7 +24,7 @@ package eu.opencloud.android.presentation.files.details
import android.accounts.Account
import android.content.Intent
-import android.graphics.Bitmap
+
import android.net.Uri
import android.os.Build
import android.os.Bundle
@@ -38,10 +38,12 @@ import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.view.isVisible
import androidx.work.WorkInfo
import com.google.android.material.snackbar.Snackbar
-import eu.opencloud.android.MainApp
+
import eu.opencloud.android.R
+import coil.load
import eu.opencloud.android.databinding.FileDetailsFragmentBinding
-import eu.opencloud.android.datamodel.ThumbnailsCacheManager
+
+import eu.opencloud.android.presentation.thumbnails.ThumbnailsRequester
import eu.opencloud.android.domain.exceptions.AccountNotFoundException
import eu.opencloud.android.domain.exceptions.InstanceNotConfiguredException
import eu.opencloud.android.domain.exceptions.TooEarlyException
@@ -428,25 +430,16 @@ class FileDetailsFragment : FileFragment() {
}
}
if (ocFile.isImage) {
- val tagId = ocFile.remoteId.toString()
- var thumbnail: Bitmap? = ThumbnailsCacheManager.getBitmapFromDiskCache(tagId)
- if (thumbnail != null && !ocFile.needsToUpdateThumbnail) {
- imageView.setImageBitmap(thumbnail)
- } else {
- // generate new Thumbnail
- if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(ocFile, imageView)) {
- val task = ThumbnailsCacheManager.ThumbnailGenerationTask(imageView, fileDetailsViewModel.getAccount())
- if (thumbnail == null) {
- thumbnail = ThumbnailsCacheManager.mDefaultImg
- }
- val asyncDrawable = ThumbnailsCacheManager.AsyncThumbnailDrawable(
- MainApp.appContext.resources,
- thumbnail,
- task
- )
- imageView.setImageDrawable(asyncDrawable)
- task.execute(ocFile)
- }
+ imageView.load(
+ ThumbnailsRequester.getPreviewUriForFile(
+ OCFileWithSyncInfo(ocFile, null),
+ fileDetailsViewModel.getAccount()
+ ),
+ ThumbnailsRequester.getCoilImageLoader(fileDetailsViewModel.getAccount())
+ ) {
+ placeholder(MimetypeIconUtil.getFileTypeIconId(ocFile.mimeType, ocFile.fileName))
+ error(MimetypeIconUtil.getFileTypeIconId(ocFile.mimeType, ocFile.fileName))
+ crossfade(true)
}
} else {
// Name of the file, to deduce the icon to use in case the MIME type is not precise enough
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/filelist/FileListAdapter.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/filelist/FileListAdapter.kt
index 911bae6f6..686552d9c 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/filelist/FileListAdapter.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/filelist/FileListAdapter.kt
@@ -25,7 +25,7 @@ package eu.opencloud.android.presentation.files.filelist
import android.accounts.Account
import android.content.Context
-import android.graphics.Bitmap
+
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
@@ -41,7 +41,9 @@ import eu.opencloud.android.R
import eu.opencloud.android.databinding.GridItemBinding
import eu.opencloud.android.databinding.ItemFileListBinding
import eu.opencloud.android.databinding.ListFooterBinding
-import eu.opencloud.android.datamodel.ThumbnailsCacheManager
+import coil.load
+import coil.dispose
+import eu.opencloud.android.presentation.thumbnails.ThumbnailsRequester
import eu.opencloud.android.domain.files.model.FileListOption
import eu.opencloud.android.domain.files.model.OCFileWithSyncInfo
import eu.opencloud.android.domain.files.model.OCFooterFile
@@ -60,13 +62,19 @@ class FileListAdapter(
var files = mutableListOf()
private var account: Account? = AccountUtils.getCurrentOpenCloudAccount(context)
private var fileListOption: FileListOption = FileListOption.ALL_FILES
+ private val disallowTouchesWithOtherWindows =
+ PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context)
+
+ init {
+ setHasStableIds(true)
+ }
fun updateFileList(filesToAdd: List, fileListOption: FileListOption) {
val listWithFooter = mutableListOf()
listWithFooter.addAll(filesToAdd)
- if (listWithFooter.isNotEmpty()) {
+ if (listWithFooter.isNotEmpty() && !isPickerMode) {
listWithFooter.add(OCFooterFile(manageListOfFilesAndGenerateText(filesToAdd)))
}
@@ -85,13 +93,22 @@ class FileListAdapter(
diffResult.dispatchUpdatesTo(this)
}
+ override fun getItemId(position: Int): Long {
+ val item = files.getOrNull(position)
+ return when (item) {
+ is OCFileWithSyncInfo -> item.file.id ?: item.file.remotePath.hashCode().toLong()
+ is OCFooterFile -> Long.MIN_VALUE + position
+ else -> position.toLong()
+ }
+ }
+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
when (viewType) {
ViewType.LIST_ITEM.ordinal -> {
val binding = ItemFileListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
binding.root.apply {
tag = ViewType.LIST_ITEM
- filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context)
+ filterTouchesWhenObscured = disallowTouchesWithOtherWindows
}
ListViewHolder(binding)
}
@@ -100,7 +117,7 @@ class FileListAdapter(
val binding = GridItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
binding.root.apply {
tag = ViewType.GRID_IMAGE
- filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context)
+ filterTouchesWhenObscured = disallowTouchesWithOtherWindows
}
GridImageViewHolder(binding)
}
@@ -109,7 +126,7 @@ class FileListAdapter(
val binding = GridItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
binding.root.apply {
tag = ViewType.GRID_ITEM
- filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context)
+ filterTouchesWhenObscured = disallowTouchesWithOtherWindows
}
GridViewHolder(binding)
}
@@ -118,7 +135,7 @@ class FileListAdapter(
val binding = ListFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
binding.root.apply {
tag = ViewType.FOOTER
- filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context)
+ filterTouchesWhenObscured = disallowTouchesWithOtherWindows
}
FooterViewHolder(binding)
}
@@ -126,9 +143,11 @@ class FileListAdapter(
override fun getItemCount(): Int = files.size
- override fun getItemId(position: Int): Long = position.toLong()
+ private fun hasFooter(): Boolean = files.lastOrNull() is OCFooterFile
+
+ private fun isFooter(position: Int) = files.getOrNull(position) is OCFooterFile
- private fun isFooter(position: Int) = position == files.size.minus(1)
+ private fun selectableItemCount(): Int = files.size - if (hasFooter()) 1 else 0
override fun getItemViewType(position: Int): Int =
@@ -166,33 +185,43 @@ class FileListAdapter(
fun selectAll() {
// Last item on list is the footer, so that element must be excluded from selection
- selectAll(totalItems = files.size - 1)
+ selectAll(totalItems = selectableItemCount())
}
fun selectInverse() {
// Last item on list is the footer, so that element must be excluded from selection
- toggleSelectionInBulk(totalItems = files.size - 1)
+ toggleSelectionInBulk(totalItems = selectableItemCount())
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val viewType = getItemViewType(position)
+ AccountUtils.getCurrentOpenCloudAccount(context)?.let { currentAccount ->
+ if (currentAccount != account) {
+ account = currentAccount
+ }
+ } ?: run {
+ if (account != null) {
+ account = null
+ }
+ }
+
if (viewType != ViewType.FOOTER.ordinal) { // Is Item
+ val hasActiveSelection = selectedItemCount > 0
val fileWithSyncInfo = files[position] as OCFileWithSyncInfo
val file = fileWithSyncInfo.file
val name = file.fileName
val fileIcon = holder.itemView.findViewById(R.id.thumbnail).apply {
tag = file.id
}
- val thumbnail: Bitmap? = file.remoteId?.let { ThumbnailsCacheManager.getBitmapFromDiskCache(file.remoteId) }
holder.itemView.findViewById(R.id.ListItemLayout)?.apply {
contentDescription = "LinearLayout-$name"
// Allow or disallow touches with other visible windows
- filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context)
+ filterTouchesWhenObscured = disallowTouchesWithOtherWindows
}
holder.itemView.findViewById(R.id.share_icons_layout).isVisible =
@@ -201,26 +230,35 @@ class FileListAdapter(
holder.itemView.findViewById(R.id.shared_via_users_icon).isVisible =
file.sharedWithSharee == true || file.isSharedWithMe
- setSpecificViewHolder(viewType, holder, fileWithSyncInfo, thumbnail)
+ setSpecificViewHolder(viewType, holder, fileWithSyncInfo, hasActiveSelection)
setIconPinAccordingToFilesLocalState(holder.itemView.findViewById(R.id.localFileIndicator), fileWithSyncInfo)
holder.itemView.setOnClickListener {
+ val adapterPosition = holder.bindingAdapterPosition
+ if (adapterPosition == RecyclerView.NO_POSITION) {
+ return@setOnClickListener
+ }
+ val currentItem = files.getOrNull(adapterPosition) as? OCFileWithSyncInfo ?: return@setOnClickListener
listener.onItemClick(
- ocFileWithSyncInfo = fileWithSyncInfo,
- position = position
+ ocFileWithSyncInfo = currentItem,
+ position = adapterPosition
)
}
holder.itemView.setOnLongClickListener {
+ val adapterPosition = holder.bindingAdapterPosition
+ if (adapterPosition == RecyclerView.NO_POSITION) {
+ return@setOnLongClickListener false
+ }
listener.onLongItemClick(
- position = position
+ position = adapterPosition
)
}
holder.itemView.setBackgroundColor(Color.WHITE)
val checkBoxV = holder.itemView.findViewById(R.id.custom_checkbox).apply {
- isVisible = getCheckedItems().isNotEmpty()
+ isVisible = hasActiveSelection
}
if (isSelected(position)) {
@@ -233,28 +271,29 @@ class FileListAdapter(
if (file.isFolder) {
// Folder
+ fileIcon.dispose()
fileIcon.setImageResource(R.drawable.ic_menu_archive)
+ fileIcon.setBackgroundColor(Color.TRANSPARENT)
} else {
// Set file icon depending on its mimetype. Ask for thumbnail later.
fileIcon.setImageResource(MimetypeIconUtil.getFileTypeIconId(file.mimeType, file.fileName))
- if (thumbnail != null) {
- fileIcon.setImageBitmap(thumbnail)
- }
- if (file.needsToUpdateThumbnail && ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, fileIcon)) {
- // generate new Thumbnail
- val task = ThumbnailsCacheManager.ThumbnailGenerationTask(fileIcon, account)
- val asyncDrawable = ThumbnailsCacheManager.AsyncThumbnailDrawable(context.resources, thumbnail, task)
-
- // If drawable is not visible, do not update it.
- if (asyncDrawable.minimumHeight > 0 && asyncDrawable.minimumWidth > 0) {
- fileIcon.setImageDrawable(asyncDrawable)
+ if (file.isImage) {
+ account?.let { acc ->
+ fileIcon.load(ThumbnailsRequester.getPreviewUriForFile(fileWithSyncInfo, acc), ThumbnailsRequester.getCoilImageLoader(acc)) {
+ placeholder(MimetypeIconUtil.getFileTypeIconId(file.mimeType, file.fileName))
+ error(MimetypeIconUtil.getFileTypeIconId(file.mimeType, file.fileName))
+ crossfade(true)
+ }
}
- task.execute(file)
+ } else {
+ fileIcon.dispose()
}
- if (file.mimeType == "image/png") {
+ if (file.mimeType.equals("image/png", ignoreCase = true)) {
fileIcon.setBackgroundColor(ContextCompat.getColor(context, R.color.background_color))
+ } else {
+ fileIcon.setBackgroundColor(Color.TRANSPARENT)
}
}
@@ -270,18 +309,23 @@ class FileListAdapter(
}
}
- private fun setSpecificViewHolder(viewType: Int, holder: RecyclerView.ViewHolder, fileWithSyncInfo: OCFileWithSyncInfo, thumbnail: Bitmap?) {
+ private fun setSpecificViewHolder(
+ viewType: Int,
+ holder: RecyclerView.ViewHolder,
+ fileWithSyncInfo: OCFileWithSyncInfo,
+ hasActiveSelection: Boolean,
+ ) {
val file = fileWithSyncInfo.file
when (viewType) {
ViewType.LIST_ITEM.ordinal -> {
val view = holder as ListViewHolder
view.binding.let {
- it.fileListConstraintLayout.filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context)
+ it.fileListConstraintLayout.filterTouchesWhenObscured = disallowTouchesWithOtherWindows
it.Filename.text = file.fileName
it.fileListSize.text = DisplayUtils.bytesToHumanReadable(file.length, context, true)
it.fileListLastMod.text = DisplayUtils.getRelativeTimestamp(context, file.modificationTimestamp)
- it.threeDotMenu.isVisible = getCheckedItems().isEmpty()
+ it.threeDotMenu.isVisible = !hasActiveSelection
it.threeDotMenu.contentDescription = context.getString(R.string.content_description_file_operations, file.fileName)
if (fileListOption.isAvailableOffline() || (fileListOption.isSharedByLink() && fileWithSyncInfo.space == null)) {
it.spacePathLine.path.apply {
@@ -320,23 +364,16 @@ class FileListAdapter(
val fileIcon = holder.itemView.findViewById(R.id.thumbnail)
val layoutParams = fileIcon.layoutParams as ViewGroup.MarginLayoutParams
- if (thumbnail == null) {
- view.binding.Filename.text = file.fileName
- // Reset layout params values default
- manageGridLayoutParams(
- layoutParams = layoutParams,
- marginVertical = 0,
- height = context.resources.getDimensionPixelSize(R.dimen.item_file_grid_height),
- width = context.resources.getDimensionPixelSize(R.dimen.item_file_grid_width),
- )
- } else {
- manageGridLayoutParams(
- layoutParams = layoutParams,
- marginVertical = context.resources.getDimensionPixelSize(R.dimen.item_file_image_grid_margin),
- height = ViewGroup.LayoutParams.MATCH_PARENT,
- width = ViewGroup.LayoutParams.MATCH_PARENT,
- )
+ view.binding.Filename.apply {
+ text = ""
+ isVisible = false
}
+ manageGridLayoutParams(
+ layoutParams = layoutParams,
+ marginVertical = context.resources.getDimensionPixelSize(R.dimen.item_file_image_grid_margin),
+ height = ViewGroup.LayoutParams.MATCH_PARENT,
+ width = ViewGroup.LayoutParams.MATCH_PARENT,
+ )
}
}
}
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/filelist/MainFileListFragment.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/filelist/MainFileListFragment.kt
index e82ce5319..7bda750cf 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/filelist/MainFileListFragment.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/filelist/MainFileListFragment.kt
@@ -66,7 +66,7 @@ import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import eu.opencloud.android.R
import eu.opencloud.android.databinding.MainFileListFragmentBinding
-import eu.opencloud.android.datamodel.ThumbnailsCacheManager
+
import eu.opencloud.android.domain.appregistry.model.AppRegistryMimeType
import eu.opencloud.android.domain.exceptions.InstanceNotConfiguredException
import eu.opencloud.android.domain.exceptions.TooEarlyException
@@ -606,51 +606,7 @@ class MainFileListFragment : Fragment(),
dialog.dismiss()
}
- val thumbnailBottomSheet = fileOptionsBottomSheetSingleFile.findViewById(R.id.thumbnail_bottom_sheet)
- if (file.isFolder) {
- // Folder
- thumbnailBottomSheet.setImageResource(R.drawable.ic_menu_archive)
- } else {
- // Set file icon depending on its mimetype. Ask for thumbnail later.
- thumbnailBottomSheet.setImageResource(
- MimetypeIconUtil.getFileTypeIconId(file.mimeType, file.fileName)
- )
- if (file.remoteId != null) {
- val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(file.remoteId)
- if (thumbnail != null) {
- thumbnailBottomSheet.setImageBitmap(thumbnail)
- }
- if (file.needsToUpdateThumbnail &&
- ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, thumbnailBottomSheet)
- ) {
- // generate new Thumbnail
- val task = ThumbnailsCacheManager.ThumbnailGenerationTask(
- thumbnailBottomSheet,
- AccountUtils.getCurrentOpenCloudAccount(requireContext())
- )
- val asyncDrawable = ThumbnailsCacheManager.AsyncThumbnailDrawable(
- resources,
- thumbnail,
- task
- )
-
- // If drawable is not visible, do not update it.
- if (asyncDrawable.minimumHeight > 0 && asyncDrawable.minimumWidth > 0) {
- thumbnailBottomSheet.setImageDrawable(asyncDrawable)
- }
- task.execute(file)
- }
-
- if (file.mimeType == "image/png") {
- thumbnailBottomSheet.setBackgroundColor(
- ContextCompat.getColor(requireContext(), R.color.background_color)
- )
- }
- }
- }
- val fileNameBottomSheet = fileOptionsBottomSheetSingleFile.findViewById(R.id.file_name_bottom_sheet)
- fileNameBottomSheet.text = file.fileName
val fileSizeBottomSheet = fileOptionsBottomSheetSingleFile.findViewById(R.id.file_size_bottom_sheet)
fileSizeBottomSheet.text = DisplayUtils.bytesToHumanReadable(file.length, requireContext(), true)
@@ -836,9 +792,10 @@ class MainFileListFragment : Fragment(),
val spaceSpecialImage = fileListUiState.space?.getSpaceSpecialImage()
if (spaceSpecialImage != null) {
+ val account = AccountUtils.getCurrentOpenCloudAccount(requireContext())
binding.spaceHeader.spaceHeaderImage.load(
ThumbnailsRequester.getPreviewUriForSpaceSpecial(spaceSpecialImage),
- ThumbnailsRequester.getCoilImageLoader()
+ if (account != null) ThumbnailsRequester.getCoilImageLoader(account) else ThumbnailsRequester.getCoilImageLoader()
) {
placeholder(R.drawable.ic_spaces)
error(R.drawable.ic_spaces)
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/removefile/RemoveFilesDialogFragment.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/removefile/RemoveFilesDialogFragment.kt
index af3baf9dd..a1008dc0a 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/removefile/RemoveFilesDialogFragment.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/files/removefile/RemoveFilesDialogFragment.kt
@@ -30,7 +30,9 @@ import android.widget.ImageView
import androidx.fragment.app.DialogFragment
import eu.opencloud.android.R
import eu.opencloud.android.databinding.RemoveFilesDialogBinding
-import eu.opencloud.android.datamodel.ThumbnailsCacheManager
+import coil.load
+import eu.opencloud.android.presentation.thumbnails.ThumbnailsRequester
+import eu.opencloud.android.presentation.authentication.AccountUtils
import eu.opencloud.android.domain.files.model.OCFile
import eu.opencloud.android.presentation.files.operations.FileOperation
import eu.opencloud.android.presentation.files.operations.FileOperationsViewModel
@@ -121,13 +123,11 @@ class RemoveFilesDialogFragment : DialogFragment() {
if (files.size == 1) {
val file = files[0]
// Show the thumbnail when the file has one
- val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(file.remoteId)
- if (thumbnail != null) {
- thumbnailImageView.setImageBitmap(thumbnail)
- } else {
- thumbnailImageView.setImageResource(
- MimetypeIconUtil.getFileTypeIconId(file.mimeType, file.fileName)
- )
+ val account = AccountUtils.getCurrentOpenCloudAccount(requireContext())
+ thumbnailImageView.load(ThumbnailsRequester.getPreviewUriForFile(file, account), ThumbnailsRequester.getCoilImageLoader(account)) {
+ placeholder(MimetypeIconUtil.getFileTypeIconId(file.mimeType, file.fileName))
+ error(MimetypeIconUtil.getFileTypeIconId(file.mimeType, file.fileName))
+ crossfade(true)
}
} else {
thumbnailImageView.visibility = View.GONE
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/sharing/ShareFileFragment.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/sharing/ShareFileFragment.kt
index 0163f24bc..5782b58c3 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/sharing/ShareFileFragment.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/sharing/ShareFileFragment.kt
@@ -37,7 +37,8 @@ import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import eu.opencloud.android.R
import eu.opencloud.android.databinding.ShareFileLayoutBinding
-import eu.opencloud.android.datamodel.ThumbnailsCacheManager
+import coil.load
+import eu.opencloud.android.presentation.thumbnails.ThumbnailsRequester
import eu.opencloud.android.domain.capabilities.model.CapabilityBooleanType
import eu.opencloud.android.domain.capabilities.model.OCCapability
import eu.opencloud.android.domain.files.model.OCFile
@@ -239,13 +240,15 @@ class ShareFileFragment : Fragment(), ShareUserListAdapter.ShareUserAdapterListe
)
)
if (file!!.isImage) {
- val remoteId = file?.remoteId.toString()
- val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(remoteId)
- if (thumbnail != null) {
- binding.shareFileIcon.setImageBitmap(thumbnail)
+ binding.shareFileIcon.load(
+ ThumbnailsRequester.getPreviewUriForFile(file!!, account!!),
+ ThumbnailsRequester.getCoilImageLoader(account!!)
+ ) {
+ placeholder(MimetypeIconUtil.getFileTypeIconId(file!!.mimeType, file!!.fileName))
+ error(MimetypeIconUtil.getFileTypeIconId(file!!.mimeType, file!!.fileName))
+ crossfade(true)
}
}
- // Name
binding.shareFileName.text = file?.fileName
// Size
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/spaces/SpacesListAdapter.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/spaces/SpacesListAdapter.kt
index 94ce04488..c3076245a 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/spaces/SpacesListAdapter.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/spaces/SpacesListAdapter.kt
@@ -73,9 +73,10 @@ class SpacesListAdapter(
val spaceSpecialImage = space.getSpaceSpecialImage()
if (spaceSpecialImage != null) {
+ val account = eu.opencloud.android.presentation.authentication.AccountUtils.getCurrentOpenCloudAccount(holder.itemView.context)
spacesListItemImage.load(
ThumbnailsRequester.getPreviewUriForSpaceSpecial(spaceSpecialImage),
- ThumbnailsRequester.getCoilImageLoader()
+ if (account != null) ThumbnailsRequester.getCoilImageLoader(account) else ThumbnailsRequester.getCoilImageLoader()
) {
placeholder(R.drawable.ic_spaces_placeholder)
error(R.drawable.ic_spaces_placeholder)
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/thumbnails/ThumbnailsRequester.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/thumbnails/ThumbnailsRequester.kt
index 124786849..728f3efde 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/thumbnails/ThumbnailsRequester.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/thumbnails/ThumbnailsRequester.kt
@@ -21,14 +21,16 @@
package eu.opencloud.android.presentation.thumbnails
import android.accounts.Account
+import android.accounts.AccountManager
import android.net.Uri
import coil.ImageLoader
import coil.disk.DiskCache
import coil.memory.MemoryCache
import coil.util.DebugLogger
import eu.opencloud.android.MainApp.Companion.appContext
-import eu.opencloud.android.R
import eu.opencloud.android.data.ClientManager
+import java.util.concurrent.ConcurrentHashMap
+import eu.opencloud.android.domain.files.model.OCFile
import eu.opencloud.android.domain.files.model.OCFileWithSyncInfo
import eu.opencloud.android.domain.spaces.model.SpaceSpecial
import eu.opencloud.android.lib.common.SingleSessionManager
@@ -46,91 +48,128 @@ import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import timber.log.Timber
import java.util.Locale
-import kotlin.math.roundToInt
object ThumbnailsRequester : KoinComponent {
private val clientManager: ClientManager by inject()
private const val SPACE_SPECIAL_PREVIEW_URI = "%s?scalingup=0&a=1&x=%d&y=%d&c=%s&preview=1"
- private const val FILE_PREVIEW_URI = "%s%s?x=%d&y=%d&c=%s&preview=1&id=%s"
+ private const val FILE_PREVIEW_URI = "%s/remote.php/webdav%s?x=%d&y=%d&c=%s&preview=1"
- private const val DISK_CACHE_SIZE: Long = 1024 * 1024 * 10 // 10MB
+ private const val DISK_CACHE_SIZE: Long = 1024 * 1024 * 100 // 100MB
- fun getCoilImageLoader(): ImageLoader {
- val openCloudClient = getOpenCloudClient()
+ private val imageLoaders = ConcurrentHashMap()
+ private var sharedDiskCache: DiskCache? = null
+ private var sharedMemoryCache: MemoryCache? = null
- val coilRequestHeaderInterceptor = CoilRequestHeaderInterceptor(
- requestHeaders = hashMapOf(
- AUTHORIZATION_HEADER to openCloudClient.credentials.headerAuth,
- ACCEPT_ENCODING_HEADER to ACCEPT_ENCODING_IDENTITY,
- USER_AGENT_HEADER to SingleSessionManager.getUserAgent(),
- OC_X_REQUEST_ID to RandomUtils.generateRandomUUID(),
- )
- )
-
- return ImageLoader(appContext).newBuilder().okHttpClient(
- okHttpClient = openCloudClient.okHttpClient.newBuilder().addNetworkInterceptor(coilRequestHeaderInterceptor).build()
- ).logger(DebugLogger())
- .memoryCache {
- MemoryCache.Builder(appContext)
- .maxSizePercent(0.1)
- .build()
- }
- .diskCache {
- DiskCache.Builder()
- .directory(appContext.cacheDir.resolve("thumbnails_coil_cache"))
- .maxSizeBytes(DISK_CACHE_SIZE)
- .build()
- }
- .build()
+ private fun getSharedDiskCache(): DiskCache {
+ if (sharedDiskCache == null) {
+ sharedDiskCache = DiskCache.Builder()
+ .directory(appContext.cacheDir.resolve("thumbnails_coil_cache"))
+ .maxSizeBytes(DISK_CACHE_SIZE)
+ .build()
+ }
+ return sharedDiskCache!!
}
- fun getPreviewUriForSpaceSpecial(spaceSpecial: SpaceSpecial): String =
- String.format(
- Locale.ROOT,
- SPACE_SPECIAL_PREVIEW_URI,
- spaceSpecial.webDavUrl,
- appContext.resources.getDimension(R.dimen.spaces_thumbnail_height).roundToInt(),
- appContext.resources.getDimension(R.dimen.spaces_thumbnail_height).roundToInt(),
- spaceSpecial.eTag
- )
-
- @Suppress("ExpressionBodySyntax")
- fun getPreviewUriForFile(ocFile: OCFileWithSyncInfo, account: Account): String {
- var baseUrl = getOpenCloudClient().baseUri.toString() + "/remote.php/dav/files/" + account.name.split("@".toRegex())
- .dropLastWhile { it.isEmpty() }
- .toTypedArray()[0]
- ocFile.space?.getSpaceSpecialImage()?.let {
- baseUrl = it.webDavUrl
+ private fun getSharedMemoryCache(): MemoryCache {
+ if (sharedMemoryCache == null) {
+ sharedMemoryCache = MemoryCache.Builder(appContext)
+ .maxSizePercent(0.25)
+ .build()
}
+ return sharedMemoryCache!!
+ }
+
+ fun getAvatarUri(account: Account): String {
+ val accountManager = AccountManager.get(appContext)
+ val baseUrl = accountManager.getUserData(account, eu.opencloud.android.lib.common.accounts.AccountUtils.Constants.KEY_OC_BASE_URL)
+ val username = AccountUtils.getUsernameOfAccount(account.name)
+ return "$baseUrl/index.php/avatar/${android.net.Uri.encode(username)}/384"
+ }
+
+ fun getPreviewUriForFile(file: OCFile, account: Account, etag: String? = null): String =
+ getPreviewUri(file.remotePath, etag ?: file.etag, account)
+
+ fun getPreviewUriForFile(fileWithSyncInfo: OCFileWithSyncInfo, account: Account): String =
+ getPreviewUriForFile(fileWithSyncInfo.file, account)
+
+ fun getPreviewUriForSpaceSpecial(spaceSpecial: SpaceSpecial): String =
+ String.format(Locale.US, SPACE_SPECIAL_PREVIEW_URI, spaceSpecial.webDavUrl, 1024, 1024, spaceSpecial.eTag)
+
+ private fun getPreviewUri(remotePath: String?, etag: String?, account: Account): String {
+ val accountManager = AccountManager.get(appContext)
+ val baseUrl = accountManager.getUserData(account, eu.opencloud.android.lib.common.accounts.AccountUtils.Constants.KEY_OC_BASE_URL)
+ val path = if (remotePath?.startsWith("/") == true) remotePath else "/$remotePath"
+ val encodedPath = Uri.encode(path, "/")
+
+ return String.format(Locale.US, FILE_PREVIEW_URI, baseUrl, encodedPath, 1024, 1024, etag)
+ }
- // Converts dp to pixel
- val fileThumbnailSize = appContext.resources.getDimension(R.dimen.file_icon_size_grid).roundToInt()
- return String.format(
- Locale.ROOT,
- FILE_PREVIEW_URI,
- baseUrl,
- Uri.encode(ocFile.file.remotePath, "/"),
- fileThumbnailSize,
- fileThumbnailSize,
- ocFile.file.etag,
- "${ocFile.file.remoteId}${ocFile.file.modificationTimestamp}",
- )
+ fun getCoilImageLoader(): ImageLoader {
+ val account = AccountUtils.getCurrentOpenCloudAccount(appContext)
+ return getCoilImageLoader(account)
}
- private fun getOpenCloudClient() = clientManager.getClientForCoilThumbnails(
- accountName = AccountUtils.getCurrentOpenCloudAccount(appContext).name
- )
+ fun getCoilImageLoader(account: Account): ImageLoader {
+ val accountName = account.name
+ return imageLoaders.getOrPut(accountName) {
+ val openCloudClient = clientManager.getClientForCoilThumbnails(accountName)
+
+ val coilRequestHeaderInterceptor = CoilRequestHeaderInterceptor(
+ clientManager = clientManager,
+ accountName = accountName
+ )
+
+ ImageLoader(appContext).newBuilder().okHttpClient(
+ okHttpClient = openCloudClient.okHttpClient.newBuilder()
+ .addNetworkInterceptor(coilRequestHeaderInterceptor).build()
+ ).logger(DebugLogger())
+ .memoryCache {
+ getSharedMemoryCache()
+ }
+ .diskCache {
+ getSharedDiskCache()
+ }
+ .build()
+ }
+ }
private class CoilRequestHeaderInterceptor(
- private val requestHeaders: HashMap
+ private val clientManager: ClientManager,
+ private val accountName: String
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
+ val openCloudClient = clientManager.getClientForCoilThumbnails(accountName)
+ val requestHeaders = hashMapOf(
+ AUTHORIZATION_HEADER to openCloudClient.credentials.headerAuth,
+ ACCEPT_ENCODING_HEADER to ACCEPT_ENCODING_IDENTITY,
+ USER_AGENT_HEADER to SingleSessionManager.getUserAgent(),
+ OC_X_REQUEST_ID to RandomUtils.generateRandomUUID(),
+ )
+
val request = chain.request().newBuilder()
requestHeaders.toHeaders().forEach { request.addHeader(it.first, it.second) }
- return chain.proceed(request.build()).newBuilder().removeHeader("Cache-Control")
- .addHeader("Cache-Control", "max-age=5000 , must-revalidate, value").build().also { Timber.d("Header :" + it.headers) }
+ val response = chain.proceed(request.build())
+ var builder = response.newBuilder()
+ var changed = false
+
+ val cacheControl = response.header("Cache-Control")
+ if (cacheControl.isNullOrEmpty() || cacheControl.contains("no-cache")) {
+ builder.removeHeader("Cache-Control")
+ builder.addHeader("Cache-Control", "max-age=5000, must-revalidate")
+ changed = true
+ }
+
+ if (chain.request().url.toString().contains("/avatar/") && response.header("Content-Type").isNullOrEmpty()) {
+ builder.addHeader("Content-Type", "image/png")
+ changed = true
+ }
+
+ if (changed) {
+ return builder.build().also { Timber.d("Header :" + it.headers) }
+ }
+ return response
}
}
}
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/ui/activity/DrawerActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/ui/activity/DrawerActivity.kt
index eb4ccb9ce..8b55bce2f 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/ui/activity/DrawerActivity.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/ui/activity/DrawerActivity.kt
@@ -78,6 +78,12 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import timber.log.Timber
+import androidx.lifecycle.lifecycleScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import eu.opencloud.android.presentation.thumbnails.ThumbnailsRequester
+
/**
* Base class to handle setup of the drawer implementation including avatar fetching and fallback
* generation.
@@ -451,11 +457,17 @@ abstract class DrawerActivity : ToolbarActivity() {
}
getDrawerCurrentAccount()?.let {
- AvatarUtils().loadAvatarForAccount(
- imageView = it,
- account = account,
- displayRadius = currentAccountAvatarRadiusDimension
- )
+ lifecycleScope.launch(Dispatchers.IO) {
+ val imageLoader = ThumbnailsRequester.getCoilImageLoader(account)
+ withContext(Dispatchers.Main) {
+ AvatarUtils().loadAvatarForAccount(
+ imageView = it,
+ account = account,
+ displayRadius = currentAccountAvatarRadiusDimension,
+ imageLoader = imageLoader
+ )
+ }
+ }
drawerViewModel.getUserQuota(account.name)
updateQuota()
}
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/ui/activity/ToolbarActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/ui/activity/ToolbarActivity.kt
index a2a7bd000..c32471a36 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/ui/activity/ToolbarActivity.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/ui/activity/ToolbarActivity.kt
@@ -42,6 +42,11 @@ import eu.opencloud.android.presentation.accounts.ManageAccountsDialogFragment
import eu.opencloud.android.presentation.accounts.ManageAccountsDialogFragment.Companion.MANAGE_ACCOUNTS_DIALOG
import eu.opencloud.android.presentation.authentication.AccountUtils
import eu.opencloud.android.presentation.avatar.AvatarUtils
+import androidx.lifecycle.lifecycleScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import eu.opencloud.android.presentation.thumbnails.ThumbnailsRequester
/**
* Base class providing toolbar registration functionality, see [.setupToolbar].
@@ -112,12 +117,18 @@ abstract class ToolbarActivity : BaseActivity() {
AccountUtils.getCurrentOpenCloudAccount(baseContext) ?: return
if (isAvatarRequested) {
- AvatarUtils().loadAvatarForAccount(
- avatarView,
- AccountUtils.getCurrentOpenCloudAccount(baseContext),
- true,
- baseContext.resources.getDimension(R.dimen.toolbar_avatar_radius)
- )
+ lifecycleScope.launch(Dispatchers.IO) {
+ val account = AccountUtils.getCurrentOpenCloudAccount(baseContext)
+ val imageLoader = ThumbnailsRequester.getCoilImageLoader(account)
+ withContext(Dispatchers.Main) {
+ AvatarUtils().loadAvatarForAccount(
+ avatarView,
+ account,
+ baseContext.resources.getDimension(R.dimen.toolbar_avatar_radius),
+ imageLoader
+ )
+ }
+ }
}
avatarView.setOnClickListener {
val dialog = ManageAccountsDialogFragment.newInstance(AccountUtils.getCurrentOpenCloudAccount(applicationContext))
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/ui/adapter/DiskLruImageCache.java b/opencloudApp/src/main/java/eu/opencloud/android/ui/adapter/DiskLruImageCache.java
deleted file mode 100644
index a823281d3..000000000
--- a/opencloudApp/src/main/java/eu/opencloud/android/ui/adapter/DiskLruImageCache.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * openCloud Android client application
- *
- * @author Christian Schabesberger
- * Copyright (C) 2020 ownCloud GmbH.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package eu.opencloud.android.ui.adapter;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.BitmapFactory;
-
-import com.jakewharton.disklrucache.DiskLruCache;
-import timber.log.Timber;
-
-public class DiskLruImageCache {
-
- private final DiskLruCache mDiskCache;
- private final CompressFormat mCompressFormat;
- private final int mCompressQuality;
- private static final int CACHE_VERSION = 2;
- private static final int VALUE_COUNT = 1;
- private static final int IO_BUFFER_SIZE = 8 * 1024;
-
- //public DiskLruImageCache( Context context,String uniqueName, int diskCacheSize,
- public DiskLruImageCache(
- File diskCacheDir, int diskCacheSize, CompressFormat compressFormat, int quality
- ) throws IOException {
-
- mDiskCache = DiskLruCache.open(
- diskCacheDir, CACHE_VERSION, VALUE_COUNT, diskCacheSize
- );
- mCompressFormat = compressFormat;
- mCompressQuality = quality;
- }
-
- private boolean writeBitmapToFile(Bitmap bitmap, DiskLruCache.Editor editor)
- throws IOException {
- OutputStream out = null;
- try {
- out = new BufferedOutputStream(editor.newOutputStream(0), IO_BUFFER_SIZE);
- return bitmap.compress(mCompressFormat, mCompressQuality, out);
- } finally {
- if (out != null) {
- out.close();
- }
- }
- }
-
- public void put(String key, Bitmap data) {
-
- DiskLruCache.Editor editor = null;
- String validKey = convertToValidKey(key);
- try {
- editor = mDiskCache.edit(validKey);
- if (editor == null) {
- return;
- }
-
- if (writeBitmapToFile(data, editor)) {
- mDiskCache.flush();
- editor.commit();
- Timber.d("cache_test_DISK_ image put on disk cache %s", validKey);
- } else {
- editor.abort();
- Timber.d("cache_test_DISK_ ERROR on: image put on disk cache %s", validKey);
- }
- } catch (IOException e) {
- Timber.w("cache_test_DISK_ ERROR on: image put on disk cache %s", validKey);
- try {
- if (editor != null) {
- editor.abort();
- }
- } catch (IOException ignored) {
- }
- }
- }
-
- public Bitmap getBitmap(String key) {
-
- Bitmap bitmap = null;
- DiskLruCache.Snapshot snapshot = null;
- String validKey = convertToValidKey(key);
- try {
-
- snapshot = mDiskCache.get(validKey);
- if (snapshot == null) {
- return null;
- }
- final InputStream in = snapshot.getInputStream(0);
- if (in != null) {
- final BufferedInputStream buffIn = new BufferedInputStream(in, IO_BUFFER_SIZE);
- bitmap = BitmapFactory.decodeStream(buffIn);
- }
- } catch (IOException e) {
- Timber.e(e);
- } finally {
- if (snapshot != null) {
- snapshot.close();
- }
- }
-
- Timber.d(bitmap == null ? "not found" : "image read from disk %s", validKey);
-
- return bitmap;
-
- }
-
- private String convertToValidKey(String key) {
- return Integer.toString(key.hashCode());
- }
-
- /**
- * Remove passed key from cache
- *
- * @param key
- */
- public void removeKey(String key) {
- String validKey = convertToValidKey(key);
- try {
- mDiskCache.remove(validKey);
- Timber.d("removeKey from cache: %s", validKey);
- } catch (IOException e) {
- Timber.e(e);
- }
- }
-}
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/ui/adapter/ReceiveExternalFilesAdapter.java b/opencloudApp/src/main/java/eu/opencloud/android/ui/adapter/ReceiveExternalFilesAdapter.java
index ec7f97096..a295f3405 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/ui/adapter/ReceiveExternalFilesAdapter.java
+++ b/opencloudApp/src/main/java/eu/opencloud/android/ui/adapter/ReceiveExternalFilesAdapter.java
@@ -25,7 +25,7 @@
import android.accounts.Account;
import android.content.Context;
-import android.graphics.Bitmap;
+
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -36,8 +36,8 @@
import eu.opencloud.android.R;
import eu.opencloud.android.datamodel.FileDataStorageManager;
-import eu.opencloud.android.datamodel.ThumbnailsCacheManager;
-import eu.opencloud.android.datamodel.ThumbnailsCacheManager.AsyncThumbnailDrawable;
+import eu.opencloud.android.presentation.thumbnails.ThumbnailsRequester;
+import coil.ImageLoader;
import eu.opencloud.android.db.PreferenceManager;
import eu.opencloud.android.domain.files.model.OCFile;
import eu.opencloud.android.extensions.VectorExtKt;
@@ -147,30 +147,23 @@ public View getView(int position, View convertView, ViewGroup parent) {
// get Thumbnail if file is image
if (file.isImage() && file.getRemoteId() != null) {
- // Thumbnail in Cache?
- Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
- String.valueOf(file.getRemoteId())
- );
- if (thumbnail != null && !file.getNeedsToUpdateThumbnail()) {
- fileIcon.setImageBitmap(thumbnail);
- } else {
- // generate new Thumbnail
- if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, fileIcon)) {
- final ThumbnailsCacheManager.ThumbnailGenerationTask task =
- new ThumbnailsCacheManager.ThumbnailGenerationTask(fileIcon, mAccount);
- if (thumbnail == null) {
- thumbnail = ThumbnailsCacheManager.mDefaultImg;
- }
- final AsyncThumbnailDrawable asyncDrawable = new AsyncThumbnailDrawable(
- mContext.getResources(),
- thumbnail,
- task
- );
- fileIcon.setImageDrawable(asyncDrawable);
- task.execute(file);
- }
- }
+ String uri = ThumbnailsRequester.INSTANCE.getPreviewUriForFile(file, mAccount, null);
+ ImageLoader imageLoader = ThumbnailsRequester.INSTANCE.getCoilImageLoader(mAccount);
+ coil.request.ImageRequest request = new coil.request.ImageRequest.Builder(mContext)
+ .data(uri)
+ .target(fileIcon)
+ .placeholder(MimetypeIconUtil.getFileTypeIconId(file.getMimeType(), file.getFileName()))
+ .error(MimetypeIconUtil.getFileTypeIconId(file.getMimeType(), file.getFileName()))
+ .crossfade(true)
+ .build();
+ imageLoader.enqueue(request);
} else {
+ ImageLoader imageLoader = ThumbnailsRequester.INSTANCE.getCoilImageLoader(mAccount);
+ coil.request.ImageRequest request = new coil.request.ImageRequest.Builder(mContext)
+ .data(null)
+ .target(fileIcon)
+ .build();
+ imageLoader.enqueue(request);
fileIcon.setImageResource(
MimetypeIconUtil.getFileTypeIconId(file.getMimeType(), file.getFileName())
);
diff --git a/opencloudApp/src/main/res/layout/opencloud_toolbar.xml b/opencloudApp/src/main/res/layout/opencloud_toolbar.xml
index 886f0a234..29dfa2b96 100644
--- a/opencloudApp/src/main/res/layout/opencloud_toolbar.xml
+++ b/opencloudApp/src/main/res/layout/opencloud_toolbar.xml
@@ -80,7 +80,6 @@
android:layout_marginHorizontal="@dimen/standard_half_margin"
android:padding="@dimen/standard_half_padding"
android:src="@drawable/ic_account_circle"
- android:tint="@color/primary"
android:contentDescription="@string/content_description_manage_accounts"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/opencloudData/src/main/java/eu/opencloud/android/data/ClientManager.kt b/opencloudData/src/main/java/eu/opencloud/android/data/ClientManager.kt
index e2356ff11..d9d65efc8 100644
--- a/opencloudData/src/main/java/eu/opencloud/android/data/ClientManager.kt
+++ b/opencloudData/src/main/java/eu/opencloud/android/data/ClientManager.kt
@@ -160,4 +160,6 @@ class ClientManager(
val openCloudClient = getClientForAccount(accountName)
return OCAppRegistryService(client = openCloudClient)
}
+
+
}
diff --git a/opencloudData/src/main/java/eu/opencloud/android/data/files/repository/OCFileRepository.kt b/opencloudData/src/main/java/eu/opencloud/android/data/files/repository/OCFileRepository.kt
index 20452995b..bc43633e2 100644
--- a/opencloudData/src/main/java/eu/opencloud/android/data/files/repository/OCFileRepository.kt
+++ b/opencloudData/src/main/java/eu/opencloud/android/data/files/repository/OCFileRepository.kt
@@ -347,7 +347,7 @@ class OCFileRepository(
it.copy(spaceId = spaceId)
}
val remoteFolder = fetchFolderResult.first()
- val remoteFolderContent = fetchFolderResult.drop(1)
+ val remoteFolderContent = fetchFolderResult.drop(1).distinctBy { it.remotePath }
// Final content for this folder, we will update the folder content all together
val folderContentUpdated = mutableListOf()