瀏覽代碼

[WIP] Replace Fresco with Coil

Fresco is replaced with Coil verywhere. But Coil is not used directly to
avoid splintering the dependency everywhere in the code. Coil is wrapped
by extension functions for 'ImageView'.

Some shared functionality is moved from 'DisplayUtils' into the
'ImageViewExtensions'.

Resolves: #2227, #2376

Signed-off-by: Tim Krüger <t@timkrueger.me>
Tim Krüger 2 年之前
父節點
當前提交
863052b53e

+ 4 - 23
app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt

@@ -37,20 +37,18 @@ import android.text.format.DateUtils
 import android.view.View
 import androidx.core.content.ContextCompat
 import androidx.core.content.res.ResourcesCompat
-import com.facebook.drawee.backends.pipeline.Fresco
-import com.facebook.drawee.interfaces.DraweeController
 import com.nextcloud.talk.R
 import com.nextcloud.talk.adapters.items.ConversationItem.ConversationItemViewHolder
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.databinding.RvItemConversationWithLastMessageBinding
+import com.nextcloud.talk.extensions.loadAvatar
 import com.nextcloud.talk.models.json.chat.ChatMessage
 import com.nextcloud.talk.models.json.conversations.Conversation
 import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType
 import com.nextcloud.talk.models.json.status.Status
 import com.nextcloud.talk.ui.StatusDrawable
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
-import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability
 import eu.davidea.flexibleadapter.FlexibleAdapter
@@ -116,7 +114,6 @@ class ConversationItem(
         payloads: List<Any>
     ) {
         val appContext = sharedApplication!!.applicationContext
-        holder.binding.dialogAvatar.controller = null
         holder.binding.dialogName.setTextColor(
             ResourcesCompat.getColor(
                 context.resources,
@@ -278,32 +275,16 @@ class ConversationItem(
                 layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background)
                 layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground)
                 val layerDrawable = LayerDrawable(layers)
-                holder.binding.dialogAvatar.hierarchy.setPlaceholderImage(
-                    DisplayUtils.getRoundedDrawable(layerDrawable)
-                )
+                holder.binding.dialogAvatar.loadAvatar(DisplayUtils.getRoundedDrawable(layerDrawable))
             } else {
-                holder.binding.dialogAvatar.hierarchy.setPlaceholderImage(R.mipmap.ic_launcher)
+                holder.binding.dialogAvatar.loadAvatar(R.mipmap.ic_launcher)
             }
             shouldLoadAvatar = false
         }
         if (shouldLoadAvatar) {
             when (model.type) {
                 ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(model.name)) {
-                    val draweeController: DraweeController = Fresco.newDraweeControllerBuilder()
-                        .setOldController(holder.binding.dialogAvatar.controller)
-                        .setAutoPlayAnimations(true)
-                        .setImageRequest(
-                            DisplayUtils.getImageRequestForUrl(
-                                ApiUtils.getUrlForAvatar(
-                                    user.baseUrl,
-                                    model.name,
-                                    true
-                                ),
-                                user
-                            )
-                        )
-                        .build()
-                    holder.binding.dialogAvatar.controller = draweeController
+                    holder.binding.dialogAvatar.loadAvatar(user, model.name!!)
                 } else {
                     holder.binding.dialogAvatar.visibility = View.GONE
                 }

+ 4 - 13
app/src/main/java/com/nextcloud/talk/adapters/items/MessageResultItem.kt

@@ -2,6 +2,8 @@
  * Nextcloud Talk application
  *
  * @author Álvaro Brey
+ * @author Tim Krüger
+ * Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
  * Copyright (C) 2022 Álvaro Brey
  * Copyright (C) 2022 Nextcloud GmbH
  *
@@ -27,9 +29,9 @@ import androidx.recyclerview.widget.RecyclerView
 import com.nextcloud.talk.R
 import com.nextcloud.talk.data.user.model.User
 import com.nextcloud.talk.databinding.RvItemSearchMessageBinding
+import com.nextcloud.talk.extensions.loadThumbnail
 import com.nextcloud.talk.models.domain.SearchMessageEntry
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
-import com.nextcloud.talk.utils.DisplayUtils
 import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
 import eu.davidea.flexibleadapter.items.IFilterable
@@ -72,7 +74,7 @@ data class MessageResultItem constructor(
     ) {
         holder.binding.conversationTitle.text = messageEntry.title
         bindMessageExcerpt(holder)
-        loadImage(holder)
+        holder.binding.thumbnail.loadThumbnail(messageEntry.thumbnailURL, currentUser)
     }
 
     private fun bindMessageExcerpt(holder: ViewHolder) {
@@ -83,17 +85,6 @@ data class MessageResultItem constructor(
         )
     }
 
-    private fun loadImage(holder: ViewHolder) {
-        DisplayUtils.loadAvatarPlaceholder(holder.binding.thumbnail)
-        if (messageEntry.thumbnailURL != null) {
-            val imageRequest = DisplayUtils.getImageRequestForUrl(
-                messageEntry.thumbnailURL,
-                currentUser
-            )
-            DisplayUtils.loadImage(holder.binding.thumbnail, imageRequest)
-        }
-    }
-
     override fun filter(constraint: String?): Boolean = true
 
     override fun getItemViewType(): Int {

+ 98 - 0
app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt

@@ -0,0 +1,98 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Tim Krüger
+ * Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
+ * Copyright (C) 2022 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.extensions
+
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.os.Build
+import android.widget.ImageView
+import androidx.core.content.ContextCompat
+import coil.load
+import coil.request.ImageRequest
+import coil.transform.CircleCropTransformation
+import com.nextcloud.talk.R
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.utils.ApiUtils
+
+fun ImageView.loadAvatar(user: User, avatar: String): io.reactivex.disposables.Disposable {
+
+    val imageRequestUri = ApiUtils.getUrlForAvatar(
+        user.baseUrl,
+        avatar,
+        true
+    )
+
+    return DisposableWrapper(
+        load(imageRequestUri) {
+            addHeader(
+                "Authorization",
+                ApiUtils.getCredentials(user.username, user.token)
+            )
+            transformations(CircleCropTransformation())
+        }
+    )
+}
+
+fun ImageView.loadThumbnail(url: String?, user: User): io.reactivex.disposables.Disposable {
+    val requestBuilder = ImageRequest.Builder(context)
+        .data(url)
+        .crossfade(true)
+        .target(this)
+        .transformations(CircleCropTransformation())
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+        val layers = arrayOfNulls<Drawable>(2)
+        layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background)
+        layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground)
+        requestBuilder.placeholder(LayerDrawable(layers))
+    } else {
+        requestBuilder.placeholder(R.mipmap.ic_launcher)
+    }
+
+    if (url != null &&
+        url.startsWith(user.baseUrl!!) &&
+        (url.contains("index.php/core/preview?fileId=") || url.contains("/avatar/"))
+    ) {
+        requestBuilder.addHeader(
+            "Authorization",
+            ApiUtils.getCredentials(user.username, user.token)
+        )
+    }
+
+    return DisposableWrapper(load(requestBuilder.build()))
+}
+
+fun ImageView.loadAvatar(any: Any?): io.reactivex.disposables.Disposable {
+    return DisposableWrapper(load(any))
+}
+
+private class DisposableWrapper(private val disposable: coil.request.Disposable) : io.reactivex.disposables
+    .Disposable {
+
+    override fun dispose() {
+        disposable.dispose()
+    }
+
+    override fun isDisposed(): Boolean {
+        return disposable.isDisposed
+    }
+}

