From 3b9e9c1488564088c7aea89c3b88cf0c7cc3ccaa Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 23 Sep 2026 17:15:29 +0200 Subject: [PATCH] refactor(display-util): avatar Signed-off-by: alperozturk96 --- .../android/ui/dialog/DialogFragmentIT.kt | 3 +- .../ui/ChooseAccountDialogFragment.kt | 15 +- .../ui/fileactions/FileActionsBottomSheet.kt | 13 +- .../utils/avatar/AvatarGenerationListener.kt | 16 ++ .../nextcloud/utils/avatar/AvatarGenerator.kt | 124 +++++++++++++ .../nextcloud/utils/avatar/AvatarLoader.kt | 154 ++++++++++++++++ .../nextcloud/utils/avatar/AvatarRequest.kt | 18 ++ .../utils/text/RichSubjectFormatter.kt | 19 +- .../datamodel/ThumbnailsCacheManager.java | 169 ------------------ .../owncloud/android/ui/AvatarGroupLayout.kt | 24 +-- .../adapter/ActivityAndVersionListAdapter.kt | 7 +- .../activities/adapter/ActivityListAdapter.kt | 8 +- .../android/ui/activity/DrawerActivity.java | 7 +- .../ui/activity/FileDisplayActivity.kt | 7 +- .../ui/activity/ManageAccountsActivity.kt | 9 +- .../android/ui/activity/UserInfoActivity.java | 10 +- .../ui/adapter/NotificationListAdapter.kt | 6 +- .../android/ui/adapter/OCFileListAdapter.java | 11 +- .../android/ui/adapter/ShareViewHolder.java | 24 +-- .../android/ui/adapter/ShareeListAdapter.kt | 13 +- .../android/ui/adapter/UserListAdapter.java | 23 +-- .../adapter/helper/AvatarShareesProvider.kt | 42 ----- .../asynctasks/RetrieveHoverCardAsyncTask.kt | 8 +- .../android/ui/dialog/AccountRemovalDialog.kt | 12 +- .../ui/dialog/MultipleAccountsDialog.kt | 8 +- .../android/ui/fragment/ActivitiesFragment.kt | 7 +- .../fragment/FileDetailActivitiesFragment.kt | 22 ++- .../ui/fragment/FileDetailSharingFragment.kt | 23 +-- .../ui/fragment/OCFileListFragment.java | 5 +- .../ui/fragment/ProfileBottomSheetDialog.kt | 16 +- .../notifications/NotificationsFragment.kt | 8 +- .../owncloud/android/utils/DisplayUtils.java | 142 --------------- .../ui/adapter/ShareeListAdapterTest.kt | 4 +- .../ui/adapter/UserListAdapterTest.java | 10 +- 34 files changed, 505 insertions(+), 482 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/utils/avatar/AvatarGenerationListener.kt create mode 100644 app/src/main/java/com/nextcloud/utils/avatar/AvatarGenerator.kt create mode 100644 app/src/main/java/com/nextcloud/utils/avatar/AvatarLoader.kt create mode 100644 app/src/main/java/com/nextcloud/utils/avatar/AvatarRequest.kt diff --git a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt index cbb1a3e649fd..9112027bde61 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt @@ -612,7 +612,8 @@ class DialogFragmentIT : AbstractIT() { fda, user, hoverCard, - fda.viewThemeUtils + fda.viewThemeUtils, + fda.avatarGenerator ) sut.show() } diff --git a/app/src/main/java/com/nextcloud/ui/ChooseAccountDialogFragment.kt b/app/src/main/java/com/nextcloud/ui/ChooseAccountDialogFragment.kt index efd2c7bb4e0a..00ff3917ae46 100644 --- a/app/src/main/java/com/nextcloud/ui/ChooseAccountDialogFragment.kt +++ b/app/src/main/java/com/nextcloud/ui/ChooseAccountDialogFragment.kt @@ -24,6 +24,8 @@ import com.nextcloud.client.account.User import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.di.Injectable import com.nextcloud.client.network.ClientFactory +import com.nextcloud.utils.avatar.AvatarGenerationListener +import com.nextcloud.utils.avatar.AvatarGenerator import com.nextcloud.utils.extensions.getParcelableArgument import com.nextcloud.utils.mdm.MDMConfig import com.owncloud.android.R @@ -36,7 +38,6 @@ import com.owncloud.android.ui.activity.DrawerActivity import com.owncloud.android.ui.adapter.UserListAdapter import com.owncloud.android.ui.adapter.UserListItem import com.owncloud.android.utils.DisplayUtils -import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener import com.owncloud.android.utils.theme.ViewThemeUtils import kotlinx.coroutines.launch import javax.inject.Inject @@ -64,6 +65,9 @@ class ChooseAccountDialogFragment : @Inject lateinit var viewThemeUtils: ViewThemeUtils + @Inject + lateinit var avatarGenerator: AvatarGenerator + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { @@ -92,13 +96,11 @@ class ChooseAccountDialogFragment : // Defining user picture binding.currentAccount.userIcon.tag = user.accountName - DisplayUtils.setAvatar( + avatarGenerator.setAccountAvatar( user, this, resources.getDimension(R.dimen.list_item_avatar_icon_radius), - resources, - binding.currentAccount.userIcon, - context + binding.currentAccount.userIcon ) // Defining user texts, accounts, etc. @@ -119,7 +121,8 @@ class ChooseAccountDialogFragment : false, false, true, - viewThemeUtils + viewThemeUtils, + avatarGenerator ) if (!MDMConfig.multiAccountSupport(requireContext())) { 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 5a9bb17c74a9..237f72abb8ba 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt @@ -35,6 +35,8 @@ import com.nextcloud.client.account.CurrentAccountProvider import com.nextcloud.client.di.Injectable import com.nextcloud.client.di.ViewModelFactory import com.nextcloud.utils.SnackbarUtil +import com.nextcloud.utils.avatar.AvatarGenerationListener +import com.nextcloud.utils.avatar.AvatarGenerator import com.nextcloud.utils.extensions.setVisibleIf import com.nextcloud.utils.thumbnail.ThumbnailArguments import com.owncloud.android.R @@ -46,7 +48,6 @@ import com.owncloud.android.datamodel.SyncedFolderProvider 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.nextcloud.utils.thumbnail.ThumbnailGenerator import com.owncloud.android.utils.theme.ViewThemeUtils @@ -74,6 +75,9 @@ class FileActionsBottomSheet : @Inject lateinit var thumbnailGenerator: ThumbnailGenerator + @Inject + lateinit var avatarGenerator: AvatarGenerator + private lateinit var viewModel: FileActionsViewModel private var _binding: FileActionsBottomSheetBinding? = null @@ -279,14 +283,11 @@ class FileActionsBottomSheet : override fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean = false } - DisplayUtils.setAvatar( - currentUserProvider.user, + avatarGenerator.setUserAvatar( lockInfo.lockedBy, listener, resources.getDimension(R.dimen.list_item_avatar_icon_radius), - resources, - this, - requireContext() + this ) } diff --git a/app/src/main/java/com/nextcloud/utils/avatar/AvatarGenerationListener.kt b/app/src/main/java/com/nextcloud/utils/avatar/AvatarGenerationListener.kt new file mode 100644 index 000000000000..b7a209d95e46 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/avatar/AvatarGenerationListener.kt @@ -0,0 +1,16 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.avatar + +import android.graphics.drawable.Drawable + +interface AvatarGenerationListener { + fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any?) + + fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean +} diff --git a/app/src/main/java/com/nextcloud/utils/avatar/AvatarGenerator.kt b/app/src/main/java/com/nextcloud/utils/avatar/AvatarGenerator.kt new file mode 100644 index 000000000000..873b4eaca5a9 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/avatar/AvatarGenerator.kt @@ -0,0 +1,124 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.avatar + +import android.accounts.AccountManager +import android.content.Context +import android.graphics.drawable.Drawable +import android.view.View +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import com.nextcloud.client.account.CurrentAccountProvider +import com.nextcloud.client.account.User +import com.nextcloud.client.di.ApplicationScope +import com.nextcloud.client.di.IoDispatcher +import com.nextcloud.client.di.MainDispatcher +import com.owncloud.android.R +import com.owncloud.android.lib.common.accounts.AccountUtils +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.ui.TextDrawable +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.lang.ref.WeakReference +import javax.inject.Inject +import javax.inject.Singleton + +@Suppress("LongParameterList") +@Singleton +class AvatarGenerator @Inject constructor( + private val context: Context, + private val accountManager: AccountManager, + private val currentAccountProvider: CurrentAccountProvider, + private val avatarLoader: AvatarLoader, + @param:ApplicationScope private val scope: CoroutineScope, + @param:IoDispatcher private val ioDispatcher: CoroutineDispatcher, + @param:MainDispatcher private val mainDispatcher: CoroutineDispatcher +) { + + companion object { + private const val TAG = "AvatarGenerator" + private const val MAX_PARALLEL_DOWNLOADS = 4 + } + + private val downloadDispatcher = ioDispatcher.limitedParallelism(MAX_PARALLEL_DOWNLOADS) + + fun setAccountAvatar(user: User, listener: AvatarGenerationListener, avatarRadius: Float, callContext: Any) { + val userId = accountManager.getUserData(user.toPlatformAccount(), AccountUtils.Constants.KEY_USER_ID) + + if (userId == null) { + Log_OC.e(TAG, "user id is null, cannot set avatar") + return + } + + setUserAvatar(userId, listener, avatarRadius, callContext, user = user) + } + + @JvmOverloads + fun setUserAvatar( + userId: String, + listener: AvatarGenerationListener, + avatarRadius: Float, + callContext: Any, + displayName: String? = userId, + user: User = currentAccountProvider.user, + avatarBorder: Int = 0 + ) { + (callContext as? View)?.contentDescription = user.toPlatformAccount().hashCode().toString() + + val request = AvatarRequest(user, userId, displayName, avatarRadius) + val hasUserId = userId.isNotEmpty() + + val placeholder = if (hasUserId) { + createInitialAvatar(displayName, avatarRadius - avatarBorder) + } else { + createLinkAvatar(callContext) + } + listener.avatarGenerated(placeholder, callContext) + + val listenerReference = WeakReference(listener) + scope.launch(ioDispatcher) { + if (hasUserId) { + avatarLoader.loadCached(request)?.let { deliver(listenerReference, request, it, callContext) } + } + + val avatar = withContext(downloadDispatcher) { avatarLoader.load(request) } ?: return@launch + deliver(listenerReference, request, avatar, callContext) + } + } + + private suspend fun deliver( + listenerReference: WeakReference, + request: AvatarRequest, + avatar: Drawable, + callContext: Any + ) = withContext(mainDispatcher) { + val listener = listenerReference.get() ?: return@withContext + + if (listener.shouldCallGeneratedCallback(request.accountName, callContext)) { + listener.avatarGenerated(avatar, callContext) + } + } + + @Suppress("TooGenericExceptionCaught") + private fun createInitialAvatar(displayName: String?, radius: Float): Drawable? = try { + TextDrawable.createAvatarByUserId(displayName, radius) + } catch (e: Exception) { + Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e) + ResourcesCompat.getDrawable(context.resources, R.drawable.account_circle_white, null) + } + + private fun createLinkAvatar(callContext: Any): Drawable? { + val themedContext = (callContext as? View)?.context ?: context + + return ContextCompat.getDrawable(themedContext, R.drawable.ic_link)?.apply { + setTint(ContextCompat.getColor(themedContext, R.color.icon_on_nc_grey)) + } + } +} diff --git a/app/src/main/java/com/nextcloud/utils/avatar/AvatarLoader.kt b/app/src/main/java/com/nextcloud/utils/avatar/AvatarLoader.kt new file mode 100644 index 000000000000..728c5ca66471 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/avatar/AvatarLoader.kt @@ -0,0 +1,154 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.avatar + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.drawable.Drawable +import android.media.ThumbnailUtils +import android.net.Uri +import androidx.core.content.res.ResourcesCompat +import com.nextcloud.client.core.Clock +import com.owncloud.android.R +import com.owncloud.android.datamodel.ArbitraryDataProvider +import com.owncloud.android.datamodel.ThumbnailsCacheManager +import com.owncloud.android.lib.common.OwnCloudClientManagerFactory +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.ui.TextDrawable +import com.owncloud.android.utils.BitmapUtils +import org.apache.commons.httpclient.HttpStatus +import org.apache.commons.httpclient.methods.GetMethod +import javax.inject.Inject +import kotlin.time.Duration.Companion.hours + +@Suppress("TooGenericExceptionCaught") +class AvatarLoader @Inject constructor( + private val context: Context, + private val arbitraryDataProvider: ArbitraryDataProvider, + private val clock: Clock +) { + + companion object { + private const val TAG = "AvatarLoader" + private const val AVATAR_TIMESTAMP = "avatarTimestamp" + private const val ETAG_HEADER = "ETag" + private const val IF_NONE_MATCH_HEADER = "If-None-Match" + private val REFRESH_INTERVAL_MILLIS = 1.hours.inWholeMilliseconds + } + + private val resources get() = context.resources + + fun loadCached(request: AvatarRequest): Drawable? { + val eTag = arbitraryDataProvider.getValue(request.accountName, ThumbnailsCacheManager.AVATAR) + val bitmap = ThumbnailsCacheManager.getBitmapFromDiskCache(request.cacheKey(eTag)) ?: return null + return BitmapUtils.bitmapToCircularBitmapDrawable(resources, bitmap) + } + + fun load(request: AvatarRequest): Drawable? = try { + loadOrDownload(request) + } catch (_: OutOfMemoryError) { + Log_OC.e(TAG, "Out of memory") + null + } catch (t: Throwable) { + Log_OC.e(TAG, "Generation of avatar for ${request.userId} failed", t) + ResourcesCompat.getDrawable(resources, R.drawable.account_circle_white, null) + } + + private fun loadOrDownload(request: AvatarRequest): Drawable? { + val eTag = arbitraryDataProvider.getValue(request.accountName, ThumbnailsCacheManager.AVATAR) + val cached = ThumbnailsCacheManager.getBitmapFromDiskCache(request.cacheKey(eTag)) + + if (cached != null && !isRefreshDue(request)) { + return toDrawable(request, cached) + } + + return try { + download(request, eTag, cached) + } catch (e: Exception) { + Log_OC.e(TAG, "Downloading avatar for ${request.userId} failed", e) + createAccountAvatar(request, cached) + } + } + + private fun isRefreshDue(request: AvatarRequest): Boolean { + val timestamp = arbitraryDataProvider.getLongValue(request.accountName, AVATAR_TIMESTAMP) + return clock.currentTime - timestamp >= REFRESH_INTERVAL_MILLIS + } + + private fun download(request: AvatarRequest, eTag: String, cached: Bitmap?): Drawable? { + val client = OwnCloudClientManagerFactory.getDefaultSingleton() + .getClientFor(request.user.toOwnCloudAccount(), context) + ThumbnailsCacheManager.setClient(client) + + val px = resources.getInteger(R.integer.file_avatar_px) + val get = GetMethod("${client.baseUri}/index.php/avatar/${Uri.encode(request.userId)}/$px") + + try { + if (eTag.isNotEmpty() && cached != null) { + get.setRequestHeader(IF_NONE_MATCH_HEADER, eTag) + } + + return when (client.executeMethod(get)) { + HttpStatus.SC_OK, HttpStatus.SC_CREATED -> readDownloadedAvatar(request, get, px) + + HttpStatus.SC_NOT_MODIFIED -> { + client.exhaustResponse(get.responseBodyAsStream) + storeRefreshTimestamp(request) + toDrawable(request, cached) + } + + else -> { + client.exhaustResponse(get.responseBodyAsStream) + toDrawable(request, cached) + } + } + } finally { + get.releaseConnection() + } + } + + private fun readDownloadedAvatar(request: AvatarRequest, get: GetMethod, px: Int): Drawable? { + val newETag = get.getResponseHeader(ETAG_HEADER)?.value?.replace("\"", "") + newETag?.let { + arbitraryDataProvider.storeOrUpdateKeyValue(request.accountName, ThumbnailsCacheManager.AVATAR, it) + } + + val bitmap = BitmapFactory.decodeStream(get.responseBodyAsStream) + val thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px) + + if (thumbnail == null || newETag.isNullOrEmpty()) { + return TextDrawable.createAvatar(request.user, request.avatarRadius) + } + + val avatar = ThumbnailsCacheManager.handlePNG(thumbnail, px, px) + ThumbnailsCacheManager.addBitmapToCache(request.cacheKey(newETag), avatar) + storeRefreshTimestamp(request) + return toDrawable(request, avatar) + } + + private fun storeRefreshTimestamp(request: AvatarRequest) { + arbitraryDataProvider.storeOrUpdateKeyValue(request.accountName, AVATAR_TIMESTAMP, clock.currentTime) + } + + private fun createAccountAvatar(request: AvatarRequest, cached: Bitmap?): Drawable? = try { + TextDrawable.createAvatar(request.user, request.avatarRadius) + } catch (e: Exception) { + Log_OC.e(TAG, "Error generating fallback avatar", e) + toDrawable(request, cached) + } + + private fun toDrawable(request: AvatarRequest, bitmap: Bitmap?): Drawable? = + BitmapUtils.bitmapToCircularBitmapDrawable(resources, bitmap) ?: createInitialAvatar(request) + + private fun createInitialAvatar(request: AvatarRequest): Drawable? = try { + TextDrawable.createAvatarByUserId(request.displayName, request.avatarRadius) + } catch (_: Exception) { + ResourcesCompat.getDrawable(resources, R.drawable.ic_user_outline, null) + } +} diff --git a/app/src/main/java/com/nextcloud/utils/avatar/AvatarRequest.kt b/app/src/main/java/com/nextcloud/utils/avatar/AvatarRequest.kt new file mode 100644 index 000000000000..1861529273c7 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/avatar/AvatarRequest.kt @@ -0,0 +1,18 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.avatar + +import com.nextcloud.client.account.User + +data class AvatarRequest(val user: User, val userId: String, val displayName: String?, val avatarRadius: Float) { + val serverName: String = user.accountName.substringAfterLast('@') + + val accountName: String = "$userId@$serverName" + + fun cacheKey(eTag: String): String = "a_${userId}_${serverName}_$eTag" +} diff --git a/app/src/main/java/com/nextcloud/utils/text/RichSubjectFormatter.kt b/app/src/main/java/com/nextcloud/utils/text/RichSubjectFormatter.kt index b7d3684bb453..3b44804e8326 100644 --- a/app/src/main/java/com/nextcloud/utils/text/RichSubjectFormatter.kt +++ b/app/src/main/java/com/nextcloud/utils/text/RichSubjectFormatter.kt @@ -19,13 +19,13 @@ import android.text.style.StyleSpan import android.view.View import androidx.core.content.ContextCompat import com.google.android.material.chip.ChipDrawable -import com.nextcloud.client.account.CurrentAccountProvider +import com.nextcloud.utils.avatar.AvatarGenerationListener +import com.nextcloud.utils.avatar.AvatarGenerator import com.owncloud.android.R -import com.owncloud.android.utils.DisplayUtils import thirdparties.fresco.BetterImageSpan -class RichSubjectFormatter(private val context: Context, private val currentAccountProvider: CurrentAccountProvider) : - DisplayUtils.AvatarGenerationListener { +class RichSubjectFormatter(private val context: Context, private val avatarGenerator: AvatarGenerator) : + AvatarGenerationListener { fun format(richSubject: String, paramForTag: (String) -> RichSubjectParam?): SpannableStringBuilder { var text = richSubject @@ -59,11 +59,11 @@ class RichSubjectFormatter(private val context: Context, private val currentAcco return ssb } - override fun avatarGenerated(avatarDrawable: Drawable, callContext: Any) { + override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any?) { (callContext as? ChipDrawable)?.chipIcon = avatarDrawable } - override fun shouldCallGeneratedCallback(tag: String, callContext: Any): Boolean = true + override fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean = true private fun SpannableStringBuilder.applyMentionSpan(param: RichSubjectParam, start: Int, end: Int) { val name = param.name @@ -71,15 +71,12 @@ class RichSubjectFormatter(private val context: Context, private val currentAcco val span = MentionChipSpan(chip, BetterImageSpan.ALIGN_CENTER, param.id.orEmpty(), name) param.id?.let { id -> - DisplayUtils.setAvatar( - currentAccountProvider.user, + avatarGenerator.setUserAvatar( id, - name, this@RichSubjectFormatter, context.resources.getDimension(R.dimen.avatar_icon_radius), - context.resources, chip, - context + displayName = name ) } 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 431822d694c8..ac8fa7cded0d 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -28,7 +28,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.provider.MediaStore; -import android.text.TextUtils; import android.view.Display; import android.view.View; import android.view.WindowManager; @@ -52,12 +51,10 @@ import com.owncloud.android.lib.resources.files.model.ImageDimension; import com.owncloud.android.lib.resources.files.model.ServerFileInterface; import com.owncloud.android.lib.resources.trashbin.model.TrashbinFile; -import com.owncloud.android.ui.TextDrawable; import com.owncloud.android.ui.adapter.DiskLruImageCache; import com.owncloud.android.ui.fragment.FileFragment; import com.owncloud.android.ui.preview.PreviewImageFragment; import com.owncloud.android.utils.BitmapUtils; -import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener; import com.owncloud.android.utils.FileStorageUtils; import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.theme.ViewThemeUtils; @@ -78,7 +75,6 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.core.content.ContextCompat; -import androidx.core.content.res.ResourcesCompat; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import static com.nextcloud.utils.extensions.ThumbnailsCacheManagerExtensionsKt.getExifOrientation; @@ -104,8 +100,6 @@ public final class ThumbnailsCacheManager { private static final String PNG_MIMETYPE = "image/png"; private static final String CACHE_FOLDER = "thumbnailCache"; public static final String AVATAR = "avatar"; - private static final String AVATAR_TIMESTAMP = "avatarTimestamp"; - private static final String ETAG = "ETag"; private static final Object mThumbnailsDiskCacheLock = new Object(); private static volatile DiskLruImageCache mThumbnailCache; @@ -902,169 +896,6 @@ private Bitmap getThumbnailFromMediaRetriever(File file) { } } - public static class AvatarGenerationTask extends AsyncTask { - private final WeakReference mAvatarGenerationListener; - private final Object mCallContext; - private final Resources mResources; - private final float mAvatarRadius; - private final User user; - private final String mUserId; - private final String displayName; - private final String mServerName; - @SuppressLint("StaticFieldLeak") private final Context mContext; - - - public AvatarGenerationTask(AvatarGenerationListener avatarGenerationListener, - Object callContext, - User user, - Resources resources, - float avatarRadius, - String userId, - String displayName, - String serverName, - Context context) { - mAvatarGenerationListener = new WeakReference<>(avatarGenerationListener); - mCallContext = callContext; - this.user = user; - mResources = resources; - mAvatarRadius = avatarRadius; - mUserId = userId; - this.displayName = displayName; - mServerName = serverName; - mContext = context; - } - - @SuppressFBWarnings("Dm") - @Override - protected Drawable doInBackground(String... params) { - Drawable thumbnail = null; - - try { - thumbnail = doAvatarInBackground(); - } catch (OutOfMemoryError oome) { - Log_OC.e(TAG, "Out of memory"); - } catch (Throwable t) { - // the app should never break due to a problem with avatars - thumbnail = ResourcesCompat.getDrawable(mResources, R.drawable.account_circle_white, null); - Log_OC.e(TAG, "Generation of avatar for " + mUserId + " failed", t); - } - - return thumbnail; - } - - protected void onPostExecute(Drawable drawable) { - if (drawable != null) { - AvatarGenerationListener listener = mAvatarGenerationListener.get(); - if (listener != null) { - String accountName = mUserId + "@" + mServerName; - if (listener.shouldCallGeneratedCallback(accountName, mCallContext)) { - listener.avatarGenerated(drawable, mCallContext); - } - } - } - } - - private @NonNull - Drawable doAvatarInBackground() { - Bitmap avatar; - - String accountName = mUserId + "@" + mServerName; - - ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(mContext); - - String eTag = arbitraryDataProvider.getValue(accountName, ThumbnailsCacheManager.AVATAR); - long timestamp = arbitraryDataProvider.getLongValue(accountName, ThumbnailsCacheManager.AVATAR_TIMESTAMP); - String avatarKey = "a_" + mUserId + "_" + mServerName + "_" + eTag; - avatar = getBitmapFromDiskCache(avatarKey); - - // Download avatar from server, only if older than 60 min or avatar does not exist - if (System.currentTimeMillis() - timestamp >= 60 * 60 * 1000 || avatar == null) { - GetMethod get = null; - try { - if (user != null) { - OwnCloudAccount ocAccount = user.toOwnCloudAccount(); - mClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, mContext); - } - - int px = mResources.getInteger(R.integer.file_avatar_px); - String uri = mClient.getBaseUri() + "/index.php/avatar/" + Uri.encode(mUserId) + "/" + px; - Log_OC.d("Avatar", "URI: " + uri); - get = new GetMethod(uri); - - // only use eTag if available and corresponding avatar is still there - // (might be deleted from cache) - if (!eTag.isEmpty() && avatar != null) { - get.setRequestHeader("If-None-Match", eTag); - } - - int status = mClient.executeMethod(get); - - // we are using eTag to download a new avatar only if it changed - switch (status) { - case HttpStatus.SC_OK: - case HttpStatus.SC_CREATED: - // new avatar - InputStream inputStream = get.getResponseBodyAsStream(); - - String newETag = null; - if (get.getResponseHeader(ETAG) != null) { - newETag = get.getResponseHeader(ETAG).getValue().replace("\"", ""); - arbitraryDataProvider.storeOrUpdateKeyValue(accountName, AVATAR, newETag); - } - - Bitmap bitmap = BitmapFactory.decodeStream(inputStream); - avatar = ThumbnailUtils.extractThumbnail(bitmap, px, px); - - // Add avatar to cache - if (avatar != null && !TextUtils.isEmpty(newETag)) { - avatar = handlePNG(avatar, px, px); - String newImageKey = "a_" + mUserId + "_" + mServerName + "_" + newETag; - addBitmapToCache(newImageKey, avatar); - arbitraryDataProvider.storeOrUpdateKeyValue(accountName, - ThumbnailsCacheManager.AVATAR_TIMESTAMP, - System.currentTimeMillis()); - } else { - return TextDrawable.createAvatar(user, mAvatarRadius); - } - break; - - case HttpStatus.SC_NOT_MODIFIED: - // old avatar - mClient.exhaustResponse(get.getResponseBodyAsStream()); - arbitraryDataProvider.storeOrUpdateKeyValue(accountName, - ThumbnailsCacheManager.AVATAR_TIMESTAMP, - System.currentTimeMillis()); - break; - default: - // everything else - mClient.exhaustResponse(get.getResponseBodyAsStream()); - break; - } - } catch (Exception e) { - try { - return TextDrawable.createAvatar(user, mAvatarRadius); - } catch (Exception e1) { - Log_OC.e(TAG, "Error generating fallback avatar"); - } - } finally { - if (get != null) { - get.releaseConnection(); - } - } - } - - if (avatar == null) { - try { - return TextDrawable.createAvatarByUserId(displayName, mAvatarRadius); - } catch (Exception e1) { - return ResourcesCompat.getDrawable(mResources, R.drawable.ic_user_outline, null); - } - } else { - return BitmapUtils.bitmapToCircularBitmapDrawable(mResources, avatar); - } - } - } - public static boolean cancelPotentialThumbnailWork(Object file, ImageView imageView) { final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView); diff --git a/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt b/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt index fc893589c707..5d1c01734823 100644 --- a/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt +++ b/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt @@ -19,12 +19,13 @@ import androidx.core.graphics.drawable.DrawableCompat import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.client.account.User import com.nextcloud.utils.GlideHelper.loadCircularBitmapIntoImageView +import com.nextcloud.utils.avatar.AvatarGenerationListener +import com.nextcloud.utils.avatar.AvatarGenerator import com.owncloud.android.R import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.lib.resources.shares.ShareType import com.owncloud.android.lib.resources.shares.ShareeUser import com.owncloud.android.utils.DisplayUtils -import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener import com.owncloud.android.utils.theme.ViewThemeUtils import kotlin.math.min @@ -63,7 +64,12 @@ class AvatarGroupLayout @JvmOverloads constructor( } @Suppress("LongMethod", "TooGenericExceptionCaught") - fun setAvatars(user: User, sharees: List, viewThemeUtils: ViewThemeUtils) { + fun setAvatars( + user: User, + sharees: List, + viewThemeUtils: ViewThemeUtils, + avatarGenerator: AvatarGenerator + ) { if (sharees == displayedSharees) { return } @@ -126,16 +132,14 @@ class AvatarGroupLayout @JvmOverloads constructor( else -> { avatar.tag = "${sharee.userId}@$serverName" - DisplayUtils.setAvatar( - user, + avatarGenerator.setUserAvatar( sharee.userId!!, - sharee.displayName, this, avatarRadius, - resources, avatar, - context, - avatarBorderSize + displayName = sharee.displayName, + user = user, + avatarBorder = avatarBorderSize ) } } @@ -189,11 +193,11 @@ class AvatarGroupLayout @JvmOverloads constructor( } } - override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any) { + override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any?) { (callContext as ImageView).setImageDrawable(avatarDrawable) } - override fun shouldCallGeneratedCallback(tag: String?, callContext: Any): Boolean = + override fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean = (callContext as ImageView).tag == tag companion object { diff --git a/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityAndVersionListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityAndVersionListAdapter.kt index e0e531f1f337..e8910ab5da6b 100644 --- a/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityAndVersionListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityAndVersionListAdapter.kt @@ -17,19 +17,22 @@ import com.nextcloud.common.NextcloudClient import com.nextcloud.utils.HumanReadableFormatter import com.owncloud.android.databinding.VersionListItemBinding import com.owncloud.android.lib.resources.activities.model.Activity +import com.nextcloud.utils.avatar.AvatarGenerator import com.owncloud.android.lib.resources.files.model.FileVersion import com.owncloud.android.ui.interfaces.ActivityListInterface import com.owncloud.android.ui.interfaces.VersionListInterface import com.owncloud.android.utils.theme.ViewThemeUtils import java.util.Date +@Suppress("LongParameterList") class ActivityAndVersionListAdapter( context: FragmentActivity, currentAccountProvider: CurrentAccountProvider, activityListInterface: ActivityListInterface, private val versionListInterface: VersionListInterface.View, - viewThemeUtils: ViewThemeUtils -) : ActivityListAdapter(context, currentAccountProvider, activityListInterface, true, viewThemeUtils) { + viewThemeUtils: ViewThemeUtils, + avatarGenerator: AvatarGenerator +) : ActivityListAdapter(context, currentAccountProvider, activityListInterface, true, viewThemeUtils, avatarGenerator) { @SuppressLint("NotifyDataSetChanged") fun setActivityAndVersionItems(items: MutableList, newClient: NextcloudClient?, clear: Boolean) { diff --git a/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityListAdapter.kt index 4aa0874ba9db..1808a0541d58 100644 --- a/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityListAdapter.kt @@ -25,6 +25,7 @@ import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.client.account.CurrentAccountProvider import com.nextcloud.common.NextcloudClient import com.nextcloud.utils.GlideHelper +import com.nextcloud.utils.avatar.AvatarGenerator import com.nextcloud.utils.text.RichSubjectFormatter import com.nextcloud.utils.text.RichSubjectParam import com.owncloud.android.MainApp @@ -50,13 +51,14 @@ import kotlin.math.floor import kotlin.math.log import kotlin.math.pow -@Suppress("MagicNumber", "TooManyFunctions") +@Suppress("MagicNumber", "TooManyFunctions", "LongParameterList") open class ActivityListAdapter( protected val context: FragmentActivity, private val currentAccountProvider: CurrentAccountProvider, private val activityListInterface: ActivityListInterface, private val isDetailView: Boolean, - private val viewThemeUtils: ViewThemeUtils + private val viewThemeUtils: ViewThemeUtils, + private val avatarGenerator: AvatarGenerator ) : RecyclerView.Adapter(), StickyHeaderAdapter { @@ -64,7 +66,7 @@ open class ActivityListAdapter( val values: MutableList = mutableListOf() private val px = getThumbnailDimension() private var cachedNextcloudClient: NextcloudClient? = null - private val richSubjectFormatter by lazy { RichSubjectFormatter(context, currentAccountProvider) } + private val richSubjectFormatter by lazy { RichSubjectFormatter(context, avatarGenerator) } // region Public Methods @Suppress("NotifyDataSetChanged") diff --git a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java index 56c21cea670a..1b3d1a06e8aa 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java @@ -68,6 +68,8 @@ import com.nextcloud.utils.HumanReadableFormatter; import com.nextcloud.utils.LinkHelper; import com.nextcloud.utils.SnackbarUtil; +import com.nextcloud.utils.avatar.AvatarGenerationListener; +import com.nextcloud.utils.avatar.AvatarGenerator; import com.nextcloud.utils.extensions.ActivityExtensionsKt; import com.nextcloud.utils.extensions.DrawerActivityExtensionsKt; import com.nextcloud.utils.extensions.NavigationViewExtensionsKt; @@ -143,7 +145,7 @@ * generation. */ public abstract class DrawerActivity extends ToolbarActivity - implements DisplayUtils.AvatarGenerationListener, Injectable { + implements AvatarGenerationListener, Injectable { private static final String TAG = DrawerActivity.class.getSimpleName(); private static final String KEY_IS_ACCOUNT_CHOOSER_ACTIVE = "IS_ACCOUNT_CHOOSER_ACTIVE"; @@ -239,6 +241,9 @@ protected int getMenuItemId() { @Inject protected ClientFactory clientFactory; + @Inject + public AvatarGenerator avatarGenerator; + @Override public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) { super.onCreate(savedInstanceState, persistentState); diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index dcf2c6872458..436eb2373016 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -159,7 +159,6 @@ import com.owncloud.android.ui.preview.PreviewTextFragment import com.owncloud.android.ui.preview.PreviewTextStringFragment import com.owncloud.android.ui.preview.pdf.PreviewPdfFragment.Companion.newInstance import com.owncloud.android.utils.DataHolderUtil -import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.ErrorMessageAdapter import com.owncloud.android.utils.FileSortOrder import com.owncloud.android.utils.MimeTypeUtil @@ -3047,13 +3046,11 @@ class FileDisplayActivity : val existingAccountName = existingUser.accountName mSwitchAccountButton.tag = existingAccountName - DisplayUtils.setAvatar( + avatarGenerator.setAccountAvatar( existingUser, this, getResources().getDimension(R.dimen.nav_drawer_menu_avatar_radius), - getResources(), - mSwitchAccountButton, - this + mSwitchAccountButton ) val userChanged = (existingAccountName != lastDisplayedAccountName) if (userChanged) { diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt index 0f383ae5183e..1ab2f2df891c 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt @@ -123,7 +123,8 @@ class ManageAccountsActivity : multipleAccountsSupported, true, true, - viewThemeUtils + viewThemeUtils, + avatarGenerator ) recyclerView = findViewById(R.id.account_list) @@ -272,7 +273,8 @@ class ManageAccountsActivity : multipleAccountsSupported, false, true, - viewThemeUtils + viewThemeUtils, + avatarGenerator ) recyclerView?.adapter = userListAdapter runOnUiThread { userListAdapter?.notifyDataSetChanged() } @@ -331,7 +333,8 @@ class ManageAccountsActivity : multipleAccountsSupported, false, true, - viewThemeUtils + viewThemeUtils, + avatarGenerator ) recyclerView?.adapter = userListAdapter } else { diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java index 557bb017dfe2..8e61cb0f71b2 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java @@ -251,12 +251,10 @@ public void onLoadCleared(@Nullable Drawable placeholder) { private void populateUserInfoUi(UserInfo userInfo) { binding.userinfoUsername.setText(user.getAccountName()); binding.userinfoIcon.setTag(user.getAccountName()); - DisplayUtils.setAvatar(user, - this, - mCurrentAccountAvatarRadiusDimension, - getResources(), - binding.userinfoIcon, - this); + avatarGenerator.setAccountAvatar(user, + this, + mCurrentAccountAvatarRadiusDimension, + binding.userinfoIcon); if (userInfo != null && !TextUtils.isEmpty(userInfo.getDisplayName())) { binding.userinfoFullName.setText(userInfo.getDisplayName()); diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.kt index 5891a85bf115..50253d15f644 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.kt @@ -27,7 +27,7 @@ import androidx.core.view.size import androidx.recyclerview.widget.RecyclerView import com.google.android.material.button.MaterialButton import com.nextcloud.android.common.ui.theme.utils.ColorRole -import com.nextcloud.client.account.CurrentAccountProvider +import com.nextcloud.utils.avatar.AvatarGenerator import com.nextcloud.client.utils.IntentUtil import com.nextcloud.utils.extensions.setVisibleIf import com.nextcloud.utils.text.RichSubjectFormatter @@ -48,12 +48,12 @@ class NotificationListAdapter( private val fragment: NotificationsFragment, private val viewThemeUtils: ViewThemeUtils, private val itemClick: NotificationsAdapterItemClick, - private val accountManager: CurrentAccountProvider + private val avatarGenerator: AvatarGenerator ) : RecyclerView.Adapter() { private val notificationsList = ArrayList() private val richSubjectFormatter by lazy { - RichSubjectFormatter(fragment.requireContext(), accountManager) + RichSubjectFormatter(fragment.requireContext(), avatarGenerator) } // region Adapter overrides 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 bce25acf11e8..4f5bc78fa046 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,8 @@ import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.model.OfflineOperationType; import com.nextcloud.utils.HumanReadableFormatter; +import com.nextcloud.utils.avatar.AvatarGenerationListener; +import com.nextcloud.utils.avatar.AvatarGenerator; import com.nextcloud.utils.e2ee.E2EVersionHelper; import com.nextcloud.utils.extensions.ImageViewExtensionsKt; import com.nextcloud.utils.extensions.ViewExtensionsKt; @@ -97,7 +99,7 @@ */ @SuppressWarnings("unchecked") public class OCFileListAdapter extends RecyclerView.Adapter - implements DisplayUtils.AvatarGenerationListener, + implements AvatarGenerationListener, CommonOCFileListAdapterInterface, PopupTextProvider { private final String userId; @@ -139,6 +141,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter ocFileListFragmentInterface.onShareIconClick(file)); - sharedAvatars.setAvatars(user, avatarShareesProvider.get(file, userId), viewThemeUtils); + sharedAvatars.setAvatars(user, avatarShareesProvider.get(file, userId), viewThemeUtils, avatarGenerator); } private void bindListItemViewHolder(ListItemViewHolder holder, OCFile file) { diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java b/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java index 20706f4d7913..9f773ddf0069 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java @@ -19,6 +19,8 @@ import android.widget.ImageView; import com.nextcloud.client.account.User; +import com.nextcloud.utils.avatar.AvatarGenerationListener; +import com.nextcloud.utils.avatar.AvatarGenerator; import com.nextcloud.utils.extensions.ImageViewExtensionsKt; import com.owncloud.android.R; import com.owncloud.android.databinding.FileDetailsShareShareItemBinding; @@ -26,7 +28,6 @@ import com.owncloud.android.lib.resources.shares.OCShare; import com.owncloud.android.ui.TextDrawable; import com.owncloud.android.ui.fragment.util.SharePermissionManager; -import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.theme.ViewThemeUtils; import androidx.annotation.NonNull; @@ -39,6 +40,7 @@ class ShareViewHolder extends RecyclerView.ViewHolder { private Context context; private ViewThemeUtils viewThemeUtils; private boolean encrypted; + private AvatarGenerator avatarGenerator; public ShareViewHolder(@NonNull View itemView) { super(itemView); @@ -48,18 +50,20 @@ public ShareViewHolder(FileDetailsShareShareItemBinding binding, User user, Context context, final ViewThemeUtils viewThemeUtils, - boolean encrypted) { + boolean encrypted, + AvatarGenerator avatarGenerator) { this(binding.getRoot()); this.binding = binding; this.user = user; this.context = context; this.viewThemeUtils = viewThemeUtils; this.encrypted = encrypted; + this.avatarGenerator = avatarGenerator; } public void bind(OCShare share, ShareeListAdapterListener listener, - DisplayUtils.AvatarGenerationListener avatarListener, + AvatarGenerationListener avatarListener, String userId, float avatarRadiusDimension) { this.avatarRadiusDimension = avatarRadiusDimension; @@ -93,14 +97,12 @@ public void bind(OCShare share, float avatarRadius = context.getResources().getDimension(R.dimen.list_item_avatar_icon_radius); if (share.getShareWith() != null) { - DisplayUtils.setAvatar(user, - share.getShareWith(), - share.getSharedWithDisplayName(), - avatarListener, - avatarRadius, - context.getResources(), - binding.icon, - context); + avatarGenerator.setUserAvatar(share.getShareWith(), + avatarListener, + avatarRadius, + binding.icon, + share.getSharedWithDisplayName(), + user); } binding.icon.setOnClickListener(v -> listener.showProfileBottomSheet(user, share.getShareWith())); diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.kt index 2921f83148da..d96d7430b7f9 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.kt @@ -18,6 +18,8 @@ import android.view.ViewGroup import android.widget.ImageView import androidx.recyclerview.widget.RecyclerView import com.nextcloud.client.account.User +import com.nextcloud.utils.avatar.AvatarGenerationListener +import com.nextcloud.utils.avatar.AvatarGenerator import com.nextcloud.utils.mdm.MDMConfig.shareViaLink import com.owncloud.android.R import com.owncloud.android.databinding.FileDetailsShareInternalShareLinkBinding @@ -29,7 +31,6 @@ import com.owncloud.android.datamodel.SharesType import com.owncloud.android.lib.resources.shares.OCShare import com.owncloud.android.lib.resources.shares.ShareType import com.owncloud.android.ui.activity.FileActivity -import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener import com.owncloud.android.utils.theme.ViewThemeUtils import kotlin.math.min @@ -45,7 +46,8 @@ class ShareeListAdapter( private val user: User?, private val viewThemeUtils: ViewThemeUtils, private val encrypted: Boolean, - private val sharesType: SharesType? + private val sharesType: SharesType?, + private val avatarGenerator: AvatarGenerator ) : RecyclerView.Adapter(), AvatarGenerationListener { private val avatarRadiusDimension: Float = fileActivity.getResources().getDimension(R.dimen.user_icon_radius) @@ -86,7 +88,7 @@ class ShareeListAdapter( else -> { val binding = FileDetailsShareShareItemBinding.inflate(inflater, parent, false) - ShareViewHolder(binding, user, fileActivity, viewThemeUtils, encrypted) + ShareViewHolder(binding, user, fileActivity, viewThemeUtils, encrypted, avatarGenerator) } } } @@ -152,11 +154,10 @@ class ShareeListAdapter( } } - override fun shouldCallGeneratedCallback(tag: String, callContext: Any?): Boolean { + override fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean { if (callContext is ImageView) { // needs to be changed once federated users have avatars - return callContext.tag.toString() == tag.split("@".toRegex()).dropLastWhile { it.isEmpty() } - .toTypedArray()[0] + return callContext.tag.toString() == tag?.substringBefore('@') } return false } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java index 6c61b21b688b..3ad327055ac3 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java @@ -23,6 +23,8 @@ import com.nextcloud.client.account.User; import com.nextcloud.client.account.UserAccountManager; +import com.nextcloud.utils.avatar.AvatarGenerationListener; +import com.nextcloud.utils.avatar.AvatarGenerator; import com.nextcloud.utils.mdm.MDMConfig; import com.owncloud.android.R; import com.owncloud.android.databinding.AccountActionBinding; @@ -39,7 +41,7 @@ import androidx.recyclerview.widget.RecyclerView; public class UserListAdapter extends RecyclerView.Adapter - implements DisplayUtils.AvatarGenerationListener { + implements AvatarGenerationListener { private static final String TAG = UserListAdapter.class.getSimpleName(); private final float accountAvatarRadiusDimension; @@ -55,6 +57,7 @@ public class UserListAdapter extends RecyclerView.Adapter