Эх сурвалжийг харах

Merge commit '863052b53e3051bb7e0fd4904d348fa294835821'

drone 2 жил өмнө
parent
commit
3a90d17649

+ 1 - 1
app/build.gradle

@@ -42,7 +42,7 @@ android {
     namespace 'com.nextcloud.talk'
 
     defaultConfig {
-        minSdkVersion 21
+        minSdkVersion 23
         targetSdkVersion 31
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 

+ 0 - 371
app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java

@@ -1,371 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * @author Andy Scherzinger
- * @author Marcel Hibbe
- * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
- * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
- * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.talk.adapters.items;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.os.Build;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.view.View;
-
-import com.facebook.drawee.backends.pipeline.Fresco;
-import com.facebook.drawee.interfaces.DraweeController;
-import com.nextcloud.talk.R;
-import com.nextcloud.talk.application.NextcloudTalkApplication;
-import com.nextcloud.talk.data.user.model.User;
-import com.nextcloud.talk.databinding.RvItemConversationWithLastMessageBinding;
-import com.nextcloud.talk.models.json.chat.ChatMessage;
-import com.nextcloud.talk.models.json.conversations.Conversation;
-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;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.regex.Pattern;
-
-import androidx.core.content.ContextCompat;
-import androidx.core.content.res.ResourcesCompat;
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
-import eu.davidea.flexibleadapter.items.IFilterable;
-import eu.davidea.flexibleadapter.items.IFlexible;
-import eu.davidea.flexibleadapter.items.ISectionable;
-import eu.davidea.viewholders.FlexibleViewHolder;
-
-public class ConversationItem extends AbstractFlexibleItem<ConversationItem.ConversationItemViewHolder> implements
-    ISectionable<ConversationItem.ConversationItemViewHolder, GenericTextHeaderItem>, IFilterable<String> {
-
-    public static final int VIEW_TYPE = R.layout.rv_item_conversation_with_last_message;
-
-    private static final float STATUS_SIZE_IN_DP = 9f;
-
-    private final Conversation conversation;
-    private final User user;
-    private final Context context;
-    private GenericTextHeaderItem header;
-    private final Status status;
-    private final ViewThemeUtils viewThemeUtils;
-
-
-    public ConversationItem(Conversation conversation, User user, Context activityContext, Status status, final ViewThemeUtils viewThemeUtils) {
-        this.conversation = conversation;
-        this.user = user;
-        this.context = activityContext;
-        this.status = status;
-        this.viewThemeUtils = viewThemeUtils;
-    }
-
-    public ConversationItem(Conversation conversation, User user,
-                            Context activityContext, GenericTextHeaderItem genericTextHeaderItem, Status status,
-                            final ViewThemeUtils viewThemeUtils) {
-        this(conversation, user, activityContext, status, viewThemeUtils);
-        this.header = genericTextHeaderItem;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o instanceof ConversationItem) {
-            ConversationItem inItem = (ConversationItem) o;
-            return conversation.equals(inItem.getModel()) && Objects.equals(status, inItem.status);
-        }
-        return false;
-    }
-
-    public Conversation getModel() {
-        return conversation;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = conversation.hashCode();
-        result = 31 * result + (status != null ? status.hashCode() : 0);
-        return result;
-    }
-
-    @Override
-    public int getLayoutRes() {
-        return R.layout.rv_item_conversation_with_last_message;
-    }
-
-    @Override
-    public int getItemViewType() {
-        return VIEW_TYPE;
-    }
-
-    @Override
-    public ConversationItemViewHolder createViewHolder(View view, FlexibleAdapter<IFlexible> adapter) {
-        return new ConversationItemViewHolder(view, adapter);
-    }
-
-    @SuppressLint("SetTextI18n")
-    @Override
-    public void bindViewHolder(FlexibleAdapter<IFlexible> adapter,
-                               ConversationItemViewHolder holder,
-                               int position,
-                               List<Object> payloads) {
-        Context appContext =
-            NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext();
-        holder.binding.dialogAvatar.setController(null);
-
-        holder.binding.dialogName.setTextColor(ResourcesCompat.getColor(context.getResources(),
-                                                                        R.color.conversation_item_header,
-                                                                        null));
-
-        if (adapter.hasFilter()) {
-            viewThemeUtils.platform.highlightText(holder.binding.dialogName,
-                                         conversation.getDisplayName(),
-                                         String.valueOf(adapter.getFilter(String.class)));
-        } else {
-            holder.binding.dialogName.setText(conversation.getDisplayName());
-        }
-
-        if (conversation.getUnreadMessages() > 0) {
-            holder.binding.dialogName.setTypeface(holder.binding.dialogName.getTypeface(), Typeface.BOLD);
-            holder.binding.dialogLastMessage.setTypeface(holder.binding.dialogLastMessage.getTypeface(), Typeface.BOLD);
-            holder.binding.dialogUnreadBubble.setVisibility(View.VISIBLE);
-            if (conversation.getUnreadMessages() < 1000) {
-                holder.binding.dialogUnreadBubble.setText(Long.toString(conversation.getUnreadMessages()));
-            } else {
-                holder.binding.dialogUnreadBubble.setText(R.string.tooManyUnreadMessages);
-            }
-
-            ColorStateList lightBubbleFillColor = ColorStateList.valueOf(
-                ContextCompat.getColor(context,
-                                       R.color.conversation_unread_bubble));
-            int lightBubbleTextColor = ContextCompat.getColor(
-                context,
-                R.color.conversation_unread_bubble_text);
-
-            if (conversation.getType() == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
-                viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble);
-            } else if (conversation.getUnreadMention()) {
-                if (CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "direct-mention-flag")) {
-                    if (conversation.getUnreadMentionDirect()) {
-                        viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble);
-                    } else {
-                        viewThemeUtils.material.colorChipOutlined(holder.binding.dialogUnreadBubble, 6.0f);
-                    }
-                } else {
-                    viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble);
-                }
-            } else {
-                holder.binding.dialogUnreadBubble.setChipBackgroundColor(lightBubbleFillColor);
-                holder.binding.dialogUnreadBubble.setTextColor(lightBubbleTextColor);
-            }
-        } else {
-            holder.binding.dialogName.setTypeface(null, Typeface.NORMAL);
-            holder.binding.dialogDate.setTypeface(null, Typeface.NORMAL);
-            holder.binding.dialogLastMessage.setTypeface(null, Typeface.NORMAL);
-            holder.binding.dialogUnreadBubble.setVisibility(View.GONE);
-        }
-
-        if (conversation.getFavorite()) {
-            holder.binding.favoriteConversationImageView.setVisibility(View.VISIBLE);
-        } else {
-            holder.binding.favoriteConversationImageView.setVisibility(View.GONE);
-        }
-
-        if (status != null && Conversation.ConversationType.ROOM_SYSTEM != conversation.getType()) {
-            float size = DisplayUtils.convertDpToPixel(STATUS_SIZE_IN_DP, appContext);
-
-            holder.binding.userStatusImage.setVisibility(View.VISIBLE);
-            holder.binding.userStatusImage.setImageDrawable(new StatusDrawable(
-                status.getStatus(),
-                status.getIcon(),
-                size,
-                context.getResources().getColor(R.color.bg_default),
-                appContext));
-        } else {
-            holder.binding.userStatusImage.setVisibility(View.GONE);
-        }
-
-        if (conversation.getLastMessage() != null) {
-            holder.binding.dialogDate.setVisibility(View.VISIBLE);
-            holder.binding.dialogDate.setText(
-                DateUtils.getRelativeTimeSpanString(conversation.getLastActivity() * 1000L,
-                                                    System.currentTimeMillis(),
-                                                    0,
-                                                    DateUtils.FORMAT_ABBREV_RELATIVE));
-
-            if (!TextUtils.isEmpty(conversation.getLastMessage().getSystemMessage()) ||
-                Conversation.ConversationType.ROOM_SYSTEM == conversation.getType()) {
-                holder.binding.dialogLastMessage.setText(conversation.getLastMessage().getText());
-            } else {
-                String authorDisplayName = "";
-                conversation.getLastMessage().setActiveUser(user);
-                String text;
-                if (conversation.getLastMessage().getCalculateMessageType() == ChatMessage.MessageType.REGULAR_TEXT_MESSAGE) {
-                    if (conversation.getLastMessage().getActorId().equals(user.getUserId())) {
-                        text = String.format(appContext.getString(R.string.nc_formatted_message_you),
-                                             conversation.getLastMessage().getLastMessageDisplayText());
-                    } else {
-                        authorDisplayName = !TextUtils.isEmpty(conversation.getLastMessage().getActorDisplayName()) ?
-                            conversation.getLastMessage().getActorDisplayName() :
-                            "guests".equals(conversation.getLastMessage().getActorType()) ?
-                                appContext.getString(R.string.nc_guest) : "";
-                        text = String.format(appContext.getString(R.string.nc_formatted_message),
-                                             authorDisplayName,
-                                             conversation.getLastMessage().getLastMessageDisplayText());
-                    }
-                } else {
-                    text = conversation.getLastMessage().getLastMessageDisplayText();
-                }
-
-                holder.binding.dialogLastMessage.setText(text);
-            }
-        } else {
-            holder.binding.dialogDate.setVisibility(View.GONE);
-            holder.binding.dialogLastMessage.setText(R.string.nc_no_messages_yet);
-        }
-
-        holder.binding.dialogAvatar.setVisibility(View.VISIBLE);
-
-        boolean shouldLoadAvatar = true;
-        String objectType;
-        if (!TextUtils.isEmpty(objectType = conversation.getObjectType())) {
-            switch (objectType) {
-                case "share:password":
-                    shouldLoadAvatar = false;
-                    holder.binding.dialogAvatar.setImageDrawable(
-                        ContextCompat.getDrawable(context,
-                                                  R.drawable.ic_circular_lock));
-                    break;
-                case "file":
-                    shouldLoadAvatar = false;
-                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-                        holder.binding.dialogAvatar.setImageDrawable(
-                            DisplayUtils.getRoundedDrawable(
-                                viewThemeUtils.talk.themePlaceholderAvatar(holder.binding.dialogAvatar,
-                                                                      R.drawable.ic_avatar_document)));
-                    } else {
-                        holder.binding.dialogAvatar.setImageDrawable(
-                            ContextCompat.getDrawable(context, R.drawable.ic_circular_document));
-                    }
-                    break;
-                default:
-                    break;
-            }
-        }
-
-        if (Conversation.ConversationType.ROOM_SYSTEM.equals(conversation.getType())) {
-            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);
-
-                holder.binding.dialogAvatar.getHierarchy().setPlaceholderImage(
-                    DisplayUtils.getRoundedDrawable(layerDrawable));
-            } else {
-                holder.binding.dialogAvatar.getHierarchy().setPlaceholderImage(R.mipmap.ic_launcher);
-            }
-            shouldLoadAvatar = false;
-        }
-
-        if (shouldLoadAvatar) {
-            switch (conversation.getType()) {
-                case ROOM_TYPE_ONE_TO_ONE_CALL:
-                    if (!TextUtils.isEmpty(conversation.getName())) {
-                        DraweeController draweeController = Fresco.newDraweeControllerBuilder()
-                            .setOldController(holder.binding.dialogAvatar.getController())
-                            .setAutoPlayAnimations(true)
-                            .setImageRequest(DisplayUtils.getImageRequestForUrl(
-                                ApiUtils.getUrlForAvatar(user.getBaseUrl(),
-                                                         conversation.getName(),
-                                                         true),
-                                user))
-                            .build();
-                        holder.binding.dialogAvatar.setController(draweeController);
-                    } else {
-                        holder.binding.dialogAvatar.setVisibility(View.GONE);
-                    }
-                    break;
-                case ROOM_GROUP_CALL:
-                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-                        holder.binding.dialogAvatar.setImageDrawable(
-                            DisplayUtils.getRoundedDrawable(
-                                viewThemeUtils.talk.themePlaceholderAvatar(holder.binding.dialogAvatar,
-                                                                      R.drawable.ic_avatar_group)));
-                    } else {
-                        holder.binding.dialogAvatar.setImageDrawable(
-                            ContextCompat.getDrawable(context, R.drawable.ic_circular_group));
-                    }
-                    break;
-                case ROOM_PUBLIC_CALL:
-                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-                        holder.binding.dialogAvatar.setImageDrawable(
-                            DisplayUtils.getRoundedDrawable(
-                                viewThemeUtils.talk.themePlaceholderAvatar(holder.binding.dialogAvatar,
-                                                                      R.drawable.ic_avatar_link)));
-                    } else {
-                        holder.binding.dialogAvatar.setImageDrawable(
-                            ContextCompat.getDrawable(context, R.drawable.ic_circular_link));
-                    }
-                    break;
-                default:
-                    holder.binding.dialogAvatar.setVisibility(View.GONE);
-            }
-        }
-    }
-
-    @Override
-    public boolean filter(String constraint) {
-        return conversation.getDisplayName() != null &&
-            Pattern
-                .compile(constraint, Pattern.CASE_INSENSITIVE | Pattern.LITERAL)
-                .matcher(conversation.getDisplayName().trim())
-                .find();
-    }
-
-    @Override
-    public GenericTextHeaderItem getHeader() {
-        return header;
-    }
-
-    @Override
-    public void setHeader(GenericTextHeaderItem header) {
-        this.header = header;
-    }
-
-    static class ConversationItemViewHolder extends FlexibleViewHolder {
-
-        RvItemConversationWithLastMessageBinding binding;
-
-        ConversationItemViewHolder(View view, FlexibleAdapter adapter) {
-            super(view, adapter);
-            binding = RvItemConversationWithLastMessageBinding.bind(view);
-        }
-    }
-}

