From 8a0e60f68af1419ba493adc9dd25659e8e20cb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Thu, 15 Sep 2022 18:25:50 +0200 Subject: Set minSdkVersion to 23 (Android 6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because of updating the minSdkVersion to 23 it comes to some obscure UI freezes when using frescos 'RoundPostprocessor#process' to round avatar bitmaps. So the function 'DisplayUtils#roundBitmap' is adopted from Nextcloud Files for Android. Signed-off-by: Tim Krüger --- app/build.gradle | 2 +- .../com/nextcloud/talk/utils/DisplayUtils.java | 36 ++++++++++++++-------- .../com/nextcloud/talk/utils/NotificationUtils.kt | 6 +--- 3 files changed, 26 insertions(+), 18 deletions(-) (limited to 'app') diff --git a/app/build.gradle b/app/build.gradle index 5cb2e675a..22dd9ab25 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { namespace 'com.nextcloud.talk' defaultConfig { - minSdkVersion 21 + minSdkVersion 23 targetSdkVersion 31 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 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 3f3d2ed9b..dbd094b08 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java +++ b/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) { 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 4341f18c1..c361b6503 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt +++ b/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? 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() -- cgit v1.2.3 From b2d6211b3c302fb9497656319274dd656a1af8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Thu, 29 Sep 2022 10:17:44 +0200 Subject: Convert 'ConverstationItem' from Java to Kotlin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is mandetory to replace Fresco with Coil. See: #2376, #2227 Signed-off-by: Tim Krüger --- .../talk/adapters/items/ConversationItem.java | 371 --------------------- .../talk/adapters/items/ConversationItem.kt | 371 +++++++++++++++++++++ .../controllers/ConversationsListController.kt | 12 +- 3 files changed, 377 insertions(+), 377 deletions(-) delete mode 100644 app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java create mode 100644 app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt (limited to 'app') 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 deleted file mode 100644 index 7110277df..000000000 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * 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 new file mode 100644 index 000000000..a655cffc4 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt @@ -0,0 +1,371 @@ +/* + * 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.facebook.drawee.backends.pipeline.Fresco +import com.facebook.drawee.interfaces.DraweeController +import com.nextcloud.talk.R +import com.nextcloud.talk.adapters.items.ConversationItem.ConversationItemViewHolder +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.databinding.RvItemConversationWithLastMessageBinding +import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.models.json.conversations.Conversation +import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType +import com.nextcloud.talk.models.json.status.Status +import com.nextcloud.talk.ui.StatusDrawable +import com.nextcloud.talk.ui.theme.ViewThemeUtils +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.DisplayUtils +import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability +import eu.davidea.flexibleadapter.FlexibleAdapter +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.dialogAvatar.controller = null + 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.hierarchy.setPlaceholderImage( + DisplayUtils.getRoundedDrawable(layerDrawable) + ) + } else { + holder.binding.dialogAvatar.hierarchy.setPlaceholderImage(R.mipmap.ic_launcher) + } + shouldLoadAvatar = false + } + if (shouldLoadAvatar) { + when (model.type) { + ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(model.name)) { + val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() + .setOldController(holder.binding.dialogAvatar.controller) + .setAutoPlayAnimations(true) + .setImageRequest( + DisplayUtils.getImageRequestForUrl( + ApiUtils.getUrlForAvatar( + user.baseUrl, + model.name, + true + ), + user + ) + ) + .build() + holder.binding.dialogAvatar.controller = draweeController + } 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/controllers/ConversationsListController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt index 14abbc2f2..fd666ed36 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 -- cgit v1.2.3 From 863052b53e3051bb7e0fd4904d348fa294835821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Thu, 29 Sep 2022 13:19:58 +0200 Subject: [WIP] Replace Fresco with Coil MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fresco is replaced with Coil verywhere. But Coil is not used directly to avoid splintering the dependency everywhere in the code. Coil is wrapped by extension functions for 'ImageView'. Some shared functionality is moved from 'DisplayUtils' into the 'ImageViewExtensions'. Resolves: #2227, #2376 Signed-off-by: Tim Krüger --- .../talk/adapters/items/ConversationItem.kt | 27 +----- .../talk/adapters/items/MessageResultItem.kt | 17 +--- .../talk/extensions/ImageViewExtensions.kt | 98 ++++++++++++++++++++++ .../com/nextcloud/talk/utils/DisplayUtils.java | 24 ------ .../rv_item_conversation_with_last_message.xml | 2 +- app/src/main/res/layout/rv_item_search_message.xml | 4 +- 6 files changed, 110 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt (limited to 'app') 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 index a655cffc4..576ddccae 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt @@ -37,20 +37,18 @@ import android.text.format.DateUtils import android.view.View import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.interfaces.DraweeController import com.nextcloud.talk.R import com.nextcloud.talk.adapters.items.ConversationItem.ConversationItemViewHolder import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.RvItemConversationWithLastMessageBinding +import com.nextcloud.talk.extensions.loadAvatar import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType import com.nextcloud.talk.models.json.status.Status import com.nextcloud.talk.ui.StatusDrawable import com.nextcloud.talk.ui.theme.ViewThemeUtils -import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability import eu.davidea.flexibleadapter.FlexibleAdapter @@ -116,7 +114,6 @@ class ConversationItem( payloads: List ) { val appContext = sharedApplication!!.applicationContext - holder.binding.dialogAvatar.controller = null holder.binding.dialogName.setTextColor( ResourcesCompat.getColor( context.resources, @@ -278,32 +275,16 @@ class ConversationItem( layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background) layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground) val layerDrawable = LayerDrawable(layers) - holder.binding.dialogAvatar.hierarchy.setPlaceholderImage( - DisplayUtils.getRoundedDrawable(layerDrawable) - ) + holder.binding.dialogAvatar.loadAvatar(DisplayUtils.getRoundedDrawable(layerDrawable)) } else { - holder.binding.dialogAvatar.hierarchy.setPlaceholderImage(R.mipmap.ic_launcher) + holder.binding.dialogAvatar.loadAvatar(R.mipmap.ic_launcher) } shouldLoadAvatar = false } if (shouldLoadAvatar) { when (model.type) { ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(model.name)) { - val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() - .setOldController(holder.binding.dialogAvatar.controller) - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - user.baseUrl, - model.name, - true - ), - user - ) - ) - .build() - holder.binding.dialogAvatar.controller = draweeController + holder.binding.dialogAvatar.loadAvatar(user, model.name!!) } else { holder.binding.dialogAvatar.visibility = View.GONE } 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 d00df59d4..76b17cba9 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,6 +2,8 @@ * 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 * @@ -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 { diff --git a/app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt b/app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt new file mode 100644 index 000000000..d23ba8f37 --- /dev/null +++ b/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 + * 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 dbd094b08..7668215c6 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java @@ -617,30 +617,6 @@ public class DisplayUtils { avatarImageView.setController(draweeController); } - public static void loadAvatarPlaceholder(final SimpleDraweeView targetView) { - final Context context = targetView.getContext(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - Drawable[] layers = new Drawable[2]; - layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background); - layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground); - LayerDrawable layerDrawable = new LayerDrawable(layers); - - targetView.getHierarchy().setPlaceholderImage( - DisplayUtils.getRoundedDrawable(layerDrawable)); - } else { - targetView.getHierarchy().setPlaceholderImage(R.mipmap.ic_launcher); - } - } - - public static void loadImage(final SimpleDraweeView targetView, final ImageRequest imageRequest) { - final DraweeController newController = Fresco.newDraweeControllerBuilder() - .setOldController(targetView.getController()) - .setAutoPlayAnimations(true) - .setImageRequest(imageRequest) - .build(); - targetView.setController(newController); - } - public static @StringRes int getSortOrderStringId(FileSortOrder sortOrder) { switch (sortOrder.getName()) { 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 0428af241..c13b8fe72 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 ~ @@ -35,7 +37,7 @@ android:layout_margin="@dimen/double_margin_between_elements" tools:background="@color/white"> -