diff options
author | drone <drone@localhost> | 2022-09-29 16:16:56 +0300 |
---|---|---|
committer | drone <drone@localhost> | 2022-09-29 16:16:56 +0300 |
commit | 3a90d176495b7b6c7e965cf87d6e2b604831c1c0 (patch) | |
tree | 4bcd53009e456b6bb49c3f7d67176b7907d6f636 /app | |
parent | 1382a23b39a17217f1782a30cae1ed7f11157737 (diff) | |
parent | 863052b53e3051bb7e0fd4904d348fa294835821 (diff) |
Merge commit '863052b53e3051bb7e0fd4904d348fa294835821'
Diffstat (limited to 'app')
10 files changed, 490 insertions, 434 deletions
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/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 <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); - } - } -} 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..576ddccae --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt @@ -0,0 +1,352 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * @author Andy Scherzinger + * @author Marcel Hibbe + * @author Tim Krüger + * Copyright (C) 2022 Tim Krüger <t@timkrueger.me> + * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> + * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> + * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package com.nextcloud.talk.adapters.items + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import android.os.Build +import android.text.TextUtils +import android.text.format.DateUtils +import android.view.View +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import com.nextcloud.talk.R +import com.nextcloud.talk.adapters.items.ConversationItem.ConversationItemViewHolder +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.databinding.RvItemConversationWithLastMessageBinding +import com.nextcloud.talk.extensions.loadAvatar +import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.models.json.conversations.Conversation +import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType +import com.nextcloud.talk.models.json.status.Status +import com.nextcloud.talk.ui.StatusDrawable +import com.nextcloud.talk.ui.theme.ViewThemeUtils +import com.nextcloud.talk.utils.DisplayUtils +import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import eu.davidea.flexibleadapter.items.IFilterable +import eu.davidea.flexibleadapter.items.IFlexible +import eu.davidea.flexibleadapter.items.ISectionable +import eu.davidea.viewholders.FlexibleViewHolder +import java.util.regex.Pattern + +class ConversationItem( + val model: Conversation, + private val user: User, + private val context: Context, + private val status: Status?, + private val viewThemeUtils: ViewThemeUtils +) : AbstractFlexibleItem<ConversationItemViewHolder>(), + ISectionable<ConversationItemViewHolder, GenericTextHeaderItem?>, + IFilterable<String?> { + private var header: GenericTextHeaderItem? = null + + constructor( + conversation: Conversation, + user: User, + activityContext: Context, + genericTextHeaderItem: GenericTextHeaderItem?, + status: Status?, + viewThemeUtils: ViewThemeUtils + ) : this(conversation, user, activityContext, status, viewThemeUtils) { + header = genericTextHeaderItem + } + + override fun equals(other: Any?): Boolean { + if (other is ConversationItem) { + return model == other.model && status == other.status + } + return false + } + + override fun hashCode(): Int { + var result = model.hashCode() + result = 31 * result + (status?.hashCode() ?: 0) + return result + } + + override fun getLayoutRes(): Int { + return R.layout.rv_item_conversation_with_last_message + } + + override fun getItemViewType(): Int { + return VIEW_TYPE + } + + override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>?>?): ConversationItemViewHolder { + return ConversationItemViewHolder(view, adapter) + } + + @SuppressLint("SetTextI18n") + override fun bindViewHolder( + adapter: FlexibleAdapter<IFlexible<*>?>, + holder: ConversationItemViewHolder, + position: Int, + payloads: List<Any> + ) { + val appContext = sharedApplication!!.applicationContext + holder.binding.dialogName.setTextColor( + ResourcesCompat.getColor( + context.resources, + R.color.conversation_item_header, + null + ) + ) + if (adapter.hasFilter()) { + viewThemeUtils.platform.highlightText( + holder.binding.dialogName, + model.displayName!!, adapter.getFilter(String::class.java).toString() + ) + } else { + holder.binding.dialogName.text = model.displayName + } + if (model.unreadMessages > 0) { + holder.binding.dialogName.setTypeface(holder.binding.dialogName.typeface, Typeface.BOLD) + holder.binding.dialogLastMessage.setTypeface(holder.binding.dialogLastMessage.typeface, Typeface.BOLD) + holder.binding.dialogUnreadBubble.visibility = View.VISIBLE + if (model.unreadMessages < 1000) { + holder.binding.dialogUnreadBubble.text = model.unreadMessages.toLong().toString() + } else { + holder.binding.dialogUnreadBubble.setText(R.string.tooManyUnreadMessages) + } + val lightBubbleFillColor = ColorStateList.valueOf( + ContextCompat.getColor( + context, + R.color.conversation_unread_bubble + ) + ) + val lightBubbleTextColor = ContextCompat.getColor( + context, + R.color.conversation_unread_bubble_text + ) + if (model.type === ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) { + viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble) + } else if (model.unreadMention) { + if (hasSpreedFeatureCapability(user, "direct-mention-flag")) { + if (model.unreadMentionDirect!!) { + viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble) + } else { + viewThemeUtils.material.colorChipOutlined(holder.binding.dialogUnreadBubble, 6.0f) + } + } else { + viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble) + } + } else { + holder.binding.dialogUnreadBubble.chipBackgroundColor = lightBubbleFillColor + holder.binding.dialogUnreadBubble.setTextColor(lightBubbleTextColor) + } + } else { + holder.binding.dialogName.setTypeface(null, Typeface.NORMAL) + holder.binding.dialogDate.setTypeface(null, Typeface.NORMAL) + holder.binding.dialogLastMessage.setTypeface(null, Typeface.NORMAL) + holder.binding.dialogUnreadBubble.visibility = View.GONE + } + if (model.favorite) { + holder.binding.favoriteConversationImageView.visibility = View.VISIBLE + } else { + holder.binding.favoriteConversationImageView.visibility = View.GONE + } + if (status != null && ConversationType.ROOM_SYSTEM !== model.type) { + val size = DisplayUtils.convertDpToPixel(STATUS_SIZE_IN_DP, appContext) + holder.binding.userStatusImage.visibility = View.VISIBLE + holder.binding.userStatusImage.setImageDrawable( + StatusDrawable( + status.status, + status.icon, + size, + context.resources.getColor(R.color.bg_default), + appContext + ) + ) + } else { + holder.binding.userStatusImage.visibility = View.GONE + } + if (model.lastMessage != null) { + holder.binding.dialogDate.visibility = View.VISIBLE + holder.binding.dialogDate.text = DateUtils.getRelativeTimeSpanString( + model.lastActivity * 1000L, + System.currentTimeMillis(), + 0, + DateUtils.FORMAT_ABBREV_RELATIVE + ) + if (!TextUtils.isEmpty(model.lastMessage!!.systemMessage) || + ConversationType.ROOM_SYSTEM === model.type + ) { + holder.binding.dialogLastMessage.text = model.lastMessage!!.text + } else { + model.lastMessage!!.activeUser = user + val text: String + if (model.lastMessage!!.getCalculateMessageType() === ChatMessage.MessageType.REGULAR_TEXT_MESSAGE) { + if (model.lastMessage!!.actorId == user.userId) { + text = String.format( + appContext.getString(R.string.nc_formatted_message_you), + model.lastMessage!!.lastMessageDisplayText + ) + } else { + val authorDisplayName = + if (!TextUtils.isEmpty(model.lastMessage!!.actorDisplayName)) { + model.lastMessage!!.actorDisplayName + } else if ("guests" == model.lastMessage!!.actorType) { + appContext.getString(R.string.nc_guest) + } else { + "" + } + text = String.format( + appContext.getString(R.string.nc_formatted_message), + authorDisplayName, + model.lastMessage!!.lastMessageDisplayText + ) + } + } else { + text = model.lastMessage!!.lastMessageDisplayText + } + holder.binding.dialogLastMessage.text = text + } + } else { + holder.binding.dialogDate.visibility = View.GONE + holder.binding.dialogLastMessage.setText(R.string.nc_no_messages_yet) + } + holder.binding.dialogAvatar.visibility = View.VISIBLE + var shouldLoadAvatar = true + var objectType: String? + if (!TextUtils.isEmpty(model.objectType.also { objectType = it })) { + when (objectType) { + "share:password" -> { + shouldLoadAvatar = false + holder.binding.dialogAvatar.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.ic_circular_lock + ) + ) + } + "file" -> { + shouldLoadAvatar = false + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + holder.binding.dialogAvatar.setImageDrawable( + DisplayUtils.getRoundedDrawable( + viewThemeUtils.talk.themePlaceholderAvatar( + holder.binding.dialogAvatar, + R.drawable.ic_avatar_document + ) + ) + ) + } else { + holder.binding.dialogAvatar.setImageDrawable( + ContextCompat.getDrawable(context, R.drawable.ic_circular_document) + ) + } + } + else -> {} + } + } + if (ConversationType.ROOM_SYSTEM == model.type) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val layers = arrayOfNulls<Drawable>(2) + layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background) + layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground) + val layerDrawable = LayerDrawable(layers) + holder.binding.dialogAvatar.loadAvatar(DisplayUtils.getRoundedDrawable(layerDrawable)) + } else { + holder.binding.dialogAvatar.loadAvatar(R.mipmap.ic_launcher) + } + shouldLoadAvatar = false + } + if (shouldLoadAvatar) { + when (model.type) { + ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(model.name)) { + holder.binding.dialogAvatar.loadAvatar(user, model.name!!) + } else { + holder.binding.dialogAvatar.visibility = View.GONE + } + ConversationType.ROOM_GROUP_CALL -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + holder.binding.dialogAvatar.setImageDrawable( + DisplayUtils.getRoundedDrawable( + viewThemeUtils.talk.themePlaceholderAvatar( + holder.binding.dialogAvatar, + R.drawable.ic_avatar_group + ) + ) + ) + } else { + holder.binding.dialogAvatar.setImageDrawable( + ContextCompat.getDrawable(context, R.drawable.ic_circular_group) + ) + } + ConversationType.ROOM_PUBLIC_CALL -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + holder.binding.dialogAvatar.setImageDrawable( + DisplayUtils.getRoundedDrawable( + viewThemeUtils.talk.themePlaceholderAvatar( + holder.binding.dialogAvatar, + R.drawable.ic_avatar_link + ) + ) + ) + } else { + holder.binding.dialogAvatar.setImageDrawable( + ContextCompat.getDrawable(context, R.drawable.ic_circular_link) + ) + } + else -> holder.binding.dialogAvatar.visibility = View.GONE + } + } + } + + override fun filter(constraint: String?): Boolean { + return model.displayName != null && + Pattern + .compile(constraint!!, Pattern.CASE_INSENSITIVE or Pattern.LITERAL) + .matcher(model.displayName!!.trim { it <= ' ' }) + .find() + } + + override fun getHeader(): GenericTextHeaderItem? { + return header + } + + override fun setHeader(header: GenericTextHeaderItem?) { + this.header = header + } + + class ConversationItemViewHolder(view: View?, adapter: FlexibleAdapter<*>?) : FlexibleViewHolder(view, adapter) { + var binding: RvItemConversationWithLastMessageBinding + + init { + binding = RvItemConversationWithLastMessageBinding.bind(view!!) + } + } + + companion object { + const val VIEW_TYPE = R.layout.rv_item_conversation_with_last_message + private const val STATUS_SIZE_IN_DP = 9f + } +} 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 <t@timkrueger.me> * Copyright (C) 2022 Álvaro Brey * Copyright (C) 2022 Nextcloud GmbH * @@ -27,9 +29,9 @@ import androidx.recyclerview.widget.RecyclerView import com.nextcloud.talk.R import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.RvItemSearchMessageBinding +import com.nextcloud.talk.extensions.loadThumbnail import com.nextcloud.talk.models.domain.SearchMessageEntry import com.nextcloud.talk.ui.theme.ViewThemeUtils -import com.nextcloud.talk.utils.DisplayUtils import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.IFilterable @@ -72,7 +74,7 @@ data class MessageResultItem constructor( ) { holder.binding.conversationTitle.text = messageEntry.title bindMessageExcerpt(holder) - loadImage(holder) + holder.binding.thumbnail.loadThumbnail(messageEntry.thumbnailURL, currentUser) } private fun bindMessageExcerpt(holder: ViewHolder) { @@ -83,17 +85,6 @@ data class MessageResultItem constructor( ) } - private fun loadImage(holder: ViewHolder) { - DisplayUtils.loadAvatarPlaceholder(holder.binding.thumbnail) - if (messageEntry.thumbnailURL != null) { - val imageRequest = DisplayUtils.getImageRequestForUrl( - messageEntry.thumbnailURL, - currentUser - ) - DisplayUtils.loadImage(holder.binding.thumbnail, imageRequest) - } - } - override fun filter(constraint: String?): Boolean = true override fun getItemViewType(): Int { 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 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 <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 + } +} 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..7668215c6 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) { @@ -605,30 +617,6 @@ public class DisplayUtils { avatarImageView.setController(draweeController); } - public static void loadAvatarPlaceholder(final SimpleDraweeView targetView) { - final Context context = targetView.getContext(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - Drawable[] layers = new Drawable[2]; - layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background); - layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground); - LayerDrawable layerDrawable = new LayerDrawable(layers); - - targetView.getHierarchy().setPlaceholderImage( - DisplayUtils.getRoundedDrawable(layerDrawable)); - } else { - targetView.getHierarchy().setPlaceholderImage(R.mipmap.ic_launcher); - } - } - - public static void loadImage(final SimpleDraweeView targetView, final ImageRequest imageRequest) { - final DraweeController newController = Fresco.newDraweeControllerBuilder() - .setOldController(targetView.getController()) - .setAutoPlayAnimations(true) - .setImageRequest(imageRequest) - .build(); - targetView.setController(newController); - } - public static @StringRes int getSortOrderStringId(FileSortOrder sortOrder) { switch (sortOrder.getName()) { 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<CloseableBitmap>? val bitmap = closeableImageRef?.get()?.underlyingBitmap if (bitmap != null) { - // According to Fresco documentation a copy of the bitmap should be made before closing the references. - // However, it seems to work without making a copy... ;-) - RoundAsCirclePostprocessor(true).process(bitmap) - avatarIcon = IconCompat.createWithBitmap(bitmap) + avatarIcon = IconCompat.createWithBitmap(DisplayUtils.roundBitmap(bitmap)) } CloseableReference.closeSafely(closeableImageRef) dataSource.close() 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"> - <com.facebook.drawee.view.SimpleDraweeView + <ImageView android:id="@id/dialogAvatar" android:layout_width="@dimen/small_item_height" android:layout_height="@dimen/small_item_height" diff --git a/app/src/main/res/layout/rv_item_search_message.xml b/app/src/main/res/layout/rv_item_search_message.xml index a8ec4aaaf..c531d4e39 100644 --- a/app/src/main/res/layout/rv_item_search_message.xml +++ b/app/src/main/res/layout/rv_item_search_message.xml @@ -3,6 +3,8 @@ ~ ~ @author Mario Danic ~ @author Andy Scherzinger + ~ @author Tim Krüger + ~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me> ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com> ~ @@ -35,7 +37,7 @@ android:layout_margin="@dimen/double_margin_between_elements" tools:background="@color/white"> - <com.facebook.drawee.view.SimpleDraweeView + <ImageView android:id="@+id/thumbnail" android:layout_width="@dimen/small_item_height" android:layout_height="@dimen/small_item_height" |