+ 352 - 0
app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt

@@ -0,0 +1,352 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * @author Andy Scherzinger
+ * @author Marcel Hibbe
+ * @author Tim Krüger
+ * Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
+ * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
+ * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
+ * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.talk.adapters.items
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.os.Build
+import android.text.TextUtils
+import android.text.format.DateUtils
+import android.view.View
+import androidx.core.content.ContextCompat
+import androidx.core.content.res.ResourcesCompat
+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.DisplayUtils
+import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
+import eu.davidea.flexibleadapter.items.IFilterable
+import eu.davidea.flexibleadapter.items.IFlexible
+import eu.davidea.flexibleadapter.items.ISectionable
+import eu.davidea.viewholders.FlexibleViewHolder
+import java.util.regex.Pattern
+
+class ConversationItem(
+    val model: Conversation,
+    private val user: User,
+    private val context: Context,
+    private val status: Status?,
+    private val viewThemeUtils: ViewThemeUtils
+) : AbstractFlexibleItem<ConversationItemViewHolder>(),
+    ISectionable<ConversationItemViewHolder, GenericTextHeaderItem?>,
+    IFilterable<String?> {
+    private var header: GenericTextHeaderItem? = null
+
+    constructor(
+        conversation: Conversation,
+        user: User,
+        activityContext: Context,
+        genericTextHeaderItem: GenericTextHeaderItem?,
+        status: Status?,
+        viewThemeUtils: ViewThemeUtils
+    ) : this(conversation, user, activityContext, status, viewThemeUtils) {
+        header = genericTextHeaderItem
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (other is ConversationItem) {
+            return model == other.model && status == other.status
+        }
+        return false
+    }
+
+    override fun hashCode(): Int {
+        var result = model.hashCode()
+        result = 31 * result + (status?.hashCode() ?: 0)
+        return result
+    }
+
+    override fun getLayoutRes(): Int {
+        return R.layout.rv_item_conversation_with_last_message
+    }
+
+    override fun getItemViewType(): Int {
+        return VIEW_TYPE
+    }
+
+    override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>?>?): ConversationItemViewHolder {
+        return ConversationItemViewHolder(view, adapter)
+    }
+
+    @SuppressLint("SetTextI18n")
+    override fun bindViewHolder(
+        adapter: FlexibleAdapter<IFlexible<*>?>,
+        holder: ConversationItemViewHolder,
+        position: Int,
+        payloads: List<Any>
+    ) {
+        val appContext = sharedApplication!!.applicationContext
+        holder.binding.dialogName.setTextColor(
+            ResourcesCompat.getColor(
+                context.resources,
+                R.color.conversation_item_header,
+                null
+            )
+        )
+        if (adapter.hasFilter()) {
+            viewThemeUtils.platform.highlightText(
+                holder.binding.dialogName,
+                model.displayName!!, adapter.getFilter(String::class.java).toString()
+            )
+        } else {
+            holder.binding.dialogName.text = model.displayName
+        }
+        if (model.unreadMessages > 0) {
+            holder.binding.dialogName.setTypeface(holder.binding.dialogName.typeface, Typeface.BOLD)
+            holder.binding.dialogLastMessage.setTypeface(holder.binding.dialogLastMessage.typeface, Typeface.BOLD)
+            holder.binding.dialogUnreadBubble.visibility = View.VISIBLE
+            if (model.unreadMessages < 1000) {
+                holder.binding.dialogUnreadBubble.text = model.unreadMessages.toLong().toString()
+            } else {
+                holder.binding.dialogUnreadBubble.setText(R.string.tooManyUnreadMessages)
+            }
+            val lightBubbleFillColor = ColorStateList.valueOf(
+                ContextCompat.getColor(
+                    context,
+                    R.color.conversation_unread_bubble
+                )
+            )
+            val lightBubbleTextColor = ContextCompat.getColor(
+                context,
+                R.color.conversation_unread_bubble_text
+            )
+            if (model.type === ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
+                viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble)
+            } else if (model.unreadMention) {
+                if (hasSpreedFeatureCapability(user, "direct-mention-flag")) {
+                    if (model.unreadMentionDirect!!) {
+                        viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble)
+                    } else {
+                        viewThemeUtils.material.colorChipOutlined(holder.binding.dialogUnreadBubble, 6.0f)
+                    }
+                } else {
+                    viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble)
+                }
+            } else {
+                holder.binding.dialogUnreadBubble.chipBackgroundColor = lightBubbleFillColor
+                holder.binding.dialogUnreadBubble.setTextColor(lightBubbleTextColor)
+            }
+        } else {
+            holder.binding.dialogName.setTypeface(null, Typeface.NORMAL)
+            holder.binding.dialogDate.setTypeface(null, Typeface.NORMAL)
+            holder.binding.dialogLastMessage.setTypeface(null, Typeface.NORMAL)
+            holder.binding.dialogUnreadBubble.visibility = View.GONE
+        }
+        if (model.favorite) {
+            holder.binding.favoriteConversationImageView.visibility = View.VISIBLE
+        } else {
+            holder.binding.favoriteConversationImageView.visibility = View.GONE
+        }
+        if (status != null && ConversationType.ROOM_SYSTEM !== model.type) {
+            val size = DisplayUtils.convertDpToPixel(STATUS_SIZE_IN_DP, appContext)
+            holder.binding.userStatusImage.visibility = View.VISIBLE
+            holder.binding.userStatusImage.setImageDrawable(
+                StatusDrawable(
+                    status.status,
+                    status.icon,
+                    size,
+                    context.resources.getColor(R.color.bg_default),
+                    appContext
+                )
+            )
+        } else {
+            holder.binding.userStatusImage.visibility = View.GONE
+        }
+        if (model.lastMessage != null) {
+            holder.binding.dialogDate.visibility = View.VISIBLE
+            holder.binding.dialogDate.text = DateUtils.getRelativeTimeSpanString(
+                model.lastActivity * 1000L,
+                System.currentTimeMillis(),
+                0,
+                DateUtils.FORMAT_ABBREV_RELATIVE
+            )
+            if (!TextUtils.isEmpty(model.lastMessage!!.systemMessage) ||
+                ConversationType.ROOM_SYSTEM === model.type
+            ) {
+                holder.binding.dialogLastMessage.text = model.lastMessage!!.text
+            } else {
+                model.lastMessage!!.activeUser = user
+                val text: String
+                if (model.lastMessage!!.getCalculateMessageType() === ChatMessage.MessageType.REGULAR_TEXT_MESSAGE) {
+                    if (model.lastMessage!!.actorId == user.userId) {
+                        text = String.format(
+                            appContext.getString(R.string.nc_formatted_message_you),
+                            model.lastMessage!!.lastMessageDisplayText
+                        )
+                    } else {
+                        val authorDisplayName =
+                            if (!TextUtils.isEmpty(model.lastMessage!!.actorDisplayName)) {
+                                model.lastMessage!!.actorDisplayName
+                            } else if ("guests" == model.lastMessage!!.actorType) {
+                                appContext.getString(R.string.nc_guest)
+                            } else {
+                                ""
+                            }
+                        text = String.format(
+                            appContext.getString(R.string.nc_formatted_message),
+                            authorDisplayName,
+                            model.lastMessage!!.lastMessageDisplayText
+                        )
+                    }
+                } else {
+                    text = model.lastMessage!!.lastMessageDisplayText
+                }
+                holder.binding.dialogLastMessage.text = text
+            }
+        } else {
+            holder.binding.dialogDate.visibility = View.GONE
+            holder.binding.dialogLastMessage.setText(R.string.nc_no_messages_yet)
+        }
+        holder.binding.dialogAvatar.visibility = View.VISIBLE
+        var shouldLoadAvatar = true
+        var objectType: String?
+        if (!TextUtils.isEmpty(model.objectType.also { objectType = it })) {
+            when (objectType) {
+                "share:password" -> {
+                    shouldLoadAvatar = false
+                    holder.binding.dialogAvatar.setImageDrawable(
+                        ContextCompat.getDrawable(
+                            context,
+                            R.drawable.ic_circular_lock
+                        )
+                    )
+                }
+                "file" -> {
+                    shouldLoadAvatar = false
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                        holder.binding.dialogAvatar.setImageDrawable(
+                            DisplayUtils.getRoundedDrawable(
+                                viewThemeUtils.talk.themePlaceholderAvatar(
+                                    holder.binding.dialogAvatar,
+                                    R.drawable.ic_avatar_document
+                                )
+                            )
+                        )
+                    } else {
+                        holder.binding.dialogAvatar.setImageDrawable(
+                            ContextCompat.getDrawable(context, R.drawable.ic_circular_document)
+                        )
+                    }
+                }
+                else -> {}
+            }
+        }
+        if (ConversationType.ROOM_SYSTEM == model.type) {
+            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)
+                val layerDrawable = LayerDrawable(layers)
+                holder.binding.dialogAvatar.loadAvatar(DisplayUtils.getRoundedDrawable(layerDrawable))
+            } else {
+                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)) {
+                    holder.binding.dialogAvatar.loadAvatar(user, model.name!!)
+                } else {
+                    holder.binding.dialogAvatar.visibility = View.GONE
+                }
+                ConversationType.ROOM_GROUP_CALL -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                    holder.binding.dialogAvatar.setImageDrawable(
+                        DisplayUtils.getRoundedDrawable(
+                            viewThemeUtils.talk.themePlaceholderAvatar(
+                                holder.binding.dialogAvatar,
+                                R.drawable.ic_avatar_group
+                            )
+                        )
+                    )
+                } else {
+                    holder.binding.dialogAvatar.setImageDrawable(
+                        ContextCompat.getDrawable(context, R.drawable.ic_circular_group)
+                    )
+                }
+                ConversationType.ROOM_PUBLIC_CALL -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                    holder.binding.dialogAvatar.setImageDrawable(
+                        DisplayUtils.getRoundedDrawable(
+                            viewThemeUtils.talk.themePlaceholderAvatar(
+                                holder.binding.dialogAvatar,
+                                R.drawable.ic_avatar_link
+                            )
+                        )
+                    )
+                } else {
+                    holder.binding.dialogAvatar.setImageDrawable(
+                        ContextCompat.getDrawable(context, R.drawable.ic_circular_link)
+                    )
+                }
+                else -> holder.binding.dialogAvatar.visibility = View.GONE
+            }
+        }
+    }
+
+    override fun filter(constraint: String?): Boolean {
+        return model.displayName != null &&
+            Pattern
+                .compile(constraint!!, Pattern.CASE_INSENSITIVE or Pattern.LITERAL)
+                .matcher(model.displayName!!.trim { it <= ' ' })
+                .find()
+    }
+
+    override fun getHeader(): GenericTextHeaderItem? {
+        return header
+    }
+
+    override fun setHeader(header: GenericTextHeaderItem?) {
+        this.header = header
+    }
+
+    class ConversationItemViewHolder(view: View?, adapter: FlexibleAdapter<*>?) : FlexibleViewHolder(view, adapter) {
+        var binding: RvItemConversationWithLastMessageBinding
+
+        init {
+            binding = RvItemConversationWithLastMessageBinding.bind(view!!)
+        }
+    }
+
+    companion object {
+        const val VIEW_TYPE = R.layout.rv_item_conversation_with_last_message
+        private const val STATUS_SIZE_IN_DP = 9f
+    }
+}

