From 2d8492ae1e646bb095c5bb283d048acf537734b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Fri, 30 Sep 2022 10:33:50 +0200 Subject: [PATCH] Revert "Set minSdkVersion to 23 (Android 6)" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Krüger --- app/build.gradle | 2 +- .../talk/adapters/items/ConversationItem.java | 371 ++++++++++++++++++ .../talk/adapters/items/ConversationItem.kt | 352 ----------------- .../talk/adapters/items/MessageResultItem.kt | 17 +- .../ConversationsListController.kt | 12 +- .../talk/extensions/ImageViewExtensions.kt | 98 ----- .../nextcloud/talk/utils/DisplayUtils.java | 62 +-- .../nextcloud/talk/utils/NotificationUtils.kt | 6 +- ...rv_item_conversation_with_last_message.xml | 2 +- .../res/layout/rv_item_search_message.xml | 4 +- scripts/analysis/findbugs-results.txt | 2 +- 11 files changed, 436 insertions(+), 492 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java delete mode 100644 app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt delete mode 100644 app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt diff --git a/app/build.gradle b/app/build.gradle index 22dd9ab25..5cb2e675a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { namespace 'com.nextcloud.talk' defaultConfig { - minSdkVersion 23 + minSdkVersion 21 targetSdkVersion 31 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java new file mode 100644 index 000000000..7110277df --- /dev/null +++ b/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 + * Copyright (C) 2021 Andy Scherzinger + * Copyright (C) 2017-2018 Mario Danic + * + * 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 . + */ + +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 implements + ISectionable, IFilterable { + + 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 adapter) { + return new ConversationItemViewHolder(view, adapter); + } + + @SuppressLint("SetTextI18n") + @Override + public void bindViewHolder(FlexibleAdapter adapter, + ConversationItemViewHolder holder, + int position, + List 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); + } + } +} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt deleted file mode 100644 index 576ddccae..000000000 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt +++ /dev/null @@ -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 - * Copyright (C) 2022 Marcel Hibbe - * Copyright (C) 2021 Andy Scherzinger - * Copyright (C) 2017-2018 Mario Danic - * - * 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 . - */ -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(), - ISectionable, - IFilterable { - 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?>?): ConversationItemViewHolder { - return ConversationItemViewHolder(view, adapter) - } - - @SuppressLint("SetTextI18n") - override fun bindViewHolder( - adapter: FlexibleAdapter?>, - holder: ConversationItemViewHolder, - position: Int, - payloads: List - ) { - 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(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 - } -} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/MessageResultItem.kt b/app/src/main/java/com/nextcloud/talk/adapters/items/MessageResultItem.kt index 76b17cba9..d00df59d4 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/MessageResultItem.kt +++ b/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 * 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 { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt index fd666ed36..14abbc2f2 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt +++ b/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 diff --git a/app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt b/app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt deleted file mode 100644 index d23ba8f37..000000000 --- a/app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Tim Krüger - * Copyright (C) 2022 Tim Krüger - * 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 . - */ - -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(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 - } -} diff --git a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java index 7668215c6..3f3d2ed9b 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java +++ b/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); - - return output; - } - public static Drawable getRoundedDrawable(Drawable drawable) { Bitmap bitmap = getBitmap(drawable); - return new BitmapDrawable(roundBitmap(bitmap)); + new RoundAsCirclePostprocessor(true).process(bitmap); + return new BitmapDrawable(bitmap); + } + + 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 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()) { diff --git a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt index c361b6503..4341f18c1 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt +++ b/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? 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() diff --git a/app/src/main/res/layout/rv_item_conversation_with_last_message.xml b/app/src/main/res/layout/rv_item_conversation_with_last_message.xml index c13b8fe72..0428af241 100644 --- a/app/src/main/res/layout/rv_item_conversation_with_last_message.xml +++ b/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"> - ~ Copyright (C) 2021 Andy Scherzinger ~ Copyright (C) 2017-2018 Mario Danic ~ @@ -37,7 +35,7 @@ android:layout_margin="@dimen/double_margin_between_elements" tools:background="@color/white"> -