浏览代码

Merge pull request #2448 from nextcloud/revert-2370-chore/set-min-sdk-version-to-23

Revert "Set minSdkVersion to 23 (Android 6)"
Tim Krüger 2 年之前
父节点
当前提交
847a361f1b

+ 1 - 1
app/build.gradle

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

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

@@ -0,0 +1,371 @@
+/*
+ * 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);
+        }
+    }
+}

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

@@ -1,352 +0,0 @@
-/*
- * 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
-    }
-}

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

@@ -2,8 +2,6 @@
  * 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
  *
@@ -29,9 +27,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
@@ -74,7 +72,7 @@ data class MessageResultItem constructor(
     ) {
         holder.binding.conversationTitle.text = messageEntry.title
         bindMessageExcerpt(holder)
-        holder.binding.thumbnail.loadThumbnail(messageEntry.thumbnailURL, currentUser)
+        loadImage(holder)
     }
 
     private fun bindMessageExcerpt(holder: ViewHolder) {
@@ -85,6 +83,17 @@ 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

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

@@ -1,98 +0,0 @@
-/*
- * 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
-    }
-}

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

@@ -32,11 +32,6 @@ 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;
@@ -169,28 +164,21 @@ public class DisplayUtils {
         }
     }
 
-    public static Bitmap roundBitmap(Bitmap bitmap) {
-        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
-
-        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);
+    public static Drawable getRoundedDrawable(Drawable drawable) {
+        Bitmap bitmap = getBitmap(drawable);
+        new RoundAsCirclePostprocessor(true).process(bitmap);
+        return new BitmapDrawable(bitmap);
+    }
 
-        return output;
+    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;
     }
 
-    public static Drawable getRoundedDrawable(Drawable drawable) {
-        Bitmap bitmap = getBitmap(drawable);
-        return new BitmapDrawable(roundBitmap(bitmap));
+    public static Drawable getRoundedBitmapDrawableFromVectorDrawableResource(Resources resources, int resource) {
+        return new BitmapDrawable(getRoundedBitmapFromVectorDrawableResource(resources, resource));
     }
 
     public static Bitmap getBitmap(Drawable drawable) {
@@ -617,6 +605,30 @@ 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()) {

+ 5 - 1
app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt

@@ -38,6 +38,7 @@ 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
@@ -333,7 +334,10 @@ object NotificationUtils {
         val closeableImageRef = DataSources.waitForFinalResult(dataSource) as CloseableReference<CloseableBitmap>?
         val bitmap = closeableImageRef?.get()?.underlyingBitmap
         if (bitmap != null) {
-            avatarIcon = IconCompat.createWithBitmap(DisplayUtils.roundBitmap(bitmap))
+            // 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)
         }
         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">
 
-        <ImageView
+        <com.facebook.drawee.view.SimpleDraweeView
             android:id="@id/dialogAvatar"
             android:layout_width="@dimen/small_item_height"
             android:layout_height="@dimen/small_item_height"

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

@@ -3,8 +3,6 @@
   ~
   ~ @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>
   ~
@@ -37,7 +35,7 @@
     android:layout_margin="@dimen/double_margin_between_elements"
     tools:background="@color/white">
 
-    <ImageView
+    <com.facebook.drawee.view.SimpleDraweeView
         android:id="@+id/thumbnail"
         android:layout_width="@dimen/small_item_height"
         android:layout_height="@dimen/small_item_height"

+ 1 - 1
scripts/analysis/findbugs-results.txt

@@ -1 +1 @@
-134
+136