+ 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 {

+ 6 - 6
app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt

@@ -587,16 +587,16 @@ class ConversationsListController(bundle: Bundle) :
                     if (activity != null) {
                         val conversationItem = ConversationItem(
                             conversation,
-                            currentUser,
-                            activity,
+                            currentUser!!,
+                            activity!!,
                             userStatuses[conversation.name],
                             viewThemeUtils
                         )
                         conversationItems.add(conversationItem)
                         val conversationItemWithHeader = ConversationItem(
                             conversation,
-                            currentUser,
-                            activity,
+                            currentUser!!,
+                            activity!!,
                             callHeaderItems[headerTitle],
                             userStatuses[conversation.name],
                             viewThemeUtils
@@ -659,8 +659,8 @@ class ConversationsListController(bundle: Bundle) :
                         }
                         val conversationItem = ConversationItem(
                             conversation,
-                            currentUser,
-                            activity,
+                            currentUser!!,
+                            activity!!,
                             callHeaderItems[headerTitle],
                             userStatuses[conversation.name],
                             viewThemeUtils

+ 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
+    }
+}

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

@@ -32,6 +32,11 @@ import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.Typeface;
 import android.graphics.drawable.Animatable;
 import android.graphics.drawable.BitmapDrawable;
@@ -164,21 +169,28 @@ public class DisplayUtils {
         }
     }
 