+ 0 - 24
app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java

@@ -617,30 +617,6 @@ public class DisplayUtils {
         avatarImageView.setController(draweeController);
     }
 
-    public static void loadAvatarPlaceholder(final SimpleDraweeView targetView) {
-        final Context context = targetView.getContext();
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            Drawable[] layers = new Drawable[2];
-            layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background);
-            layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground);
-            LayerDrawable layerDrawable = new LayerDrawable(layers);
-
-            targetView.getHierarchy().setPlaceholderImage(
-                DisplayUtils.getRoundedDrawable(layerDrawable));
-        } else {
-            targetView.getHierarchy().setPlaceholderImage(R.mipmap.ic_launcher);
-        }
-    }
-
-    public static void loadImage(final SimpleDraweeView targetView, final ImageRequest imageRequest) {
-        final DraweeController newController = Fresco.newDraweeControllerBuilder()
-            .setOldController(targetView.getController())
-            .setAutoPlayAnimations(true)
-            .setImageRequest(imageRequest)
-            .build();
-        targetView.setController(newController);
-    }
-
     public static @StringRes
     int getSortOrderStringId(FileSortOrder sortOrder) {
         switch (sortOrder.getName()) {

+ 1 - 1
app/src/main/res/layout/rv_item_conversation_with_last_message.xml

@@ -38,7 +38,7 @@
         android:layout_centerVertical="true"
         android:layout_marginEnd="@dimen/double_margin_between_elements">
 
-        <com.facebook.drawee.view.SimpleDraweeView
+        <ImageView
             android:id="@id/dialogAvatar"
             android:layout_width="@dimen/small_item_height"
             android:layout_height="@dimen/small_item_height"

+ 3 - 1
app/src/main/res/layout/rv_item_search_message.xml

@@ -3,6 +3,8 @@
   ~
   ~ @author Mario Danic
   ~ @author Andy Scherzinger
+  ~ @author Tim Krüger
+  ~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
   ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
   ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
   ~
@@ -35,7 +37,7 @@
     android:layout_margin="@dimen/double_margin_between_elements"
     tools:background="@color/white">
 
-    <com.facebook.drawee.view.SimpleDraweeView
+    <ImageView
         android:id="@+id/thumbnail"
         android:layout_width="@dimen/small_item_height"
         android:layout_height="@dimen/small_item_height"