-    public static Drawable getRoundedDrawable(Drawable drawable) {
-        Bitmap bitmap = getBitmap(drawable);
-        new RoundAsCirclePostprocessor(true).process(bitmap);
-        return new BitmapDrawable(bitmap);
-    }
+    public static Bitmap roundBitmap(Bitmap bitmap) {
+        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
 
-    public static Bitmap getRoundedBitmapFromVectorDrawableResource(Resources resources, int resource) {
-        VectorDrawable vectorDrawable = (VectorDrawable) ResourcesCompat.getDrawable(resources, resource, null);
-        Bitmap bitmap = getBitmap(vectorDrawable);
-        new RoundPostprocessor(true).process(bitmap);
-        return bitmap;
+        final Canvas canvas = new Canvas(output);
+
+        final Paint paint = new Paint();
+        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        final RectF rectF = new RectF(rect);
+
+        paint.setAntiAlias(true);
+        canvas.drawARGB(0, 0, 0, 0);
+        canvas.drawOval(rectF, paint);
+
+        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
+        canvas.drawBitmap(bitmap, rect, rect, paint);
+
+        return output;
     }
 
-    public static Drawable getRoundedBitmapDrawableFromVectorDrawableResource(Resources resources, int resource) {
-        return new BitmapDrawable(getRoundedBitmapFromVectorDrawableResource(resources, resource));
+    public static Drawable getRoundedDrawable(Drawable drawable) {
+        Bitmap bitmap = getBitmap(drawable);
+        return new BitmapDrawable(roundBitmap(bitmap));
     }
 
     public static Bitmap getBitmap(Drawable drawable) {
@@ -605,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 - 5
app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt

@@ -38,7 +38,6 @@ import com.facebook.common.references.CloseableReference
 import com.facebook.datasource.DataSources
 import com.facebook.drawee.backends.pipeline.Fresco
 import com.facebook.imagepipeline.image.CloseableBitmap
-import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor
 import com.nextcloud.talk.BuildConfig
 import com.nextcloud.talk.R
 import com.nextcloud.talk.data.user.model.User
@@ -334,10 +333,7 @@ object NotificationUtils {
         val closeableImageRef = DataSources.waitForFinalResult(dataSource) as CloseableReference<CloseableBitmap>?
         val bitmap = closeableImageRef?.get()?.underlyingBitmap
         if (bitmap != null) {
-            // According to Fresco documentation a copy of the bitmap should be made before closing the references.
-            // However, it seems to work without making a copy... ;-)
-            RoundAsCirclePostprocessor(true).process(bitmap)
-            avatarIcon = IconCompat.createWithBitmap(bitmap)
+            avatarIcon = IconCompat.createWithBitmap(DisplayUtils.roundBitmap(bitmap))
         }
         CloseableReference.closeSafely(closeableImageRef)
         dataSource.close()

+ 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"