From 537f375f863cae1de81b84a0050ea873e4661f10 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: [PATCH 1/6] 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 | 367 ----------------- .../talk/adapters/items/ConversationItem.kt | 368 ++++++++++++++++++ .../ConversationsListController.kt | 12 +- 3 files changed, 374 insertions(+), 373 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 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 a37396189..000000000 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java +++ /dev/null @@ -1,367 +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.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.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 ViewThemeUtils viewThemeUtils; - - - public ConversationItem(Conversation conversation, User user, Context activityContext, final ViewThemeUtils viewThemeUtils) { - this.conversation = conversation; - this.user = user; - this.context = activityContext; - this.viewThemeUtils = viewThemeUtils; - } - - public ConversationItem(Conversation conversation, User user, - Context activityContext, GenericTextHeaderItem genericTextHeaderItem, - final ViewThemeUtils viewThemeUtils) { - this(conversation, user, activityContext, viewThemeUtils); - this.header = genericTextHeaderItem; - } - - @Override - public boolean equals(Object o) { - if (o instanceof ConversationItem) { - ConversationItem inItem = (ConversationItem) o; - return conversation.equals(inItem.getModel()); - } - return false; - } - - public Conversation getModel() { - return conversation; - } - - @Override - public int hashCode() { - int result = conversation.hashCode(); - result = 31 * result; - 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 (conversation.getStatus() != 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( - conversation.getStatus(), - conversation.getStatusIcon(), - 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.getType() == Conversation.ConversationType.ROOM_SYSTEM) { - 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..c129852dc --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt @@ -0,0 +1,368 @@ +/* + * 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.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 viewThemeUtils: ViewThemeUtils +) : AbstractFlexibleItem(), + ISectionable, + IFilterable { + private var header: GenericTextHeaderItem? = null + + constructor( + conversation: Conversation, + user: User, + activityContext: Context, + genericTextHeaderItem: GenericTextHeaderItem?, + viewThemeUtils: ViewThemeUtils + ) : this(conversation, user, activityContext, viewThemeUtils) { + header = genericTextHeaderItem + } + + override fun equals(other: Any?): Boolean { + if (other is ConversationItem) { + return model == other.model + } + return false + } + + override fun hashCode(): Int { + var result = model.hashCode() + result *= 31 + 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 (model != 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( + model.status, + model.status, + 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 ae1d5b4ae..8df593a46 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt @@ -585,15 +585,15 @@ class ConversationsListController(bundle: Bundle) : if (activity != null) { val conversationItem = ConversationItem( conversation, - currentUser, - activity, + currentUser!!, + activity!!, viewThemeUtils ) conversationItems.add(conversationItem) val conversationItemWithHeader = ConversationItem( conversation, - currentUser, - activity, + currentUser!!, + activity!!, callHeaderItems[headerTitle], viewThemeUtils ) @@ -651,8 +651,8 @@ class ConversationsListController(bundle: Bundle) : } val conversationItem = ConversationItem( conversation, - currentUser, - activity, + currentUser!!, + activity!!, callHeaderItems[headerTitle], viewThemeUtils ) From 49da4639719952ee04a71870407bc950c41232ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Fri, 14 Oct 2022 11:53:48 +0200 Subject: [PATCH 2/6] Replace Fresco with Coil MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fresco is replaced with Coil everywhere to make it possible to set 'minSdkVersion' to 23. 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'. The exisiting initialization of Coil has also be changed. The usage of the self initialized OKHttp client is removed. If this one is added the caching of the http client is used by Coil additionally to memory and disk cache. Resolves: #2227, #2376 Signed-off-by: Tim Krüger --- app/build.gradle | 9 +- .../talk/activities/CallActivity.java | 33 +- .../activities/CallNotificationActivity.kt | 41 +-- .../talk/adapters/ParticipantsAdapter.java | 15 +- .../talk/adapters/ReactionsAdapter.kt | 2 +- .../talk/adapters/ReactionsViewHolder.kt | 45 +-- .../talk/adapters/items/AdvancedUserItem.java | 22 +- .../talk/adapters/items/ContactItem.java | 53 ++- .../talk/adapters/items/ConversationItem.kt | 303 ++++++++---------- .../items/MentionAutocompleteItem.java | 34 +- .../talk/adapters/items/MessageResultItem.kt | 17 +- .../talk/adapters/items/ParticipantItem.java | 59 +--- .../IncomingLinkPreviewMessageViewHolder.kt | 29 +- .../IncomingLocationMessageViewHolder.kt | 24 +- .../messages/IncomingPollMessageViewHolder.kt | 29 +- .../IncomingPreviewMessageViewHolder.java | 6 +- .../messages/IncomingTextMessageViewHolder.kt | 30 +- .../IncomingVoiceMessageViewHolder.kt | 15 +- .../talk/adapters/messages/LinkPreview.kt | 11 +- .../OutcomingPreviewMessageViewHolder.java | 4 +- .../messages/PreviewMessageViewHolder.kt | 34 +- .../application/NextcloudTalkApplication.kt | 29 +- .../MentionAutocompleteCallback.java | 2 +- .../talk/controllers/ChatController.kt | 93 +++--- .../controllers/ConversationInfoController.kt | 56 +--- .../ConversationsListController.kt | 123 +++---- .../talk/extensions/ImageViewExtensions.kt | 283 ++++++++++++++++ .../nextcloud/talk/jobs/NotificationWorker.kt | 2 +- .../adapters/PollResultVoterViewHolder.kt | 41 +-- .../PollResultVotersOverviewViewHolder.kt | 59 +--- .../talk/receivers/DirectReplyReceiver.kt | 2 +- .../RemoteFileBrowserItemsListViewHolder.kt | 23 +- .../RemoteFileBrowserItemsViewHolder.kt | 16 +- .../adapters/SharedItemsGridViewHolder.kt | 4 +- .../adapters/SharedItemsListViewHolder.kt | 13 +- .../adapters/SharedItemsViewHolder.kt | 57 +--- .../dialog/ChooseAccountDialogFragment.java | 17 +- .../ChooseAccountShareToDialogFragment.kt | 21 +- .../nextcloud/talk/utils/DisplayUtils.java | 208 +++--------- .../talk/utils/FileSortOrderByName.kt | 5 +- .../nextcloud/talk/utils/FileViewerUtils.kt | 4 +- .../nextcloud/talk/utils/NotificationUtils.kt | 58 ++-- .../utils/OkHttpNetworkFetcherWithCache.java | 41 --- .../com/nextcloud/talk/utils/text/Spans.java | 3 +- .../daveKoeller/AlphanumComparator.java | 2 +- .../parties}/daveKoeller/lgpl-2.1.txt | 0 .../third/parties/fresco/BetterImageSpan.kt | 138 ++++++++ app/src/main/res/drawable/shape_oval.xml | 5 + app/src/main/res/layout/account_item.xml | 8 +- app/src/main/res/layout/activity_main.xml | 3 +- app/src/main/res/layout/call_activity.xml | 113 ++++--- app/src/main/res/layout/call_item.xml | 4 +- .../res/layout/call_notification_activity.xml | 37 ++- .../layout/controller_conversation_info.xml | 6 +- .../main/res/layout/controller_profile.xml | 7 +- .../main/res/layout/controller_settings.xml | 7 +- .../main/res/layout/current_account_item.xml | 8 +- ...m_custom_incoming_link_preview_message.xml | 4 +- .../item_custom_incoming_location_message.xml | 4 +- .../item_custom_incoming_poll_message.xml | 4 +- .../item_custom_incoming_preview_message.xml | 22 +- .../item_custom_incoming_text_message.xml | 4 +- .../item_custom_incoming_voice_message.xml | 4 +- .../item_custom_outcoming_preview_message.xml | 20 +- .../res/layout/poll_result_voter_item.xml | 5 +- app/src/main/res/layout/reaction_item.xml | 5 +- .../res/layout/reference_inside_message.xml | 45 ++- .../main/res/layout/rv_item_browser_file.xml | 5 +- app/src/main/res/layout/rv_item_contact.xml | 8 +- .../res/layout/rv_item_contact_shimmer.xml | 4 +- .../rv_item_conversation_info_participant.xml | 15 +- ...rv_item_conversation_with_last_message.xml | 7 +- app/src/main/res/layout/rv_item_load_more.xml | 3 +- .../res/layout/rv_item_search_message.xml | 7 +- app/src/main/res/layout/shared_item_grid.xml | 8 +- app/src/main/res/layout/shared_item_list.xml | 9 +- app/src/main/res/values/dimens.xml | 4 + app/src/main/res/values/strings.xml | 10 + 78 files changed, 1131 insertions(+), 1379 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt delete mode 100644 app/src/main/java/com/nextcloud/talk/utils/OkHttpNetworkFetcherWithCache.java rename app/src/main/java/{third_parties => third/parties}/daveKoeller/AlphanumComparator.java (99%) rename app/src/main/java/{third_parties => third/parties}/daveKoeller/lgpl-2.1.txt (100%) create mode 100644 app/src/main/java/third/parties/fresco/BetterImageSpan.kt create mode 100644 app/src/main/res/drawable/shape_oval.xml diff --git a/app/build.gradle b/app/build.gradle index 895f5f78c..05e1259db 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -245,15 +245,8 @@ dependencies { implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'com.github.wooplr:Spotlight:1.3' implementation 'com.google.code.findbugs:jsr305:3.0.2' - implementation('com.github.nextcloud-deps:ChatKit:0.3.0-1', { - exclude group: 'com.facebook.fresco' - }) + implementation 'com.github.nextcloud-deps:ChatKit:0.3.1' - implementation 'com.github.nextcloud-deps.fresco:fresco:v111' - implementation 'com.github.nextcloud-deps.fresco:animated-webp:v111' - implementation 'com.github.nextcloud-deps.fresco:webpsupport:v111' - implementation 'com.github.nextcloud-deps.fresco:animated-gif:v111' - implementation 'com.github.nextcloud-deps.fresco:imagepipeline-okhttp3:v111' implementation 'joda-time:joda-time:2.12.2' implementation "io.coil-kt:coil:${coilKtVersion}" implementation "io.coil-kt:coil-gif:${coilKtVersion}" diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java index cbd377bc9..95829d10b 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java +++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java @@ -149,7 +149,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.content.res.AppCompatResources; import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import autodagger.AutoInjector; @@ -540,20 +539,16 @@ public class CallActivity extends CallBaseActivity { private void updateAudioOutputButton(WebRtcAudioManager.AudioDevice activeAudioDevice) { switch (activeAudioDevice) { case BLUETOOTH: - binding.audioOutputButton.getHierarchy().setPlaceholderImage( - AppCompatResources.getDrawable(context, R.drawable.ic_baseline_bluetooth_audio_24)); + binding.audioOutputButton.setImageResource ( R.drawable.ic_baseline_bluetooth_audio_24); break; case SPEAKER_PHONE: - binding.audioOutputButton.getHierarchy().setPlaceholderImage( - AppCompatResources.getDrawable(context, R.drawable.ic_volume_up_white_24dp)); + binding.audioOutputButton.setImageResource(R.drawable.ic_volume_up_white_24dp); break; case EARPIECE: - binding.audioOutputButton.getHierarchy().setPlaceholderImage( - AppCompatResources.getDrawable(context, R.drawable.ic_baseline_phone_in_talk_24)); + binding.audioOutputButton.setImageResource(R.drawable.ic_baseline_phone_in_talk_24); break; case WIRED_HEADSET: - binding.audioOutputButton.getHierarchy().setPlaceholderImage( - AppCompatResources.getDrawable(context, R.drawable.ic_baseline_headset_mic_24)); + binding.audioOutputButton.setImageResource(R.drawable.ic_baseline_headset_mic_24); break; default: Log.e(TAG, "Icon for audio output not available"); @@ -795,7 +790,7 @@ public class CallActivity extends CallBaseActivity { onCameraClick(); } } else { - binding.cameraButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_off_white_24px); + binding.cameraButton.setImageResource(R.drawable.ic_videocam_off_white_24px); binding.cameraButton.setAlpha(0.7f); binding.switchSelfVideoButton.setVisibility(View.GONE); } @@ -806,7 +801,7 @@ public class CallActivity extends CallBaseActivity { onMicrophoneClick(); } } else { - binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); + binding.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px); } if (!isConnectionEstablished()) { @@ -917,7 +912,7 @@ public class CallActivity extends CallBaseActivity { if (!canPublishAudioStream) { microphoneOn = false; - binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); + binding.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px); toggleMedia(false, false); } @@ -961,12 +956,12 @@ public class CallActivity extends CallBaseActivity { microphoneOn = !microphoneOn; if (microphoneOn) { - binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_white_24px); + binding.microphoneButton.setImageResource(R.drawable.ic_mic_white_24px); updatePictureInPictureActions(R.drawable.ic_mic_white_24px, getResources().getString(R.string.nc_pip_microphone_mute), MICROPHONE_PIP_REQUEST_MUTE); } else { - binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); + binding.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px); updatePictureInPictureActions(R.drawable.ic_mic_off_white_24px, getResources().getString(R.string.nc_pip_microphone_unmute), MICROPHONE_PIP_REQUEST_UNMUTE); @@ -974,7 +969,7 @@ public class CallActivity extends CallBaseActivity { toggleMedia(microphoneOn, false); } else { - binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_white_24px); + binding.microphoneButton.setImageResource(R.drawable.ic_mic_white_24px); pulseAnimation.start(); toggleMedia(true, false); } @@ -997,7 +992,7 @@ public class CallActivity extends CallBaseActivity { if (!canPublishVideoStream) { videoOn = false; - binding.cameraButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_off_white_24px); + binding.cameraButton.setImageResource(R.drawable.ic_videocam_off_white_24px); binding.switchSelfVideoButton.setVisibility(View.GONE); return; } @@ -1006,12 +1001,12 @@ public class CallActivity extends CallBaseActivity { videoOn = !videoOn; if (videoOn) { - binding.cameraButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_white_24px); + binding.cameraButton.setImageResource(R.drawable.ic_videocam_white_24px); if (cameraEnumerator.getDeviceNames().length > 1) { binding.switchSelfVideoButton.setVisibility(View.VISIBLE); } } else { - binding.cameraButton.getHierarchy().setPlaceholderImage(R.drawable.ic_videocam_off_white_24px); + binding.cameraButton.setImageResource(R.drawable.ic_videocam_off_white_24px); binding.switchSelfVideoButton.setVisibility(View.GONE); } @@ -2675,7 +2670,7 @@ public class CallActivity extends CallBaseActivity { v.onTouchEvent(event); if (event.getAction() == MotionEvent.ACTION_UP && isPushToTalkActive) { isPushToTalkActive = false; - binding.microphoneButton.getHierarchy().setPlaceholderImage(R.drawable.ic_mic_off_white_24px); + binding.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px); pulseAnimation.stop(); toggleMedia(false, false); animateCallControls(false, 5000); diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.kt index f81389f67..6a6ed114e 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/CallNotificationActivity.kt @@ -28,8 +28,6 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.res.Configuration -import android.graphics.Bitmap -import android.graphics.drawable.BitmapDrawable import android.media.AudioAttributes import android.media.MediaPlayer import android.os.Build @@ -41,24 +39,18 @@ import android.view.View import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import autodagger.AutoInjector -import com.facebook.common.executors.UiThreadImmediateExecutorService -import com.facebook.common.references.CloseableReference -import com.facebook.datasource.DataSource -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber -import com.facebook.imagepipeline.image.CloseableImage import com.nextcloud.talk.R import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.CallNotificationActivityBinding +import com.nextcloud.talk.extensions.loadAvatar import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.models.json.participants.ParticipantsOverall import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DoNotDisturbUtils.shouldPlaySound import com.nextcloud.talk.utils.NotificationUtils import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri @@ -75,6 +67,7 @@ import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers +import kotlinx.android.synthetic.main.call_item.* import okhttp3.Cache import org.parceler.Parcels import java.io.IOException @@ -356,7 +349,7 @@ class CallNotificationActivity : CallBaseActivity() { private fun setUpAfterConversationIsKnown() { binding!!.conversationNameTextView.text = currentConversation!!.displayName if (currentConversation!!.type === Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) { - setAvatarForOneToOneCall() + avatarImageView.loadAvatar(userBeingCalled!!, currentConversation!!.name!!) } else { binding!!.avatarImageView.setImageResource(R.drawable.ic_circular_group) } @@ -364,34 +357,6 @@ class CallNotificationActivity : CallBaseActivity() { showAnswerControls() } - @Suppress("MagicNumber") - private fun setAvatarForOneToOneCall() { - val imageRequest = DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - userBeingCalled!!.baseUrl, - currentConversation!!.name, - true - ) - ) - val imagePipeline = Fresco.getImagePipeline() - val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null) - dataSource.subscribe( - object : BaseBitmapDataSubscriber() { - override fun onNewResultImpl(bitmap: Bitmap?) { - binding!!.avatarImageView.hierarchy.setImage( - BitmapDrawable(resources, bitmap), 100f, - true - ) - } - - override fun onFailureImpl(dataSource: DataSource>) { - Log.e(TAG, "failed to load avatar") - } - }, - UiThreadImmediateExecutorService.getInstance() - ) - } - private fun endMediaNotifications() { if (mediaPlayer != null) { if (mediaPlayer!!.isPlaying) { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/ParticipantsAdapter.java b/app/src/main/java/com/nextcloud/talk/adapters/ParticipantsAdapter.java index bd09717d6..b8b5cc60b 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/ParticipantsAdapter.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/ParticipantsAdapter.java @@ -12,12 +12,9 @@ import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.interfaces.DraweeController; -import com.facebook.drawee.view.SimpleDraweeView; import com.nextcloud.talk.R; import com.nextcloud.talk.activities.CallActivity; -import com.nextcloud.talk.utils.DisplayUtils; +import com.nextcloud.talk.extensions.ImageViewExtensionsKt; import org.webrtc.MediaStream; import org.webrtc.MediaStreamTrack; @@ -109,7 +106,7 @@ public class ParticipantsAdapter extends BaseAdapter { convertView.setLayoutParams(layoutParams); TextView nickTextView = convertView.findViewById(R.id.peer_nick_text_view); - SimpleDraweeView imageView = convertView.findViewById(R.id.avatarImageView); + ImageView imageView = convertView.findViewById(R.id.avatarImageView); MediaStream mediaStream = participantDisplayItem.getMediaStream(); if (hasVideoStream(participantDisplayItem, mediaStream)) { @@ -128,13 +125,7 @@ public class ParticipantsAdapter extends BaseAdapter { nickTextView.setVisibility(View.VISIBLE); nickTextView.setText(participantDisplayItem.getNick()); } - - imageView.setController(null); - DraweeController draweeController = Fresco.newDraweeControllerBuilder() - .setOldController(imageView.getController()) - .setImageRequest(DisplayUtils.getImageRequestForUrl(participantDisplayItem.getUrlForAvatar())) - .build(); - imageView.setController(draweeController); + ImageViewExtensionsKt.loadAvatarWithUrl(imageView,null, participantDisplayItem.getUrlForAvatar()); } ImageView audioOffView = convertView.findViewById(R.id.remote_audio_off); diff --git a/app/src/main/java/com/nextcloud/talk/adapters/ReactionsAdapter.kt b/app/src/main/java/com/nextcloud/talk/adapters/ReactionsAdapter.kt index 0a3ff60f7..551e15a26 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/ReactionsAdapter.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/ReactionsAdapter.kt @@ -34,7 +34,7 @@ class ReactionsAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReactionsViewHolder { val itemBinding = ReactionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return ReactionsViewHolder(itemBinding, user?.baseUrl) + return ReactionsViewHolder(itemBinding, user) } override fun onBindViewHolder(holder: ReactionsViewHolder, position: Int) { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/ReactionsViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/ReactionsViewHolder.kt index facc1cd49..2f9071ae0 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/ReactionsViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/ReactionsViewHolder.kt @@ -22,18 +22,17 @@ package com.nextcloud.talk.adapters import android.text.TextUtils import androidx.recyclerview.widget.RecyclerView -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.interfaces.DraweeController import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.ReactionItemBinding +import com.nextcloud.talk.extensions.loadAvatar +import com.nextcloud.talk.extensions.loadGuestAvatar import com.nextcloud.talk.models.json.reactions.ReactionVoter -import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils class ReactionsViewHolder( private val binding: ReactionItemBinding, - private val baseUrl: String? + private val user: User? ) : RecyclerView.ViewHolder(binding.root) { fun bind(reactionItem: ReactionItem, clickListener: ReactionItemClickListener) { @@ -41,7 +40,7 @@ class ReactionsViewHolder( binding.reaction.text = reactionItem.reaction binding.name.text = reactionItem.reactionVoter.actorDisplayName - if (baseUrl != null && baseUrl.isNotEmpty()) { + if (user != null && user.baseUrl?.isNotEmpty() == true) { loadAvatar(reactionItem) } } @@ -52,35 +51,13 @@ class ReactionsViewHolder( if (!TextUtils.isEmpty(reactionItem.reactionVoter.actorDisplayName)) { displayName = reactionItem.reactionVoter.actorDisplayName!! } - val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() - .setOldController(binding.avatar.controller) - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForGuestAvatar( - baseUrl, - displayName, - false - ) - ) - ) - .build() - binding.avatar.controller = draweeController + binding.avatar.loadGuestAvatar(user!!.baseUrl!!, displayName!!, false) } else if (reactionItem.reactionVoter.actorType == ReactionVoter.ReactionActorType.USERS) { - val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() - .setOldController(binding.avatar.controller) - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - baseUrl, - reactionItem.reactionVoter.actorId, - false - ) - ) - ) - .build() - binding.avatar.controller = draweeController + binding.avatar.loadAvatar( + user!!, + reactionItem.reactionVoter.actorId!!, + false + ) } } } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.java index ad4179e88..36f652da1 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.java @@ -27,15 +27,12 @@ import android.net.Uri; import android.text.TextUtils; 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.data.user.model.User; import com.nextcloud.talk.databinding.AccountItemBinding; +import com.nextcloud.talk.extensions.ImageViewExtensionsKt; import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.ui.theme.ViewThemeUtils; -import com.nextcloud.talk.utils.ApiUtils; -import com.nextcloud.talk.utils.DisplayUtils; import java.util.List; import java.util.regex.Pattern; @@ -108,7 +105,6 @@ public class AdvancedUserItem extends AbstractFlexibleItem= Build.VERSION_CODES.O) { - holder.binding.avatarDraweeView.getHierarchy().setPlaceholderImage( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar(holder.binding.avatarDraweeView, - roundPlaceholderDrawable))); + avatar = viewThemeUtils.talk.themePlaceholderAvatar( + holder.binding.avatarView, + roundPlaceholderDrawable + ); + } else { - holder.binding.avatarDraweeView.setImageResource(fallbackImageResource); + avatar = fallbackImageResource; } + + ImageViewExtensionsKt.loadAvatar(holder.binding.avatarView, avatar); } @Override 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 c129852dc..4b16ed6af 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 @@ -29,27 +29,26 @@ 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.extensions.loadAvatar +import com.nextcloud.talk.extensions.loadGroupCallAvatar +import com.nextcloud.talk.extensions.loadPublicCallAvatar +import com.nextcloud.talk.extensions.loadSystemAvatar 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.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 @@ -113,7 +112,6 @@ class ConversationItem( payloads: List ) { val appContext = sharedApplication!!.applicationContext - holder.binding.dialogAvatar.controller = null holder.binding.dialogName.setTextColor( ResourcesCompat.getColor( context.resources, @@ -130,40 +128,7 @@ class ConversationItem( 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) - } + showUnreadMessages(holder) } else { holder.binding.dialogName.setTypeface(null, Typeface.NORMAL) holder.binding.dialogDate.setTypeface(null, Typeface.NORMAL) @@ -175,13 +140,13 @@ class ConversationItem( } else { holder.binding.favoriteConversationImageView.visibility = View.GONE } - if (model != null && ConversationType.ROOM_SYSTEM !== model.type) { + if (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( model.status, - model.status, + model.statusIcon, size, context.resources.getColor(R.color.bg_default), appContext @@ -190,10 +155,77 @@ class ConversationItem( } else { holder.binding.userStatusImage.visibility = View.GONE } + setLastMessage(holder, appContext) + showAvatar(holder) + } + + private fun showAvatar(holder: ConversationItemViewHolder) { + holder.binding.dialogAvatar.visibility = View.VISIBLE + var shouldLoadAvatar = shouldLoadAvatar(holder) + if (ConversationType.ROOM_SYSTEM == model.type) { + holder.binding.dialogAvatar.loadSystemAvatar() + 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 -> + holder.binding.dialogAvatar.loadGroupCallAvatar(viewThemeUtils) + ConversationType.ROOM_PUBLIC_CALL -> + holder.binding.dialogAvatar.loadPublicCallAvatar(viewThemeUtils) + else -> holder.binding.dialogAvatar.visibility = View.GONE + } + } + } + + private fun shouldLoadAvatar( + holder: ConversationItemViewHolder + ): Boolean { + var objectType: String? + var returnValue = true + if (!TextUtils.isEmpty(model.objectType.also { objectType = it })) { + when (objectType) { + "share:password" -> { + holder.binding.dialogAvatar.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.ic_circular_lock + ) + ) + returnValue = false + } + "file" -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + holder.binding.dialogAvatar.loadAvatar( + viewThemeUtils.talk.themePlaceholderAvatar( + holder.binding.dialogAvatar, + R.drawable.ic_avatar_document + ) + ) + } else { + holder.binding.dialogAvatar.loadAvatar( + R.drawable.ic_circular_document + ) + } + returnValue = false + } + } + } + return returnValue + } + + private fun setLastMessage( + holder: ConversationItemViewHolder, + appContext: Context + ) { if (model.lastMessage != null) { holder.binding.dialogDate.visibility = View.VISIBLE holder.binding.dialogDate.text = DateUtils.getRelativeTimeSpanString( - model.lastActivity * 1000L, + model.lastActivity * MILLIES, System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE @@ -204,30 +236,13 @@ class ConversationItem( 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 - ) - } + + val text = if (model.lastMessage!!.getCalculateMessageType() === ChatMessage.MessageType + .REGULAR_TEXT_MESSAGE + ) { + calculateRegularLastMessageText(appContext) } else { - text = model.lastMessage!!.lastMessageDisplayText + model.lastMessage!!.lastMessageDisplayText } holder.binding.dialogLastMessage.text = text } @@ -235,105 +250,68 @@ class ConversationItem( 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 - ) + } + + private fun calculateRegularLastMessageText(appContext: Context): String { + return if (model.lastMessage!!.actorId == user.userId) { + 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 { + "" + } + String.format( + appContext.getString(R.string.nc_formatted_message), + authorDisplayName, + model.lastMessage!!.lastMessageDisplayText + ) + } + } + + private fun showUnreadMessages(holder: ConversationItemViewHolder) { + 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 < UNREAD_MESSAGES_TRESHOLD) { + 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, + UNREAD_BUBBLE_STROKE_WIDTH ) } - "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 + viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble) } + } else { + holder.binding.dialogUnreadBubble.chipBackgroundColor = lightBubbleFillColor + holder.binding.dialogUnreadBubble.setTextColor(lightBubbleTextColor) } } @@ -363,6 +341,9 @@ class ConversationItem( companion object { const val VIEW_TYPE = R.layout.rv_item_conversation_with_last_message + private const val MILLIES = 1000L private const val STATUS_SIZE_IN_DP = 9f + private const val UNREAD_BUBBLE_STROKE_WIDTH = 6.0f + private const val UNREAD_MESSAGES_TRESHOLD = 1000 } } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/MentionAutocompleteItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/MentionAutocompleteItem.java index 1773e1c60..13c655ff7 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/MentionAutocompleteItem.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/MentionAutocompleteItem.java @@ -29,15 +29,13 @@ import android.content.Context; import android.os.Build; 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.data.user.model.User; +import com.nextcloud.talk.extensions.ImageViewExtensionsKt; import com.nextcloud.talk.models.json.mention.Mention; import com.nextcloud.talk.models.json.status.StatusType; 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 java.util.List; @@ -151,34 +149,22 @@ public class MentionAutocompleteItem extends AbstractFlexibleItem= Build.VERSION_CODES.O) { - holder.binding.avatarDraweeView.getHierarchy().setPlaceholderImage( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar(holder.binding.avatarDraweeView, - R.drawable.ic_avatar_group))); + ImageViewExtensionsKt.loadAvatar( + holder.binding.avatarView, + viewThemeUtils.talk.themePlaceholderAvatar( + holder.binding.avatarView, + R.drawable.ic_avatar_group + ) + ); } else { - holder.binding.avatarDraweeView.setImageResource(R.drawable.ic_circular_group); + ImageViewExtensionsKt.loadAvatar(holder.binding.avatarView, R.drawable.ic_circular_group); } } else { String avatarId = objectId; - String avatarUrl = ApiUtils.getUrlForAvatar(currentUser.getBaseUrl(), - avatarId, true); - if (SOURCE_GUESTS.equals(source)) { avatarId = displayName; - avatarUrl = ApiUtils.getUrlForGuestAvatar( - currentUser.getBaseUrl(), - avatarId, - false); } - - holder.binding.avatarDraweeView.setController(null); - - DraweeController draweeController = Fresco.newDraweeControllerBuilder() - .setOldController(holder.binding.avatarDraweeView.getController()) - .setAutoPlayAnimations(true) - .setImageRequest(DisplayUtils.getImageRequestForUrl(avatarUrl)) - .build(); - holder.binding.avatarDraweeView.setController(draweeController); + ImageViewExtensionsKt.loadAvatar(holder.binding.avatarView, currentUser, avatarId, true); } drawStatus(holder); 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..fe2424125 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) + messageEntry.thumbnailURL?.let { holder.binding.thumbnail.loadThumbnail(it, 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/adapters/items/ParticipantItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/ParticipantItem.java index ea676457b..6bc572d68 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/ParticipantItem.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/ParticipantItem.java @@ -27,23 +27,20 @@ package com.nextcloud.talk.adapters.items; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; -import android.os.Build; import android.text.TextUtils; 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.RvItemConversationInfoParticipantBinding; +import com.nextcloud.talk.extensions.ImageViewExtensionsKt; import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter; import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.models.json.participants.Participant.InCallFlags; import com.nextcloud.talk.models.json.status.StatusType; 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 java.util.List; @@ -111,24 +108,22 @@ public class ParticipantItem extends AbstractFlexibleItem= Build.VERSION_CODES.O) { - holder.binding.avatarDraweeView.getHierarchy().setPlaceholderImage( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar(holder.binding.avatarDraweeView, - R.drawable.ic_avatar_group))); - } else { - holder.binding.avatarDraweeView.setImageResource(R.drawable.ic_circular_group); - } + ImageViewExtensionsKt.loadGroupCallAvatar(holder.binding.avatarView, viewThemeUtils); } else if (participant.getCalculatedActorType() == Participant.ActorType.EMAILS) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - holder.binding.avatarDraweeView.getHierarchy().setPlaceholderImage( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar(holder.binding.avatarDraweeView, - R.drawable.ic_avatar_mail))); - } else { - holder.binding.avatarDraweeView.setImageResource(R.drawable.ic_circular_mail); - } + ImageViewExtensionsKt.loadMailAvatar(holder.binding.avatarView, viewThemeUtils); } else if (participant.getCalculatedActorType() == Participant.ActorType.GUESTS || participant.getType() == Participant.ParticipantType.GUEST || participant.getType() == Participant.ParticipantType.GUEST_MODERATOR) { @@ -180,25 +161,11 @@ public class ParticipantItem extends AbstractFlexibleItem= 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) - binding.messageUserAvatar.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable)) - } else { - binding.messageUserAvatar.setImageResource(R.mipmap.ic_launcher) - } + binding.messageUserAvatar.loadChangelogBotAvatar() } else if (message.actorType == "bots") { - val drawable = TextDrawable.builder() - .beginConfig() - .bold() - .endConfig() - .buildRound( - ">", - ResourcesCompat.getColor(context.resources, R.color.black, null) - ) - binding.messageUserAvatar.visibility = View.VISIBLE - binding.messageUserAvatar.setImageDrawable(drawable) + binding.messageUserAvatar.loadBotsAvatar() } } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt index 8da197c1d..65a4bc8f6 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt @@ -29,8 +29,6 @@ package com.nextcloud.talk.adapters.messages import android.annotation.SuppressLint import android.content.Context import android.content.Intent -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable import android.net.Uri import android.text.TextUtils import android.util.Log @@ -40,18 +38,17 @@ import android.view.View import android.webkit.WebView import android.webkit.WebViewClient import android.widget.Toast -import androidx.appcompat.content.res.AppCompatResources import autodagger.AutoInjector import coil.load -import com.amulyakhare.textdrawable.TextDrawable import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.databinding.ItemCustomIncomingLocationMessageBinding +import com.nextcloud.talk.extensions.loadBotsAvatar +import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.UriUtils import com.nextcloud.talk.utils.preferences.AppPreferences import com.stfalcon.chatkit.messages.MessageHolders @@ -136,22 +133,9 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : Mess if (message.actorType == "guests") { // do nothing, avatar is set } else if (message.actorType == "bots" && message.actorId == "changelog") { - val layers = arrayOfNulls(2) - layers[0] = AppCompatResources.getDrawable(context!!, R.drawable.ic_launcher_background) - layers[1] = AppCompatResources.getDrawable(context!!, R.drawable.ic_launcher_foreground) - val layerDrawable = LayerDrawable(layers) - binding.messageUserAvatar.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable)) + binding.messageUserAvatar.loadChangelogBotAvatar() } else if (message.actorType == "bots") { - val drawable = TextDrawable.builder() - .beginConfig() - .bold() - .endConfig() - .buildRound( - ">", - context!!.resources.getColor(R.color.black) - ) - binding.messageUserAvatar.visibility = View.VISIBLE - binding.messageUserAvatar.setImageDrawable(drawable) + binding.messageUserAvatar.loadBotsAvatar() } } else { if (message.isOneToOneConversation) { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt index 5466c5a8e..c1b9a00ff 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt @@ -22,27 +22,23 @@ package com.nextcloud.talk.adapters.messages import android.annotation.SuppressLint import android.content.Context -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable -import android.os.Build import android.text.TextUtils import android.view.View import androidx.core.content.ContextCompat -import androidx.core.content.res.ResourcesCompat import autodagger.AutoInjector import coil.load -import com.amulyakhare.textdrawable.TextDrawable import com.nextcloud.talk.R import com.nextcloud.talk.activities.MainActivity import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.databinding.ItemCustomIncomingPollMessageBinding +import com.nextcloud.talk.extensions.loadBotsAvatar +import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.polls.ui.PollMainDialogFragment import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.preferences.AppPreferences import com.stfalcon.chatkit.messages.MessageHolders import javax.inject.Inject @@ -170,26 +166,9 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : MessageH if (message.actorType == "guests") { // do nothing, avatar is set } else if (message.actorType == "bots" && message.actorId == "changelog") { - 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) - binding.messageUserAvatar.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable)) - } else { - binding.messageUserAvatar.setImageResource(R.mipmap.ic_launcher) - } + binding.messageUserAvatar.loadChangelogBotAvatar() } else if (message.actorType == "bots") { - val drawable = TextDrawable.builder() - .beginConfig() - .bold() - .endConfig() - .buildRound( - ">", - ResourcesCompat.getColor(context.resources, R.color.black, null) - ) - binding.messageUserAvatar.visibility = View.VISIBLE - binding.messageUserAvatar.setImageDrawable(drawable) + binding.messageUserAvatar.loadBotsAvatar() } } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java index a60f089f1..78fbac71e 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java @@ -3,7 +3,7 @@ * * @author Andy Scherzinger * @author Tim Krüger - * Copyright (C) 2021 Tim Krüger + * Copyright (C) 2021-2022 Tim Krüger * Copyright (C) 2021 Andy Scherzinger * * This program is free software: you can redistribute it and/or modify @@ -23,9 +23,9 @@ package com.nextcloud.talk.adapters.messages; import android.view.View; +import android.widget.ImageView; import android.widget.ProgressBar; -import com.facebook.drawee.view.SimpleDraweeView; import com.google.android.material.card.MaterialCardView; import com.nextcloud.talk.R; import com.nextcloud.talk.databinding.ItemCustomIncomingPreviewMessageBinding; @@ -74,7 +74,7 @@ public class IncomingPreviewMessageViewHolder extends PreviewMessageViewHolder { } @Override - public SimpleDraweeView getPreviewContactPhoto() { + public ImageView getPreviewContactPhoto() { return binding.contactPhoto; } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt index 81b26b99a..800375c6a 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt @@ -26,24 +26,21 @@ package com.nextcloud.talk.adapters.messages import android.content.Context import android.content.Intent -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable import android.net.Uri -import android.os.Build import android.text.Spannable import android.text.SpannableString import android.text.TextUtils import android.util.TypedValue import android.view.View import androidx.core.content.ContextCompat -import androidx.core.content.res.ResourcesCompat import autodagger.AutoInjector import coil.load -import com.amulyakhare.textdrawable.TextDrawable import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding +import com.nextcloud.talk.extensions.loadBotsAvatar +import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback import com.nextcloud.talk.ui.theme.ViewThemeUtils @@ -179,28 +176,9 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : MessageHolde if (message.actorType == "guests") { // do nothing, avatar is set } else if (message.actorType == "bots" && message.actorId == "changelog") { - if (context != null) { - 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) - binding.messageUserAvatar.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable)) - } else { - binding.messageUserAvatar.setImageResource(R.mipmap.ic_launcher) - } - } + binding.messageUserAvatar.loadChangelogBotAvatar() } else if (message.actorType == "bots") { - val drawable = TextDrawable.builder() - .beginConfig() - .bold() - .endConfig() - .buildRound( - ">", - ResourcesCompat.getColor(context!!.resources, R.color.black, null) - ) - binding.messageUserAvatar.visibility = View.VISIBLE - binding.messageUserAvatar.setImageDrawable(drawable) + binding.messageUserAvatar.loadBotsAvatar() } } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt index 2cc1419aa..ab67e99d6 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt @@ -28,9 +28,6 @@ package com.nextcloud.talk.adapters.messages import android.annotation.SuppressLint import android.content.Context -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable -import android.os.Build import android.text.TextUtils import android.util.Log import android.view.View @@ -46,10 +43,10 @@ import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding +import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.preferences.AppPreferences import com.stfalcon.chatkit.messages.MessageHolders import java.util.concurrent.ExecutionException @@ -245,15 +242,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message if (message.actorType == "guests") { // do nothing, avatar is set } else if (message.actorType == "bots" && message.actorId == "changelog") { - 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) - binding.messageUserAvatar.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable)) - } else { - binding.messageUserAvatar.setImageResource(R.mipmap.ic_launcher) - } + binding.messageUserAvatar.loadChangelogBotAvatar() } else if (message.actorType == "bots") { val drawable = TextDrawable.builder() .beginConfig() diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt index 5a80b08ef..f3da8ee2d 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt @@ -25,14 +25,12 @@ import android.content.Intent import android.net.Uri import android.util.Log import android.view.View -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.interfaces.DraweeController +import coil.load import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.databinding.ReferenceInsideMessageBinding import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.opengraph.OpenGraphOverall import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable @@ -92,12 +90,7 @@ class LinkPreview { val referenceThumbUrl = reference.openGraphObject?.thumb if (!referenceThumbUrl.isNullOrEmpty()) { binding.referenceThumbImage.visibility = View.VISIBLE - val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() - .setAutoPlayAnimations(true) - .setImageRequest(DisplayUtils.getImageRequestForUrl(referenceThumbUrl)) - .build() - binding.referenceThumbImage.controller = - draweeController + binding.referenceThumbImage.load(referenceThumbUrl) } else { binding.referenceThumbImage.visibility = View.GONE } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java index f6860c9da..82778e9dd 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java @@ -23,9 +23,9 @@ package com.nextcloud.talk.adapters.messages; import android.view.View; +import android.widget.ImageView; import android.widget.ProgressBar; -import com.facebook.drawee.view.SimpleDraweeView; import com.google.android.material.card.MaterialCardView; import com.nextcloud.talk.R; import com.nextcloud.talk.databinding.ItemCustomOutcomingPreviewMessageBinding; @@ -75,7 +75,7 @@ public class OutcomingPreviewMessageViewHolder extends PreviewMessageViewHolder } @Override - public SimpleDraweeView getPreviewContactPhoto() { + public ImageView getPreviewContactPhoto() { return binding.contactPhoto; } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageViewHolder.kt index 7891e601b..cfd4be17b 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageViewHolder.kt @@ -5,7 +5,7 @@ * @author Marcel Hibbe * @author Andy Scherzinger * @author Tim Krüger - * Copyright (C) 2021 Tim Krüger + * Copyright (C) 2021-2022 Tim Krüger * Copyright (C) 2021 Andy Scherzinger * Copyright (C) 2021 Marcel Hibbe * Copyright (C) 2017-2018 Mario Danic @@ -30,7 +30,6 @@ import android.content.Context import android.content.Intent import android.graphics.PorterDuff import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable import android.net.Uri import android.os.Handler import android.util.Base64 @@ -38,13 +37,13 @@ import android.util.Log import android.view.Gravity import android.view.MenuItem import android.view.View +import android.widget.ImageView import android.widget.PopupMenu import android.widget.ProgressBar import androidx.appcompat.view.ContextThemeWrapper import androidx.core.content.ContextCompat import androidx.emoji.widget.EmojiTextView import autodagger.AutoInjector -import com.facebook.drawee.view.SimpleDraweeView import com.google.android.material.card.MaterialCardView import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication @@ -53,6 +52,7 @@ import com.nextcloud.talk.components.filebrowser.models.BrowserFile import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding +import com.nextcloud.talk.extensions.loadChangelogBotAvatar import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback import com.nextcloud.talk.ui.theme.ViewThemeUtils @@ -92,6 +92,8 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : lateinit var commonMessageInterface: CommonMessageInterface var previewMessageInterface: PreviewMessageInterface? = null + private var placeholder: Drawable? = null + init { sharedApplication!!.componentApplication.inject(this) } @@ -118,13 +120,7 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : } } if (ACTOR_TYPE_BOTS == message.actorType && ACTOR_ID_CHANGELOG == message.actorId) { - if (context != null) { - 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) - userAvatar.hierarchy.setPlaceholderImage(DisplayUtils.getRoundedDrawable(layerDrawable)) - } + userAvatar.loadChangelogBotAvatar() } } } @@ -150,11 +146,10 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : } if (message.selectedIndividualHashMap!!.containsKey(KEY_CONTACT_PHOTO)) { image = previewContactPhoto - val drawable = getDrawableFromContactDetails( + placeholder = getDrawableFromContactDetails( context, message.selectedIndividualHashMap!![KEY_CONTACT_PHOTO] ) - image.hierarchy.setPlaceholderImage(drawable) } else if (message.selectedIndividualHashMap!!.containsKey(KEY_MIMETYPE)) { val mimetype = message.selectedIndividualHashMap!![KEY_MIMETYPE] val drawableResourceId = getDrawableResourceIdForMimeType(mimetype) @@ -170,7 +165,7 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : PorterDuff.Mode.SRC_ATOP ) } - image.hierarchy.setPlaceholderImage(drawable) + placeholder = drawable } else { fetchFileInformation( "/" + message.selectedIndividualHashMap!![KEY_PATH], @@ -208,13 +203,13 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : DisplayUtils.setClickableString("Tenor", "https://tenor.com", messageText) } else { if (message.messageType == ChatMessage.MessageType.SINGLE_LINK_IMAGE_MESSAGE.name) { - (clickView as SimpleDraweeView?)?.setOnClickListener { + clickView!!.setOnClickListener { val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(message.imageUrl)) browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context!!.startActivity(browserIntent) } } else { - (clickView as SimpleDraweeView?)?.setOnClickListener(null) + clickView!!.setOnClickListener(null) } messageText.text = "" } @@ -238,6 +233,10 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : commonMessageInterface.onClickReaction(chatMessage, emoji) } + override fun getPayloadForImageLoader(message: ChatMessage?): Any? { + return placeholder + } + private fun getDrawableFromContactDetails(context: Context?, base64: String?): Drawable? { var drawable: Drawable? = null if (base64 != "") { @@ -300,8 +299,7 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : if (browserFileList.isNotEmpty()) { Handler(context!!.mainLooper).post { val resourceId = getDrawableResourceIdForMimeType(browserFileList[0].mimeType) - val drawable = ContextCompat.getDrawable(context!!, resourceId) - image.hierarchy.setPlaceholderImage(drawable) + placeholder = ContextCompat.getDrawable(context!!, resourceId) } } } @@ -324,7 +322,7 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) : abstract val messageText: EmojiTextView abstract val previewContainer: View abstract val previewContactContainer: MaterialCardView - abstract val previewContactPhoto: SimpleDraweeView + abstract val previewContactPhoto: ImageView abstract val previewContactName: EmojiTextView abstract val previewContactProgressBar: ProgressBar? diff --git a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt index 5407a12f4..cbad9f041 100644 --- a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt +++ b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt @@ -4,6 +4,8 @@ * @author Marcel Hibbe * @author Andy Scherzinger * @author Mario Danic + * @author Tim Krüger + * Copyright (C) 2022 Tim Krüger * Copyright (C) 2022 Marcel Hibbe * Copyright (C) 2022 Andy Scherzinger * Copyright (C) 2017 Mario Danic @@ -46,9 +48,7 @@ import coil.decode.GifDecoder import coil.decode.ImageDecoderDecoder import coil.decode.SvgDecoder import coil.memory.MemoryCache -import com.facebook.cache.disk.DiskCacheConfig -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.imagepipeline.core.ImagePipelineConfig +import coil.util.DebugLogger import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.components.filebrowser.webdav.DavUtils import com.nextcloud.talk.dagger.modules.BusModule @@ -66,7 +66,6 @@ import com.nextcloud.talk.utils.ClosedInterfaceImpl import com.nextcloud.talk.utils.DeviceUtils import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.NotificationUtils -import com.nextcloud.talk.utils.OkHttpNetworkFetcherWithCache import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageModule import com.nextcloud.talk.utils.database.user.UserModule import com.nextcloud.talk.utils.preferences.AppPreferences @@ -174,18 +173,6 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver { setAppTheme(appPreferences.theme) super.onCreate() - val imagePipelineConfig = ImagePipelineConfig.newBuilder(this) - .setNetworkFetcher(OkHttpNetworkFetcherWithCache(okHttpClient)) - .setMainDiskCacheConfig( - DiskCacheConfig.newBuilder(this) - .setMaxCacheSize(0) - .setMaxCacheSizeOnLowDiskSpace(0) - .setMaxCacheSizeOnVeryLowDiskSpace(0) - .build() - ) - .build() - - Fresco.initialize(this, imagePipelineConfig) Security.insertProviderAt(Conscrypt.newProvider(), 1) ClosedInterfaceImpl().providerInstallerInstallIfNeededAsync() @@ -240,7 +227,7 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver { } private fun buildDefaultImageLoader(): ImageLoader { - return ImageLoader.Builder(applicationContext) + val imageLoaderBuilder = ImageLoader.Builder(applicationContext) .memoryCache { // Use 50% of the application's available memory. MemoryCache.Builder(applicationContext).maxSizePercent(FIFTY_PERCENT).build() @@ -254,8 +241,12 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver { } add(SvgDecoder.Factory()) } - .okHttpClient(okHttpClient) - .build() + + if (BuildConfig.DEBUG) { + imageLoaderBuilder.logger(DebugLogger()) + } + + return imageLoaderBuilder.build() } companion object { diff --git a/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java b/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java index eeeaa45e0..07e733e26 100644 --- a/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java +++ b/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java @@ -27,7 +27,7 @@ import android.text.Editable; import android.text.Spanned; import android.widget.EditText; -import com.facebook.widget.text.span.BetterImageSpan; +import third.parties.fresco.BetterImageSpan; import com.nextcloud.talk.R; import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.models.json.mention.Mention; diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index a94d7ca03..1f0b04a1a 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -5,7 +5,7 @@ * @author Marcel Hibbe * @author Andy Scherzinger * @author Tim Krüger - * Copyright (C) 2021 Tim Krüger + * Copyright (C) 2021-2022 Tim Krüger * Copyright (C) 2021 Andy Scherzinger * Copyright (C) 2021-2022 Marcel Hibbe * Copyright (C) 2017-2019 Mario Danic @@ -37,8 +37,9 @@ import android.content.pm.PackageManager import android.content.res.AssetFileDescriptor import android.content.res.Resources import android.database.Cursor -import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable import android.media.MediaPlayer import android.media.MediaRecorder import android.net.Uri @@ -78,7 +79,7 @@ import androidx.appcompat.view.ContextThemeWrapper import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.core.content.PermissionChecker -import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import androidx.core.graphics.drawable.toBitmap import androidx.core.widget.doAfterTextChanged import androidx.emoji.text.EmojiCompat import androidx.emoji.widget.EmojiTextView @@ -90,15 +91,13 @@ import androidx.work.OneTimeWorkRequest import androidx.work.WorkInfo import androidx.work.WorkManager import autodagger.AutoInjector +import coil.imageLoader import coil.load +import coil.request.ImageRequest +import coil.target.Target +import coil.transform.CircleCropTransformation import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler -import com.facebook.common.executors.UiThreadImmediateExecutorService -import com.facebook.common.references.CloseableReference -import com.facebook.datasource.DataSource -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber -import com.facebook.imagepipeline.image.CloseableImage import com.google.android.flexbox.FlexboxLayout import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.nextcloud.talk.BuildConfig @@ -134,6 +133,7 @@ import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.ControllerChatBinding import com.nextcloud.talk.events.UserMentionClickEvent import com.nextcloud.talk.events.WebSocketCommunicationEvent +import com.nextcloud.talk.extensions.loadAvatarOrImagePreview import com.nextcloud.talk.jobs.DownloadFileToCacheWorker import com.nextcloud.talk.jobs.ShareOperationWorker import com.nextcloud.talk.jobs.UploadAndShareFilesWorker @@ -165,7 +165,6 @@ import com.nextcloud.talk.utils.ConductorRemapping import com.nextcloud.talk.utils.ConductorRemapping.remapChatController import com.nextcloud.talk.utils.ContactUtils import com.nextcloud.talk.utils.DateUtils -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.FileUtils import com.nextcloud.talk.utils.ImageEmojiEditText import com.nextcloud.talk.utils.MagicCharPolicy @@ -462,42 +461,48 @@ class ChatController(args: Bundle) : private fun loadAvatarForStatusBar() { if (isOneToOneConversation() && activity != null) { - val imageRequest = DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - conversationUser?.baseUrl, - currentConversation?.name, - true - ), - conversationUser!! + + val url = ApiUtils.getUrlForAvatar( + conversationUser!!.baseUrl, + currentConversation!!.name, + true ) + val target = object : Target { - val imagePipeline = Fresco.getImagePipeline() - val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null) + private fun setIcon(drawable: Drawable?) { - dataSource.subscribe( - object : BaseBitmapDataSubscriber() { - override fun onNewResultImpl(bitmap: Bitmap?) { - if (actionBar != null && bitmap != null && resources != null) { - val avatarSize = (actionBar?.height!! / TOOLBAR_AVATAR_RATIO).roundToInt() - if (avatarSize > 0) { - val bitmapResized = Bitmap.createScaledBitmap(bitmap, avatarSize, avatarSize, false) + actionBar?.let { + val avatarSize = (it.height / TOOLBAR_AVATAR_RATIO).roundToInt() - val roundedBitmapDrawable = - RoundedBitmapDrawableFactory.create(resources!!, bitmapResized) - roundedBitmapDrawable.isCircular = true - roundedBitmapDrawable.setAntiAlias(true) - actionBar?.setIcon(roundedBitmapDrawable) - } else { - Log.d(TAG, "loadAvatarForStatusBar avatarSize <= 0") - } + if (drawable != null && avatarSize > 0) { + val bitmap = drawable.toBitmap(avatarSize, avatarSize) + it.setIcon(BitmapDrawable(resources, bitmap)) + } else { + Log.d(TAG, "loadAvatarForStatusBar avatarSize <= 0") } } + } - override fun onFailureImpl(dataSource: DataSource>) { - // unused atm - } - }, - UiThreadImmediateExecutorService.getInstance() + override fun onStart(placeholder: Drawable?) { + this.setIcon(placeholder) + } + + override fun onSuccess(result: Drawable) { + this.setIcon(result) + } + } + + val credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token) + + context.imageLoader.enqueue( + ImageRequest.Builder(context) + .data(url) + .addHeader("Authorization", credentials) + .placeholder(R.drawable.ic_user) + .transformations(CircleCropTransformation()) + .crossfade(true) + .target(target) + .build() ) } } @@ -617,14 +622,8 @@ class ChatController(args: Bundle) : adapter = TalkMessagesListAdapter( senderId, messageHolders, - ImageLoader { imageView, url, payload -> - val draweeController = Fresco.newDraweeControllerBuilder() - .setImageRequest(DisplayUtils.getImageRequestForUrl(url, conversationUser)) - .setControllerListener(DisplayUtils.getImageControllerListener(imageView)) - .setOldController(imageView.controller) - .setAutoPlayAnimations(true) - .build() - imageView.controller = draweeController + ImageLoader { imageView, url, _ -> + imageView.loadAvatarOrImagePreview(url!!, conversationUser, placeholder = payload as? Drawable) }, this ) diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt index 84930b8ee..a44f16f4b 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt @@ -28,9 +28,6 @@ package com.nextcloud.talk.controllers import android.annotation.SuppressLint import android.content.Intent -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable -import android.os.Build import android.os.Bundle import android.os.Parcelable import android.text.TextUtils @@ -42,7 +39,6 @@ import android.view.View.VISIBLE import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.SwitchCompat -import androidx.core.content.ContextCompat import androidx.work.Data import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager @@ -53,7 +49,6 @@ import com.afollestad.materialdialogs.bottomsheets.BottomSheet import com.afollestad.materialdialogs.datetime.dateTimePicker import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler -import com.facebook.drawee.backends.pipeline.Fresco import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.nextcloud.talk.R import com.nextcloud.talk.adapters.items.ParticipantItem @@ -67,6 +62,10 @@ import com.nextcloud.talk.conversation.info.GuestAccessHelper import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.ControllerConversationInfoBinding import com.nextcloud.talk.events.EventStatus +import com.nextcloud.talk.extensions.loadAvatar +import com.nextcloud.talk.extensions.loadSystemAvatar +import com.nextcloud.talk.extensions.loadGroupCallAvatar +import com.nextcloud.talk.extensions.loadPublicCallAvatar import com.nextcloud.talk.jobs.DeleteConversationWorker import com.nextcloud.talk.jobs.LeaveConversationWorker import com.nextcloud.talk.models.json.conversations.Conversation @@ -83,7 +82,6 @@ import com.nextcloud.talk.shareditems.activities.SharedItemsActivity import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DateConstants import com.nextcloud.talk.utils.DateUtils -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule @@ -778,54 +776,16 @@ class ConversationInfoController(args: Bundle) : private fun loadConversationAvatar() { when (conversation!!.type) { Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(conversation!!.name)) { - val draweeController = Fresco.newDraweeControllerBuilder() - .setOldController(binding.avatarImage.controller) - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - conversationUser!!.baseUrl, - conversation!!.name, - true - ), - conversationUser - ) - ) - .build() - binding.avatarImage.controller = draweeController + conversation!!.name?.let { binding.avatarImage.loadAvatar(conversationUser!!, it) } } Conversation.ConversationType.ROOM_GROUP_CALL -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - binding.avatarImage.hierarchy.setPlaceholderImage( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar(binding.avatarImage, R.drawable.ic_avatar_group) - ) - ) - } else { - binding.avatarImage.hierarchy.setPlaceholderImage( - R.drawable.ic_circular_group - ) - } + binding.avatarImage.loadGroupCallAvatar(viewThemeUtils) } Conversation.ConversationType.ROOM_PUBLIC_CALL -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - binding.avatarImage.hierarchy.setPlaceholderImage( - DisplayUtils.getRoundedDrawable( - viewThemeUtils.talk.themePlaceholderAvatar(binding.avatarImage, R.drawable.ic_avatar_link) - ) - ) - } else { - binding.avatarImage.hierarchy.setPlaceholderImage( - R.drawable.ic_circular_link - ) - } + binding.avatarImage.loadPublicCallAvatar(viewThemeUtils) } Conversation.ConversationType.ROOM_SYSTEM -> { - 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) - binding.avatarImage.hierarchy.setPlaceholderImage(DisplayUtils.getRoundedDrawable(layerDrawable)) + binding.avatarImage.loadSystemAvatar() } else -> { 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 8df593a46..4d6fafc18 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt @@ -31,7 +31,7 @@ import android.app.SearchManager import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.graphics.Bitmap +import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build import android.os.Bundle @@ -49,8 +49,6 @@ import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.SearchView -import androidx.core.content.res.ResourcesCompat -import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.view.MenuItemCompat import androidx.fragment.app.DialogFragment import androidx.recyclerview.widget.RecyclerView @@ -58,15 +56,13 @@ import androidx.work.Data import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import autodagger.AutoInjector +import coil.imageLoader +import coil.request.ImageRequest +import coil.target.Target +import coil.transform.CircleCropTransformation import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler -import com.facebook.common.executors.UiThreadImmediateExecutorService -import com.facebook.common.references.CloseableReference -import com.facebook.datasource.DataSource -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber -import com.facebook.imagepipeline.image.CloseableImage import com.google.android.material.button.MaterialButton import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.nextcloud.talk.R @@ -104,7 +100,6 @@ import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ClosedInterfaceImpl import com.nextcloud.talk.utils.ConductorRemapping.remapChatController -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.FileUtils import com.nextcloud.talk.utils.Mimetype import com.nextcloud.talk.utils.ParticipantPermissions @@ -211,78 +206,56 @@ class ConversationsListController(bundle: Bundle) : prepareViews() } - private fun loadUserAvatar(button: MaterialButton) { - if (activity != null) { - val imageRequest = DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - currentUser!!.baseUrl, - currentUser!!.userId, - true - ), - currentUser - ) - val imagePipeline = Fresco.getImagePipeline() - val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null) - dataSource.subscribe( - object : BaseBitmapDataSubscriber() { - override fun onNewResultImpl(bitmap: Bitmap?) { - if (bitmap != null && resources != null) { - val roundedBitmapDrawable = RoundedBitmapDrawableFactory.create( - resources!!, - bitmap - ) - roundedBitmapDrawable.isCircular = true - roundedBitmapDrawable.setAntiAlias(true) - button.icon = roundedBitmapDrawable - } - } + private fun loadUserAvatar( + target: Target + ) { - override fun onFailureImpl(dataSource: DataSource>) { - if (resources != null) { - button.icon = ResourcesCompat.getDrawable(resources!!, R.drawable.ic_user, null) - } - } - }, - UiThreadImmediateExecutorService.getInstance() + if (activity != null) { + val url = ApiUtils.getUrlForAvatar( + currentUser!!.baseUrl, + currentUser!!.userId, + true + ) + + val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token) + + context.imageLoader.enqueue( + ImageRequest.Builder(context) + .data(url) + .addHeader("Authorization", credentials) + .placeholder(R.drawable.ic_user) + .transformations(CircleCropTransformation()) + .crossfade(true) + .target(target) + .build() ) } } - private fun loadUserAvatar(menuItem: MenuItem) { - if (activity != null) { - val imageRequest = DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - currentUser!!.baseUrl, - currentUser!!.userId, - true - ), - currentUser - ) - val imagePipeline = Fresco.getImagePipeline() - val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null) - dataSource.subscribe( - object : BaseBitmapDataSubscriber() { - override fun onNewResultImpl(bitmap: Bitmap?) { - if (bitmap != null && resources != null) { - val roundedBitmapDrawable = RoundedBitmapDrawableFactory.create( - resources!!, - bitmap - ) - roundedBitmapDrawable.isCircular = true - roundedBitmapDrawable.setAntiAlias(true) - menuItem.icon = roundedBitmapDrawable - } - } + private fun loadUserAvatar(button: MaterialButton) { - override fun onFailureImpl(dataSource: DataSource>) { - if (resources != null) { - menuItem.icon = ResourcesCompat.getDrawable(resources!!, R.drawable.ic_user, null) - } - } - }, - UiThreadImmediateExecutorService.getInstance() - ) + val target = object : Target { + override fun onStart(placeholder: Drawable?) { + button.icon = placeholder + } + override fun onSuccess(result: Drawable) { + button.icon = result + } } + + loadUserAvatar(target) + } + + private fun loadUserAvatar(menuItem: MenuItem) { + val target = object : Target { + override fun onStart(placeholder: Drawable?) { + menuItem.icon = placeholder + } + override fun onSuccess(result: Drawable) { + menuItem.icon = result + } + } + loadUserAvatar(target) } override fun onAttach(view: View) { 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..c8b391d22 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt @@ -0,0 +1,283 @@ +/* + * 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 . + */ + +@file:Suppress("TooManyFunctions") + +package com.nextcloud.talk.extensions + +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import android.os.Build +import android.util.Log +import android.widget.ImageView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import coil.annotation.ExperimentalCoilApi +import coil.imageLoader +import coil.load +import coil.request.ImageRequest +import coil.request.SuccessResult +import coil.result +import coil.transform.CircleCropTransformation +import coil.transform.RoundedCornersTransformation +import com.amulyakhare.textdrawable.TextDrawable +import com.nextcloud.talk.R +import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.ui.theme.ViewThemeUtils +import com.nextcloud.talk.utils.ApiUtils + +private const val ROUNDING_PIXEL = 16f +private const val TAG = "ImageViewExtensions" + +fun ImageView.loadAvatar( + user: User, + avatar: String, + requestBigSize: Boolean = true +): io.reactivex.disposables +.Disposable { + + val imageRequestUri = ApiUtils.getUrlForAvatar( + user.baseUrl, + avatar, + requestBigSize + ) + + return loadAvatarInternal(user, imageRequestUri, false) +} + +fun ImageView.replaceAvatar( + user: User, + avatar: String, + requestBigSize: Boolean = true +): io.reactivex.disposables +.Disposable { + + val imageRequestUri = ApiUtils.getUrlForAvatar( + user.baseUrl, + avatar, + requestBigSize + ) + + return loadAvatarInternal(user, imageRequestUri, true) +} + +@OptIn(ExperimentalCoilApi::class) +private fun ImageView.loadAvatarInternal( + user: User?, + url: String, + replace: Boolean +): io.reactivex.disposables +.Disposable { + + if (replace && this.result is SuccessResult) { + val result = this.result as SuccessResult + val memoryCacheKey = result.memoryCacheKey + val memoryCache = context.imageLoader.memoryCache + memoryCacheKey?.let { memoryCache?.remove(it) } + + val diskCacheKey = result.diskCacheKey + val diskCache = context.imageLoader.diskCache + diskCacheKey?.let { diskCache?.remove(it) } + } + + return DisposableWrapper( + load(url) { + user?.let { + addHeader( + "Authorization", + ApiUtils.getCredentials(user.username, user.token) + ) + } + transformations(CircleCropTransformation()) + placeholder(R.drawable.account_circle_96dp) + listener(onError = { _, result -> + Log.w(TAG, "Can't load avatar with URL: $url", result.throwable) + }) + } + ) +} + +@Deprecated("Use function loadAvatar", level = DeprecationLevel.WARNING) +fun ImageView.loadAvatarWithUrl(user: User? = null, url: String): io.reactivex.disposables.Disposable { + return loadAvatarInternal(user, url, false) +} + +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.startsWith(user.baseUrl!!) && + (url.contains("index.php/core/preview?fileId=") || url.contains("/avatar/")) + ) { + requestBuilder.addHeader( + "Authorization", + ApiUtils.getCredentials(user.username, user.token) + ) + } + + return DisposableWrapper(context.imageLoader.enqueue(requestBuilder.build())) +} + +fun ImageView.loadImage(url: String, user: User, placeholder: Drawable? = null): io.reactivex.disposables.Disposable { + + val requestBuilder = ImageRequest.Builder(context) + .data(url) + .crossfade(true) + .target(this) + .placeholder(placeholder) + .error(placeholder) + .transformations(RoundedCornersTransformation(ROUNDING_PIXEL, ROUNDING_PIXEL, ROUNDING_PIXEL, ROUNDING_PIXEL)) + + if (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(context.imageLoader.enqueue(requestBuilder.build())) +} + +fun ImageView.loadAvatarOrImagePreview(url: String, user: User, placeholder: Drawable? = null): io.reactivex +.disposables.Disposable { + return if (url.contains("/avatar/")) { + loadAvatarInternal(user, url, false) + } else { + loadImage(url, user, placeholder) + } +} + +fun ImageView.loadAvatar(any: Any?): io.reactivex.disposables.Disposable { + return DisposableWrapper( + load(any) { + transformations(CircleCropTransformation()) + } + ) +} + +fun ImageView.loadSystemAvatar(): io.reactivex.disposables.Disposable { + val data: Any = 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) + layerDrawable + } else { + R.mipmap.ic_launcher + } + + return DisposableWrapper( + load(data) { + transformations(CircleCropTransformation()) + } + ) +} + +fun ImageView.loadChangelogBotAvatar(): io.reactivex.disposables.Disposable { + return loadSystemAvatar() +} + +fun ImageView.loadBotsAvatar(): io.reactivex.disposables.Disposable { + return loadAvatar( + TextDrawable.builder() + .beginConfig() + .bold() + .endConfig() + .buildRound( + ">", + ResourcesCompat.getColor(context.resources, R.color.black, null) + ) + ) +} + +fun ImageView.loadGroupCallAvatar(viewThemeUtils: ViewThemeUtils): io.reactivex.disposables.Disposable { + + val data: Any = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + viewThemeUtils.talk.themePlaceholderAvatar(this, R.drawable.ic_avatar_group) as Any + } else { + R.drawable.ic_circular_group + } + return loadAvatar(data) +} + +fun ImageView.loadPublicCallAvatar(viewThemeUtils: ViewThemeUtils): io.reactivex.disposables.Disposable { + val data: Any = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + viewThemeUtils.talk.themePlaceholderAvatar(this, R.drawable.ic_avatar_link) as Any + } else { + R.drawable.ic_circular_link + } + return loadAvatar(data) +} + +fun ImageView.loadMailAvatar(viewThemeUtils: ViewThemeUtils): io.reactivex.disposables.Disposable { + val data: Any = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + viewThemeUtils.talk.themePlaceholderAvatar(this, R.drawable.ic_avatar_mail) as Any + } else { + R.drawable.ic_circular_mail + } + return loadAvatar(data) +} + +fun ImageView.loadGuestAvatar(user: User, name: String, big: Boolean): io.reactivex.disposables.Disposable { + return loadGuestAvatar(user.baseUrl!!, name, big) +} + +fun ImageView.loadGuestAvatar(baseUrl: String, name: String, big: Boolean): io.reactivex.disposables.Disposable { + val imageRequestUri = ApiUtils.getUrlForGuestAvatar( + baseUrl, + name, + big + ) + return DisposableWrapper( + load(imageRequestUri) { + transformations(CircleCropTransformation()) + listener(onError = { _, result -> + Log.w(TAG, "Can't load guest avatar with URL: $imageRequestUri", result.throwable) + }) + } + ) +} + +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/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt index 2d7dc11b7..d70735032 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt @@ -526,7 +526,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor notificationUser.id, false ) else ApiUtils.getUrlForGuestAvatar(baseUrl, notificationUser.name, false) - person.setIcon(loadAvatarSync(avatarUrl)) + person.setIcon(loadAvatarSync(avatarUrl, context!!)) } notificationBuilder.setStyle(getStyle(person.build(), style)) } diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterViewHolder.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterViewHolder.kt index e69f0038b..bc415f4e2 100644 --- a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVoterViewHolder.kt @@ -22,16 +22,15 @@ package com.nextcloud.talk.polls.adapters import android.annotation.SuppressLint import android.text.TextUtils -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.interfaces.DraweeController +import android.widget.ImageView import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.PollResultVoterItemBinding +import com.nextcloud.talk.extensions.loadAvatar +import com.nextcloud.talk.extensions.loadGuestAvatar import com.nextcloud.talk.polls.model.PollDetails import com.nextcloud.talk.ui.theme.ViewThemeUtils -import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils class PollResultVoterViewHolder( private val user: User, @@ -46,45 +45,19 @@ class PollResultVoterViewHolder( binding.root.setOnClickListener { clickListener.onClick() } binding.pollVoterName.text = item.details.actorDisplayName - binding.pollVoterAvatar.controller = getAvatarDraweeController(item.details) + loadAvatar(item.details, binding.pollVoterAvatar) viewThemeUtils.dialog.colorDialogSupportingText(binding.pollVoterName) } - private fun getAvatarDraweeController(pollDetail: PollDetails): DraweeController? { - var draweeController: DraweeController? = null + private fun loadAvatar(pollDetail: PollDetails, avatar: ImageView) { if (pollDetail.actorType == "guests") { var displayName = NextcloudTalkApplication.sharedApplication?.resources?.getString(R.string.nc_guest) if (!TextUtils.isEmpty(pollDetail.actorDisplayName)) { displayName = pollDetail.actorDisplayName!! } - draweeController = Fresco.newDraweeControllerBuilder() - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForGuestAvatar( - user.baseUrl, - displayName, - false - ), - user - ) - ) - .build() + avatar.loadGuestAvatar(user, displayName!!, false) } else if (pollDetail.actorType == "users") { - draweeController = Fresco.newDraweeControllerBuilder() - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - user.baseUrl, - pollDetail.actorId, - false - ), - user - ) - ) - .build() + avatar.loadAvatar(user, pollDetail.actorId!!, false) } - return draweeController } } diff --git a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewViewHolder.kt b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewViewHolder.kt index d071cc76b..d934115fb 100644 --- a/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/polls/adapters/PollResultVotersOverviewViewHolder.kt @@ -2,6 +2,8 @@ * Nextcloud Talk application * * @author Marcel Hibbe + * @author Tim Krüger + * Copyright (C) 2022 Tim Krüger * Copyright (C) 2022 Marcel Hibbe * * This program is free software: you can redistribute it and/or modify @@ -22,20 +24,16 @@ package com.nextcloud.talk.polls.adapters import android.annotation.SuppressLint import android.text.TextUtils +import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView -import androidx.core.content.res.ResourcesCompat -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.generic.RoundingParams -import com.facebook.drawee.interfaces.DraweeController -import com.facebook.drawee.view.SimpleDraweeView import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.PollResultVotersOverviewItemBinding +import com.nextcloud.talk.extensions.loadAvatar +import com.nextcloud.talk.extensions.loadGuestAvatar import com.nextcloud.talk.polls.model.PollDetails -import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils class PollResultVotersOverviewViewHolder( private val user: User, @@ -61,24 +59,14 @@ class PollResultVotersOverviewViewHolder( for (i in 0 until avatarsToDisplay) { val pollDetails = item.detailsList[i] - val avatar = SimpleDraweeView(binding.root.context) + val avatar = ImageView(binding.root.context) layoutParams.marginStart = i * AVATAR_OFFSET avatar.layoutParams = layoutParams avatar.translationZ = i.toFloat() * -1 - val roundingParams = RoundingParams.fromCornersRadius(AVATAR_RADIUS) - roundingParams.roundAsCircle = true - roundingParams.borderColor = ResourcesCompat.getColor( - itemView.context.resources!!, - R.color.vote_dialog_background, - null - ) - roundingParams.borderWidth = DisplayUtils.convertDpToPixel(2.0f, itemView.context) - - avatar.hierarchy.roundingParams = roundingParams - avatar.controller = getAvatarDraweeController(pollDetails) + loadAvatar(pollDetails, avatar) binding.votersAvatarsOverviewWrapper.addView(avatar) @@ -92,47 +80,20 @@ class PollResultVotersOverviewViewHolder( } } - private fun getAvatarDraweeController(pollDetail: PollDetails): DraweeController? { - var draweeController: DraweeController? = null + private fun loadAvatar(pollDetail: PollDetails, avatar: ImageView) { if (pollDetail.actorType == "guests") { var displayName = NextcloudTalkApplication.sharedApplication?.resources?.getString(R.string.nc_guest) if (!TextUtils.isEmpty(pollDetail.actorDisplayName)) { displayName = pollDetail.actorDisplayName!! } - draweeController = Fresco.newDraweeControllerBuilder() - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForGuestAvatar( - user.baseUrl, - displayName, - false - ), - user - ) - ) - .build() + avatar.loadGuestAvatar(user, displayName!!, false) } else if (pollDetail.actorType == "users") { - draweeController = Fresco.newDraweeControllerBuilder() - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - user.baseUrl, - pollDetail.actorId, - false - ), - user - ) - ) - .build() + avatar.loadAvatar(user, pollDetail.actorId!!, false) } - return draweeController } companion object { const val AVATAR_SIZE = 60 - const val AVATAR_RADIUS = 5f const val MAX_AVATARS = 10 const val AVATAR_OFFSET = AVATAR_SIZE - 20 const val DOTS_OFFSET = 70 diff --git a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt index 64f62f564..aa6806974 100644 --- a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt +++ b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt @@ -162,7 +162,7 @@ class DirectReplyReceiver : BroadcastReceiver() { val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false) val me = Person.Builder() .setName(currentUser.displayName) - .setIcon(NotificationUtils.loadAvatarSync(avatarUrl)) + .setIcon(NotificationUtils.loadAvatarSync(avatarUrl, context)) .build() val message = NotificationCompat.MessagingStyle.Message(reply, System.currentTimeMillis(), me) previousStyle?.addMessage(message) diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt index bef8be657..b1e10a118 100644 --- a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsListViewHolder.kt @@ -22,20 +22,18 @@ package com.nextcloud.talk.remotefilebrowser.adapters import android.text.format.Formatter import android.view.View +import android.widget.ImageView import autodagger.AutoInjector -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.interfaces.DraweeController -import com.facebook.drawee.view.SimpleDraweeView import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.RvItemBrowserFileBinding +import com.nextcloud.talk.extensions.loadImage import com.nextcloud.talk.remotefilebrowser.SelectionInterface import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DateUtils.getLocalDateTimeStringFromTimestamp -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.Mimetype.FOLDER @AutoInjector(NextcloudTalkApplication::class) @@ -48,7 +46,7 @@ class RemoteFileBrowserItemsListViewHolder( onItemClicked: (Int) -> Unit ) : RemoteFileBrowserItemsViewHolder(binding, mimeTypeSelectionFilter, currentUser, selectionInterface) { - override val fileIcon: SimpleDraweeView + override val fileIcon: ImageView get() = binding.fileIcon private var selectable: Boolean = true @@ -68,7 +66,6 @@ class RemoteFileBrowserItemsListViewHolder( override fun onBind(item: RemoteFileBrowserItem) { super.onBind(item) - binding.fileIcon.controller = null if (!item.isAllowedToReShare || item.isEncrypted) { binding.root.isEnabled = false binding.root.alpha = DISABLED_ALPHA @@ -95,11 +92,7 @@ class RemoteFileBrowserItemsListViewHolder( calculateClickability(item, selectable) setSelectability() - binding.fileIcon - .hierarchy - .setPlaceholderImage( - viewThemeUtils.talk.getPlaceholderImage(binding.root.context, item.mimeType) - ) + val placeholder = viewThemeUtils.talk.getPlaceholderImage(binding.root.context, item.mimeType) if (item.hasPreview) { val path = ApiUtils.getUrlForFilePreviewWithRemotePath( @@ -108,12 +101,10 @@ class RemoteFileBrowserItemsListViewHolder( binding.fileIcon.context.resources.getDimensionPixelSize(R.dimen.small_item_height) ) if (path.isNotEmpty()) { - val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() - .setAutoPlayAnimations(true) - .setImageRequest(DisplayUtils.getImageRequestForUrl(path)) - .build() - binding.fileIcon.controller = draweeController + binding.fileIcon.loadImage(path, currentUser, placeholder) } + } else { + binding.fileIcon.setImageDrawable(placeholder) } binding.filenameTextView.text = item.displayName diff --git a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsViewHolder.kt b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsViewHolder.kt index fa76dcccb..027b849aa 100644 --- a/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/remotefilebrowser/adapters/RemoteFileBrowserItemsViewHolder.kt @@ -20,11 +20,9 @@ package com.nextcloud.talk.remotefilebrowser.adapters -import android.graphics.drawable.Drawable -import androidx.core.content.ContextCompat +import android.widget.ImageView import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding -import com.facebook.drawee.view.SimpleDraweeView import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.remotefilebrowser.SelectionInterface import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem @@ -37,17 +35,9 @@ abstract class RemoteFileBrowserItemsViewHolder( val selectionInterface: SelectionInterface, ) : RecyclerView.ViewHolder(binding.root) { - abstract val fileIcon: SimpleDraweeView + abstract val fileIcon: ImageView open fun onBind(item: RemoteFileBrowserItem) { - fileIcon.hierarchy.setPlaceholderImage(staticImage(item.mimeType, fileIcon)) - } - - private fun staticImage( - mimeType: String?, - image: SimpleDraweeView - ): Drawable { - val drawableResourceId = DrawableUtils.getDrawableResourceIdForMimeType(mimeType) - return ContextCompat.getDrawable(image.context, drawableResourceId)!! + fileIcon.setImageResource(DrawableUtils.getDrawableResourceIdForMimeType(item.mimeType)) } } diff --git a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsGridViewHolder.kt b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsGridViewHolder.kt index 30977e6c8..d2b0cb7ad 100644 --- a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsGridViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsGridViewHolder.kt @@ -23,8 +23,8 @@ package com.nextcloud.talk.shareditems.adapters import android.view.View +import android.widget.ImageView import android.widget.ProgressBar -import com.facebook.drawee.view.SimpleDraweeView import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.SharedItemGridBinding import com.nextcloud.talk.ui.theme.ViewThemeUtils @@ -35,7 +35,7 @@ class SharedItemsGridViewHolder( viewThemeUtils: ViewThemeUtils ) : SharedItemsViewHolder(binding, user, viewThemeUtils) { - override val image: SimpleDraweeView + override val image: ImageView get() = binding.image override val clickTarget: View get() = binding.image diff --git a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsListViewHolder.kt b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsListViewHolder.kt index f63549ede..1eb23cf90 100644 --- a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsListViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsListViewHolder.kt @@ -26,9 +26,10 @@ import android.content.Context import android.content.Intent import android.text.format.Formatter import android.view.View +import android.widget.ImageView import android.widget.ProgressBar import androidx.core.content.ContextCompat -import com.facebook.drawee.view.SimpleDraweeView +import coil.load import com.nextcloud.talk.R import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.SharedItemListBinding @@ -46,7 +47,7 @@ class SharedItemsListViewHolder( viewThemeUtils: ViewThemeUtils ) : SharedItemsViewHolder(binding, user, viewThemeUtils) { - override val image: SimpleDraweeView + override val image: ImageView get() = binding.fileImage override val clickTarget: View get() = binding.fileItem @@ -75,7 +76,7 @@ class SharedItemsListViewHolder( binding.separator1.visibility = View.GONE binding.fileDate.text = item.dateTime binding.actor.text = item.actorName - image.hierarchy.setPlaceholderImage(R.drawable.ic_baseline_bar_chart_24) + image.load(R.drawable.ic_baseline_bar_chart_24) image.setColorFilter( ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon), android.graphics.PorterDuff.Mode.SRC_IN @@ -93,7 +94,7 @@ class SharedItemsListViewHolder( binding.separator1.visibility = View.GONE binding.fileDate.text = item.dateTime binding.actor.text = item.actorName - image.hierarchy.setPlaceholderImage(R.drawable.ic_baseline_location_on_24) + image.load(R.drawable.ic_baseline_location_on_24) image.setColorFilter( ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon), android.graphics.PorterDuff.Mode.SRC_IN @@ -114,7 +115,7 @@ class SharedItemsListViewHolder( binding.separator1.visibility = View.GONE binding.fileDate.text = item.dateTime binding.actor.text = item.actorName - image.hierarchy.setPlaceholderImage(R.drawable.ic_mimetype_file) + image.load(R.drawable.ic_mimetype_file) image.setColorFilter( ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon), android.graphics.PorterDuff.Mode.SRC_IN @@ -129,7 +130,7 @@ class SharedItemsListViewHolder( binding.separator1.visibility = View.GONE binding.fileDate.text = item.dateTime binding.actor.text = item.actorName - image.hierarchy.setPlaceholderImage(R.drawable.ic_baseline_deck_24) + image.load(R.drawable.ic_baseline_deck_24) image.setColorFilter( ContextCompat.getColor(image.context, R.color.high_emphasis_menu_icon), android.graphics.PorterDuff.Mode.SRC_IN diff --git a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt index a1861cdaa..c699e3595 100644 --- a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt @@ -23,29 +23,20 @@ package com.nextcloud.talk.shareditems.adapters import android.content.Context -import android.net.Uri -import android.util.Log import android.view.View +import android.widget.ImageView import android.widget.ProgressBar import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.controller.BaseControllerListener -import com.facebook.drawee.controller.ControllerListener -import com.facebook.drawee.interfaces.DraweeController -import com.facebook.drawee.view.SimpleDraweeView -import com.facebook.imagepipeline.common.RotationOptions -import com.facebook.imagepipeline.image.ImageInfo -import com.facebook.imagepipeline.request.ImageRequestBuilder import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.extensions.loadImage import com.nextcloud.talk.shareditems.model.SharedDeckCardItem import com.nextcloud.talk.shareditems.model.SharedFileItem import com.nextcloud.talk.shareditems.model.SharedItem -import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.shareditems.model.SharedLocationItem import com.nextcloud.talk.shareditems.model.SharedOtherItem import com.nextcloud.talk.shareditems.model.SharedPollItem -import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.FileViewerUtils abstract class SharedItemsViewHolder( @@ -58,21 +49,21 @@ abstract class SharedItemsViewHolder( private val TAG = SharedItemsViewHolder::class.simpleName } - abstract val image: SimpleDraweeView + abstract val image: ImageView abstract val clickTarget: View abstract val progressBar: ProgressBar - private val authHeader = mapOf( - Pair( - "Authorization", - ApiUtils.getCredentials(user.username, user.token) - ) - ) - open fun onBind(item: SharedFileItem) { - image.hierarchy.setPlaceholderImage(viewThemeUtils.talk.getPlaceholderImage(image.context, item.mimeType)) + + val placeholder = viewThemeUtils.talk.getPlaceholderImage(image.context, item.mimeType) if (item.previewAvailable) { - image.controller = configurePreview(item) + image.loadImage( + item.previewLink, + user, + placeholder + ) + } else { + image.setImageDrawable(placeholder) } /* @@ -105,28 +96,6 @@ abstract class SharedItemsViewHolder( ) } - private fun configurePreview(item: SharedFileItem): DraweeController { - val imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(item.previewLink)) - .setProgressiveRenderingEnabled(true) - .setRotationOptions(RotationOptions.autoRotate()) - .disableDiskCache() - .setHeaders(authHeader) - .build() - - val listener: ControllerListener = object : BaseControllerListener() { - override fun onFailure(id: String, e: Throwable) { - Log.w(TAG, "Failed to load image. A static mimetype image will be used", e) - } - } - - return Fresco.newDraweeControllerBuilder() - .setOldController(image.controller) - .setAutoPlayAnimations(true) - .setImageRequest(imageRequest) - .setControllerListener(listener) - .build() - } - open fun onBind(item: SharedPollItem, showPoll: (item: SharedItem, context: Context) -> Unit) {} open fun onBind(item: SharedLocationItem) {} diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java index c42b711b3..d75c99403 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java @@ -33,8 +33,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.interfaces.DraweeController; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.nextcloud.talk.activities.MainActivity; import com.nextcloud.talk.adapters.items.AdvancedUserItem; @@ -42,6 +40,7 @@ import com.nextcloud.talk.api.NcApi; import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.databinding.DialogChooseAccountBinding; +import com.nextcloud.talk.extensions.ImageViewExtensionsKt; import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.models.json.status.Status; import com.nextcloud.talk.models.json.status.StatusOverall; @@ -132,22 +131,10 @@ public class ChooseAccountDialogFragment extends DialogFragment { if (user.getBaseUrl() != null && (user.getBaseUrl().startsWith("http://") || user.getBaseUrl().startsWith("https://"))) { binding.currentAccount.userIcon.setVisibility(View.VISIBLE); - - DraweeController draweeController = Fresco.newDraweeControllerBuilder() - .setOldController(binding.currentAccount.userIcon.getController()) - .setAutoPlayAnimations(true) - .setImageRequest(DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - user.getBaseUrl(), - user.getUserId(), - false))) - .build(); - binding.currentAccount.userIcon.setController(draweeController); - + ImageViewExtensionsKt.loadAvatar(binding.currentAccount.userIcon, user, user.getUserId(), true); } else { binding.currentAccount.userIcon.setVisibility(View.INVISIBLE); } - loadCurrentStatus(user); } } diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt index 90bdba9dd..38a90a99b 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt @@ -33,8 +33,6 @@ import android.view.ViewGroup import androidx.fragment.app.DialogFragment import androidx.recyclerview.widget.LinearLayoutManager import autodagger.AutoInjector -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.drawee.interfaces.DraweeController import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.nextcloud.talk.activities.MainActivity import com.nextcloud.talk.adapters.items.AdvancedUserItem @@ -42,11 +40,10 @@ import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.DialogChooseAccountShareToBinding +import com.nextcloud.talk.extensions.loadAvatar import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.users.UserManager -import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import java.net.CookieManager @@ -97,21 +94,7 @@ class ChooseAccountShareToDialogFragment : DialogFragment() { if (user.baseUrl != null && (user.baseUrl!!.startsWith("http://") || user.baseUrl!!.startsWith("https://")) ) { - binding!!.currentAccount.userIcon.visibility = View.VISIBLE - val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() - .setOldController(binding!!.currentAccount.userIcon.controller) - .setAutoPlayAnimations(true) - .setImageRequest( - DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatar( - user.baseUrl, - user.userId, - false - ) - ) - ) - .build() - binding!!.currentAccount.userIcon.controller = draweeController + binding!!.currentAccount.userIcon.loadAvatar(user, user.userId!!) } else { binding!!.currentAccount.userIcon.visibility = View.INVISIBLE } 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 cb363337d..fdd4d5605 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java @@ -3,6 +3,8 @@ * * @author Mario Danic * @author Andy Scherzinger + * @author Tim Krüger + * Copyright (C) 2022 Tim Krüger * Copyright (C) 2021 Andy Scherzinger * Copyright (C) 2017-2020 Mario Danic * @@ -33,11 +35,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Typeface; -import android.graphics.drawable.Animatable; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.graphics.drawable.VectorDrawable; import android.net.Uri; import android.os.Build; import android.text.Spannable; @@ -51,36 +49,19 @@ import android.text.style.AbsoluteSizeSpan; import android.text.style.ClickableSpan; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; -import android.util.Log; import android.util.TypedValue; import android.view.View; -import android.view.ViewGroup; import android.view.Window; import android.widget.EditText; +import android.widget.ImageView; import android.widget.TextView; -import com.facebook.common.executors.UiThreadImmediateExecutorService; -import com.facebook.common.references.CloseableReference; -import com.facebook.datasource.DataSource; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.controller.ControllerListener; -import com.facebook.drawee.interfaces.DraweeController; -import com.facebook.drawee.view.SimpleDraweeView; -import com.facebook.imagepipeline.common.RotationOptions; -import com.facebook.imagepipeline.core.ImagePipeline; -import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber; -import com.facebook.imagepipeline.image.CloseableImage; -import com.facebook.imagepipeline.image.ImageInfo; -import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor; -import com.facebook.imagepipeline.postprocessors.RoundPostprocessor; -import com.facebook.imagepipeline.request.ImageRequest; -import com.facebook.imagepipeline.request.ImageRequestBuilder; -import com.facebook.widget.text.span.BetterImageSpan; import com.google.android.material.chip.ChipDrawable; import com.nextcloud.talk.R; import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.events.UserMentionClickEvent; +import com.nextcloud.talk.extensions.ImageViewExtensionsKt; import com.nextcloud.talk.ui.theme.ViewThemeUtils; import com.nextcloud.talk.utils.text.Spans; @@ -91,8 +72,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.DateFormat; import java.util.Date; -import java.util.HashMap; -import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -109,6 +88,11 @@ import androidx.core.content.res.ResourcesCompat; import androidx.core.graphics.ColorUtils; import androidx.core.graphics.drawable.DrawableCompat; import androidx.emoji.text.EmojiCompat; +import coil.Coil; +import coil.request.ImageRequest; +import coil.target.Target; +import coil.transform.CircleCropTransformation; +import third.parties.fresco.BetterImageSpan; import static com.nextcloud.talk.utils.FileSortOrder.sort_a_to_z_id; import static com.nextcloud.talk.utils.FileSortOrder.sort_big_to_small_id; @@ -119,8 +103,6 @@ import static com.nextcloud.talk.utils.FileSortOrder.sort_z_to_a_id; public class DisplayUtils { - private static final String TAG = "DisplayUtils"; - private static final int INDEX_LUMINATION = 2; private static final double MAX_LIGHTNESS = 0.92; @@ -154,33 +136,6 @@ public class DisplayUtils { textView.setMovementMethod(LinkMovementMethod.getInstance()); } - private static void updateViewSize(@Nullable ImageInfo imageInfo, SimpleDraweeView draweeView) { - if (imageInfo != null && draweeView.getId() != R.id.messageUserAvatar) { - int maxSize = draweeView.getContext().getResources().getDimensionPixelSize(R.dimen.maximum_file_preview_size); - draweeView.getLayoutParams().width = imageInfo.getWidth() > maxSize ? maxSize : imageInfo.getWidth(); - draweeView.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT; - draweeView.setAspectRatio((float) imageInfo.getWidth() / imageInfo.getHeight()); - draweeView.requestLayout(); - } - } - - public static Drawable getRoundedDrawable(Drawable drawable) { - Bitmap bitmap = getBitmap(drawable); - 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) { Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), @@ -191,60 +146,6 @@ public class DisplayUtils { return bitmap; } - public static ImageRequest getImageRequestForUrl(String url) { - return getImageRequestForUrl(url, (User) null); - } - - public static ImageRequest getImageRequestForUrl(String url, @Nullable User user) { - Map headers = new HashMap<>(); - if (user != null && - url.startsWith(user.getBaseUrl()) && - (url.contains("index.php/core/preview?fileId=") || url.contains("/avatar/"))) { - headers.put("Authorization", ApiUtils.getCredentials(user.getUsername(), user.getToken())); - } - - return ImageRequestBuilder.newBuilderWithSource(Uri.parse(url)) - .setProgressiveRenderingEnabled(true) - .setRotationOptions(RotationOptions.autoRotate()) - .disableDiskCache() - .setHeaders(headers) - .build(); - } - - public static ControllerListener getImageControllerListener(SimpleDraweeView draweeView) { - return new ControllerListener() { - @Override - public void onSubmit(String id, Object callerContext) { - // unused atm - } - - @Override - public void onFinalImageSet(String id, @Nullable Object imageInfo, @Nullable Animatable animatable) { - updateViewSize((ImageInfo) imageInfo, draweeView); - } - - @Override - public void onIntermediateImageSet(String id, @Nullable Object imageInfo) { - updateViewSize((ImageInfo) imageInfo, draweeView); - } - - @Override - public void onIntermediateImageFailed(String id, Throwable throwable) { - // unused atm - } - - @Override - public void onFailure(String id, Throwable throwable) { - // unused atm - } - - @Override - public void onRelease(String id) { - // unused atm - } - }; - } - public static float convertDpToPixel(float dp, Context context) { return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()) + 0.5f); @@ -335,33 +236,37 @@ public class DisplayUtils { conversationUser.getBaseUrl(), String.valueOf(label), true); } - ImageRequest imageRequest = getImageRequestForUrl(url); - ImagePipeline imagePipeline = Fresco.getImagePipeline(); - DataSource> dataSource = imagePipeline.fetchDecodedImage( - imageRequest, - context); - dataSource.subscribe( - new BaseBitmapDataSubscriber() { + ImageRequest imageRequest = new ImageRequest.Builder(context) + .data(url) + .crossfade(true) + .transformations(new CircleCropTransformation()) + .target(new Target() { @Override - protected void onNewResultImpl(Bitmap bitmap) { - if (bitmap != null) { - chip.setChipIcon(getRoundedDrawable(new BitmapDrawable(bitmap))); + public void onStart(@Nullable Drawable drawable) { - // A hack to refresh the chip icon - if (emojiEditText != null) { - emojiEditText.post(() -> emojiEditText.setTextKeepState( - emojiEditText.getText(), - TextView.BufferType.SPANNABLE)); - } + } + + @Override + public void onError(@Nullable Drawable drawable) { + + } + + @Override + public void onSuccess(@NonNull Drawable drawable) { + chip.setChipIcon(drawable); + + // A hack to refresh the chip icon + if (emojiEditText != null) { + emojiEditText.post(() -> emojiEditText.setTextKeepState( + emojiEditText.getText(), + TextView.BufferType.SPANNABLE)); } } + }) + .build(); - @Override - protected void onFailureImpl(DataSource> dataSource) { - } - }, - UiThreadImmediateExecutorService.getInstance()); + Coil.imageLoader(context).enqueue(imageRequest); } return chip; @@ -575,7 +480,7 @@ public class DisplayUtils { } } - public static void loadAvatarImage(User user, SimpleDraweeView avatarImageView, boolean deleteCache) { + public static void loadAvatarImage(User user, ImageView avatarImageView, boolean deleteCache) { String avatarId; if (!TextUtils.isEmpty(user.getUserId())) { avatarId = user.getUserId(); @@ -583,50 +488,13 @@ public class DisplayUtils { avatarId = user.getUsername(); } - String avatarString = ApiUtils.getUrlForAvatar(user.getBaseUrl(), avatarId, true); - - // clear cache if (deleteCache) { - Uri avatarUri = Uri.parse(avatarString); - - ImagePipeline imagePipeline = Fresco.getImagePipeline(); - imagePipeline.evictFromMemoryCache(avatarUri); - imagePipeline.evictFromDiskCache(avatarUri); - imagePipeline.evictFromCache(avatarUri); - } - - DraweeController draweeController = Fresco.newDraweeControllerBuilder() - .setOldController(avatarImageView.getController()) - .setAutoPlayAnimations(true) - .setImageRequest(DisplayUtils.getImageRequestForUrl(avatarString)) - .build(); - 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)); + ImageViewExtensionsKt.replaceAvatar(avatarImageView, user, avatarId, true); } else { - targetView.getHierarchy().setPlaceholderImage(R.mipmap.ic_launcher); + ImageViewExtensionsKt.loadAvatar(avatarImageView, user, avatarId, true); } } - 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()) { @@ -649,7 +517,7 @@ public class DisplayUtils { /** * calculates the relative time string based on the given modification timestamp. * - * @param context the app's context + * @param context the app's context * @param modificationTimestamp the UNIX timestamp of the file modification time in milliseconds. * @return a relative time string */ diff --git a/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByName.kt b/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByName.kt index 2b4f3524a..487c4f3c7 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByName.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/FileSortOrderByName.kt @@ -22,7 +22,7 @@ package com.nextcloud.talk.utils import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem -import third_parties.daveKoeller.AlphanumComparator +import third.parties.daveKoeller.AlphanumComparator import java.util.Collections class FileSortOrderByName internal constructor(name: String, ascending: Boolean) : FileSortOrder(name, ascending) { @@ -40,7 +40,8 @@ class FileSortOrderByName internal constructor(name: String, ascending: Boolean) * Comparator for RemoteFileBrowserItems, sorts by name. */ class RemoteFileBrowserItemNameComparator(private val multiplier: Int) : Comparator { - private val alphanumComparator = AlphanumComparator() + private val alphanumComparator = + AlphanumComparator() override fun compare(left: RemoteFileBrowserItem, right: RemoteFileBrowserItem): Int { return if (!left.isFile && !right.isFile) { diff --git a/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt index 35d1f2998..098c2dceb 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt @@ -28,6 +28,7 @@ import android.net.Uri import android.os.Build import android.util.Log import android.view.View +import android.widget.ImageView import android.widget.ProgressBar import android.widget.Toast import androidx.core.content.FileProvider @@ -36,7 +37,6 @@ import androidx.work.Data import androidx.work.OneTimeWorkRequest import androidx.work.WorkInfo import androidx.work.WorkManager -import com.facebook.drawee.view.SimpleDraweeView import com.nextcloud.talk.R import com.nextcloud.talk.activities.FullScreenImageActivity import com.nextcloud.talk.activities.FullScreenMediaActivity @@ -412,7 +412,7 @@ class FileViewerUtils(private val context: Context, private val user: User) { data class ProgressUi( val progressBar: ProgressBar?, val messageText: EmojiTextView?, - val previewImage: SimpleDraweeView + val previewImage: ImageView ) data class FileInfo( 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 62bbc837c..dbd264dea 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt @@ -27,18 +27,19 @@ import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context +import android.graphics.drawable.BitmapDrawable import android.media.AudioAttributes import android.net.Uri import android.os.Build import android.service.notification.StatusBarNotification import android.text.TextUtils +import android.util.Log import androidx.core.graphics.drawable.IconCompat +import coil.executeBlocking +import coil.imageLoader +import coil.request.ImageRequest +import coil.transform.CircleCropTransformation import com.bluelinelabs.logansquare.LoganSquare -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 @@ -50,6 +51,8 @@ import java.io.IOException @Suppress("TooManyFunctions") object NotificationUtils { + const val TAG = "NotificationUtils" + enum class NotificationChannels { NOTIFICATION_CHANNEL_MESSAGES_V4, NOTIFICATION_CHANNEL_CALLS_V4, @@ -320,28 +323,31 @@ object NotificationUtils { ) } - /* - * Load user avatar synchronously. - * Inspired by: - * https://frescolib.org/docs/using-image-pipeline.html - * https://github.com/facebook/fresco/issues/830 - * https://localcoder.org/using-facebooks-fresco-to-load-a-bitmap - */ - fun loadAvatarSync(avatarUrl: String): IconCompat? { - // TODO - how to handle errors here? + fun loadAvatarSync(avatarUrl: String, context: Context): IconCompat? { var avatarIcon: IconCompat? = null - val imageRequest = DisplayUtils.getImageRequestForUrl(avatarUrl) - val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, null) - 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) - } - CloseableReference.closeSafely(closeableImageRef) - dataSource.close() + + val request = ImageRequest.Builder(context) + .data(avatarUrl) + .transformations(CircleCropTransformation()) + .placeholder(R.drawable.account_circle_96dp) + .placeholder(R.drawable.account_circle_96dp) + .target( + onSuccess = { result -> + val bitmap = (result as BitmapDrawable).bitmap + avatarIcon = IconCompat.createWithBitmap(bitmap) + }, + onError = { error -> + error?.let { + val bitmap = (error as BitmapDrawable).bitmap + avatarIcon = IconCompat.createWithBitmap(bitmap) + } + Log.w(TAG, "Can't load avatar for URL: $avatarUrl") + } + ) + .build() + + context.imageLoader.executeBlocking(request) + return avatarIcon } diff --git a/app/src/main/java/com/nextcloud/talk/utils/OkHttpNetworkFetcherWithCache.java b/app/src/main/java/com/nextcloud/talk/utils/OkHttpNetworkFetcherWithCache.java deleted file mode 100644 index 82e483cf0..000000000 --- a/app/src/main/java/com/nextcloud/talk/utils/OkHttpNetworkFetcherWithCache.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * 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.utils; - -import com.facebook.imagepipeline.backends.okhttp3.OkHttpNetworkFetcher; -import okhttp3.Call; -import okhttp3.OkHttpClient; - -import java.util.concurrent.Executor; - -public class OkHttpNetworkFetcherWithCache extends OkHttpNetworkFetcher { - public OkHttpNetworkFetcherWithCache(OkHttpClient okHttpClient) { - super(okHttpClient); - } - - public OkHttpNetworkFetcherWithCache(Call.Factory callFactory, Executor cancellationExecutor) { - super(callFactory, cancellationExecutor); - } - - public OkHttpNetworkFetcherWithCache(Call.Factory callFactory, Executor cancellationExecutor, boolean disableOkHttpCache) { - super(callFactory, cancellationExecutor, true); - } -} diff --git a/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java b/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java index 077ebe857..50d6bfa07 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java +++ b/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java @@ -22,9 +22,10 @@ package com.nextcloud.talk.utils.text; import android.graphics.drawable.Drawable; -import com.facebook.widget.text.span.BetterImageSpan; + import androidx.annotation.NonNull; +import third.parties.fresco.BetterImageSpan; public class Spans { diff --git a/app/src/main/java/third_parties/daveKoeller/AlphanumComparator.java b/app/src/main/java/third/parties/daveKoeller/AlphanumComparator.java similarity index 99% rename from app/src/main/java/third_parties/daveKoeller/AlphanumComparator.java rename to app/src/main/java/third/parties/daveKoeller/AlphanumComparator.java index 35cd76177..59eac62ea 100644 --- a/app/src/main/java/third_parties/daveKoeller/AlphanumComparator.java +++ b/app/src/main/java/third/parties/daveKoeller/AlphanumComparator.java @@ -22,7 +22,7 @@ * */ -package third_parties.daveKoeller; +package third.parties.daveKoeller; import java.io.Serializable; import java.math.BigInteger; diff --git a/app/src/main/java/third_parties/daveKoeller/lgpl-2.1.txt b/app/src/main/java/third/parties/daveKoeller/lgpl-2.1.txt similarity index 100% rename from app/src/main/java/third_parties/daveKoeller/lgpl-2.1.txt rename to app/src/main/java/third/parties/daveKoeller/lgpl-2.1.txt diff --git a/app/src/main/java/third/parties/fresco/BetterImageSpan.kt b/app/src/main/java/third/parties/fresco/BetterImageSpan.kt new file mode 100644 index 000000000..fbd003ed0 --- /dev/null +++ b/app/src/main/java/third/parties/fresco/BetterImageSpan.kt @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * MIT License + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package third.parties.fresco + +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Paint.FontMetricsInt +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.text.style.ReplacementSpan +import androidx.annotation.IntDef + +/** + * A better implementation of image spans that also supports centering images against the text. + * + * In order to migrate from ImageSpan, replace `new ImageSpan(drawable, alignment)` with + * `new BetterImageSpan(drawable, BetterImageSpan.normalizeAlignment(alignment))`. + * + * There are 2 main differences between BetterImageSpan and ImageSpan: + * 1. Pass in ALIGN_CENTER to center images against the text. + * 2. ALIGN_BOTTOM no longer unnecessarily increases the size of the text: + * DynamicDrawableSpan (ImageSpan's parent) adjusts sizes as if alignment was ALIGN_BASELINE + * which can lead to unnecessary whitespace. + */ +open class BetterImageSpan @JvmOverloads constructor( + val drawable: Drawable, + @param:BetterImageSpanAlignment private val mAlignment: Int = ALIGN_BASELINE +) : ReplacementSpan() { + @IntDef(*[ALIGN_BASELINE, ALIGN_BOTTOM, ALIGN_CENTER]) + @Retention(AnnotationRetention.SOURCE) + annotation class BetterImageSpanAlignment + + private var mWidth = 0 + private var mHeight = 0 + private var mBounds: Rect? = null + private val mFontMetricsInt = FontMetricsInt() + + init { + updateBounds() + } + + /** + * Returns the width of the image span and increases the height if font metrics are available. + */ + override fun getSize( + paint: Paint, + text: CharSequence, + start: Int, + end: Int, + fontMetrics: FontMetricsInt? + ): Int { + updateBounds() + if (fontMetrics == null) { + return mWidth + } + val offsetAbove = getOffsetAboveBaseline(fontMetrics) + val offsetBelow = mHeight + offsetAbove + if (offsetAbove < fontMetrics.ascent) { + fontMetrics.ascent = offsetAbove + } + if (offsetAbove < fontMetrics.top) { + fontMetrics.top = offsetAbove + } + if (offsetBelow > fontMetrics.descent) { + fontMetrics.descent = offsetBelow + } + if (offsetBelow > fontMetrics.bottom) { + fontMetrics.bottom = offsetBelow + } + return mWidth + } + + override fun draw( + canvas: Canvas, + text: CharSequence, + start: Int, + end: Int, + x: Float, + top: Int, + y: Int, + bottom: Int, + paint: Paint + ) { + paint.getFontMetricsInt(mFontMetricsInt) + val iconTop = y + getOffsetAboveBaseline(mFontMetricsInt) + canvas.translate(x, iconTop.toFloat()) + drawable.draw(canvas) + canvas.translate(-x, -iconTop.toFloat()) + } + + private fun updateBounds() { + mBounds = drawable.bounds + mWidth = mBounds!!.width() + mHeight = mBounds!!.height() + } + + private fun getOffsetAboveBaseline(fm: FontMetricsInt): Int { + return when (mAlignment) { + ALIGN_BOTTOM -> fm.descent - mHeight + ALIGN_CENTER -> { + val textHeight = fm.descent - fm.ascent + val offset = (textHeight - mHeight) / 2 + fm.ascent + offset + } + ALIGN_BASELINE -> -mHeight + else -> -mHeight + } + } + + companion object { + const val ALIGN_BOTTOM = 0 + const val ALIGN_BASELINE = 1 + const val ALIGN_CENTER = 2 + } +} diff --git a/app/src/main/res/drawable/shape_oval.xml b/app/src/main/res/drawable/shape_oval.xml new file mode 100644 index 000000000..df8bfd8bd --- /dev/null +++ b/app/src/main/res/drawable/shape_oval.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/account_item.xml b/app/src/main/res/layout/account_item.xml index 1f4b04761..8668dbcb9 100644 --- a/app/src/main/res/layout/account_item.xml +++ b/app/src/main/res/layout/account_item.xml @@ -21,7 +21,6 @@ --> - + android:src="@drawable/account_circle_48dp" /> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 55fab6b8a..430439fdb 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -113,7 +113,8 @@ android:transitionName="userAvatar.transitionTag" app:cornerRadius="@dimen/button_corner_radius" app:iconSize="@dimen/avatar_size_app_bar" - tools:visibility="gone" /> + app:iconTint="@null" + tools:icon="@drawable/ic_user" /> diff --git a/app/src/main/res/layout/call_activity.xml b/app/src/main/res/layout/call_activity.xml index e7dc38665..2a3f3b71b 100644 --- a/app/src/main/res/layout/call_activity.xml +++ b/app/src/main/res/layout/call_activity.xml @@ -2,9 +2,11 @@ ~ Nextcloud Talk application ~ ~ @author Mario Danic - ~ Copyright (C) 2017-2018 Mario Danic ~ @author Marcel Hibbe + ~ @author Tim Krüger + ~ Copyright (C) 2022 Tim Krüger ~ Copyright (C) 2021 Marcel Hibbe + ~ 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 @@ -69,14 +71,14 @@ android:visibility="invisible" tools:visibility="visible" /> - + app:srcCompat="@drawable/ic_switch_video_white_24px" + android:contentDescription="@string/nc_call_button_content_description_switch_to_self_vide"/> - + android:layout_height="wrap_content" + android:adjustViewBounds="true" + android:layout_marginHorizontal="@dimen/call_controls_margin_horizontal" + android:layout_weight="1" + android:background="@drawable/shape_oval" + android:backgroundTint="@color/call_buttons_background" + app:srcCompat="@drawable/ic_baseline_picture_in_picture_alt_24" + android:contentDescription="@string/nc_call_button_content_description_pip" /> - + android:layout_height="wrap_content" + android:adjustViewBounds="true" + android:layout_marginHorizontal="@dimen/call_controls_margin_horizontal" + android:layout_weight="1" + android:background="@drawable/shape_oval" + android:backgroundTint="@color/call_buttons_background" + app:srcCompat="@drawable/ic_volume_mute_white_24dp" + android:contentDescription="@string/nc_call_button_content_description_audio_output" /> - + android:background="@drawable/shape_oval" + android:backgroundTint="@color/call_buttons_background" + app:srcCompat="@drawable/ic_videocam_white_24px" + android:contentDescription="@string/nc_call_button_content_description_camera" /> - + android:background="@drawable/shape_oval" + android:backgroundTint="@color/call_buttons_background" + app:srcCompat="@drawable/ic_mic_off_white_24px" + android:contentDescription="@string/nc_call_button_content_description_microphone" /> - + android:layout_height="wrap_content" + android:adjustViewBounds="true" + android:layout_marginHorizontal="@dimen/call_controls_margin_horizontal" + android:layout_weight="1" + android:background="@drawable/shape_oval" + android:backgroundTint="@color/nc_darkRed" + app:srcCompat="@drawable/ic_call_end_white_24px" + android:contentDescription="@string/nc_call_button_content_description_hangup" /> - + app:srcCompat="@drawable/ic_circular_group" + tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout/call_item.xml b/app/src/main/res/layout/call_item.xml index 41f175472..8b87a7eac 100644 --- a/app/src/main/res/layout/call_item.xml +++ b/app/src/main/res/layout/call_item.xml @@ -31,12 +31,12 @@ android:gravity="center" android:orientation="vertical"> - + android:contentDescription="@string/avatar"/> ~ Copyright (C) 2021 Andy Scherzinger ~ Copyright (C) 2017-2018 Mario Danic ~ @@ -36,36 +38,37 @@ android:animateLayoutChanges="true" android:orientation="horizontal"> - + android:background="@drawable/shape_oval" + android:backgroundTint="@color/nc_darkGreen" + android:src="@drawable/ic_call_white_24dp" + android:contentDescription="@string/nc_call_button_content_description_answer_voice_only" /> - + android:background="@drawable/shape_oval" + android:backgroundTint="@color/nc_darkRed" + android:src="@drawable/ic_call_end_white_24px" + android:contentDescription="@string/nc_call_button_content_description_hangup" /> - + tools:visibility="visible" + android:contentDescription="@string/nc_call_button_content_description_answer_video_call" /> - + android:contentDescription="@string/avatar" /> diff --git a/app/src/main/res/layout/controller_conversation_info.xml b/app/src/main/res/layout/controller_conversation_info.xml index 6ae406ad2..53d8fd534 100644 --- a/app/src/main/res/layout/controller_conversation_info.xml +++ b/app/src/main/res/layout/controller_conversation_info.xml @@ -77,13 +77,13 @@ android:layout_marginTop="@dimen/margin_between_elements" tools:text="Jane Doe" /> - + tools:background="@color/hwSecurityRed" + android:contentDescription="@string/avatar" /> diff --git a/app/src/main/res/layout/controller_profile.xml b/app/src/main/res/layout/controller_profile.xml index d09b65b7d..f401adaf3 100644 --- a/app/src/main/res/layout/controller_profile.xml +++ b/app/src/main/res/layout/controller_profile.xml @@ -19,7 +19,6 @@ --> - + android:contentDescription="@string/avatar" /> - + android:contentDescription="@string/avatar" /> - + android:src="@drawable/account_circle_48dp" /> - + android:contentDescription="@string/avatar" /> - + android:contentDescription="@string/avatar" /> - + android:contentDescription="@string/avatar" /> - + android:contentDescription="@string/avatar" /> - - + tools:src="@drawable/ic_call_black_24dp" + tools:ignore="ContentDescription" /> - - + tools:src="@drawable/ic_call_black_24dp" + tools:ignore="ContentDescription" /> - + android:contentDescription="@string/avatar" /> - + android:contentDescription="@string/avatar" /> - - + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scaleType="fitStart" + tools:src="@drawable/ic_call_black_24dp" + tools:ignore="ContentDescription" /> - - + tools:src="@drawable/ic_call_black_24dp" + tools:ignore="ContentDescription" /> - + android:contentDescription="@string/avatar"/> - + android:contentDescription="@string/avatar"/> + tools:layout_height="100dp" /> + tools:visibility="visible" /> + tools:visibility="visible" /> + tools:visibility="visible" /> - + tools:visibility="visible" + tools:ignore="ContentDescription" /> \ No newline at end of file diff --git a/app/src/main/res/layout/rv_item_browser_file.xml b/app/src/main/res/layout/rv_item_browser_file.xml index 08958cdff..74bf7c065 100644 --- a/app/src/main/res/layout/rv_item_browser_file.xml +++ b/app/src/main/res/layout/rv_item_browser_file.xml @@ -93,13 +93,12 @@ android:textSize="@dimen/two_line_primary_text_size" tools:text="filename.md" /> - + tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout/rv_item_contact.xml b/app/src/main/res/layout/rv_item_contact.xml index 7bcefd7e7..4abb3ecee 100644 --- a/app/src/main/res/layout/rv_item_contact.xml +++ b/app/src/main/res/layout/rv_item_contact.xml @@ -48,19 +48,19 @@ android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toStartOf="@id/checkedImageView" - android:layout_toEndOf="@id/avatar_drawee_view" + android:layout_toEndOf="@id/avatar_view" android:ellipsize="end" android:lines="1" android:textAlignment="viewStart" android:textAppearance="@style/ListItem" tools:text="Jane Doe" /> - + android:contentDescription="@string/avatar" /> diff --git a/app/src/main/res/layout/rv_item_contact_shimmer.xml b/app/src/main/res/layout/rv_item_contact_shimmer.xml index d392feb5a..417efe7bd 100644 --- a/app/src/main/res/layout/rv_item_contact_shimmer.xml +++ b/app/src/main/res/layout/rv_item_contact_shimmer.xml @@ -29,7 +29,7 @@ android:orientation="vertical"> diff --git a/app/src/main/res/layout/rv_item_conversation_info_participant.xml b/app/src/main/res/layout/rv_item_conversation_info_participant.xml index c72f1b1d3..494a1c608 100644 --- a/app/src/main/res/layout/rv_item_conversation_info_participant.xml +++ b/app/src/main/res/layout/rv_item_conversation_info_participant.xml @@ -26,15 +26,14 @@ android:layout_marginBottom="@dimen/standard_half_margin" android:layout_marginTop="@dimen/standard_margin"> - + app:layout_constraintTop_toTopOf="parent" /> ~ Copyright (C) 2021 Andy Scherzinger ~ Copyright (C) 2017-2018 Mario Danic ~ @@ -38,12 +40,11 @@ android:layout_centerVertical="true" android:layout_marginEnd="@dimen/double_margin_between_elements"> - + android:contentDescription="@null" /> + app:layout_constraintTop_toTopOf="parent" /> ~ Copyright (C) 2021 Andy Scherzinger ~ Copyright (C) 2017-2018 Mario Danic ~ @@ -35,14 +37,13 @@ android:layout_margin="@dimen/double_margin_between_elements" tools:background="@color/white"> - + app:layout_constraintTop_toTopOf="parent" /> @@ -40,14 +40,12 @@ app:layout_flexGrow="1" app:layout_wrapBefore="true"> - + tools:ignore="ContentDescription" /> - + tools:src="@drawable/ic_call_black_24dp" + tools:ignore="ContentDescription" /> ~ Copyright (C) 2017-2019 Mario Danic ~ ~ This program is free software: you can redistribute it and/or modify @@ -67,6 +69,8 @@ 80dp 180dp 110dp + 10dp + 10dp 48dp 48dp 0dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9fa3b5a2c..40ec2036e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,6 +4,8 @@ ~ @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 @@ -211,6 +213,14 @@ %1$s with phone %1$s with video You missed a call from %s + Open picture in picture mode + Change audio output + Toggle camera + Toggle microphone + Hangup + Answer as voice call only + Answer as video call + Switch to self video Mute microphone From 4b46270362efcf1175ad8f92c7b5ed1bddbadda6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Kr=C3=BCger?= Date: Fri, 14 Oct 2022 11:52:17 +0200 Subject: [PATCH 3/6] 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 +- .../nextcloud/talk/activities/BaseActivity.kt | 5 +- .../talk/activities/CallActivity.java | 17 +---- .../nextcloud/talk/activities/MainActivity.kt | 8 +-- .../application/NextcloudTalkApplication.kt | 2 - .../talk/controllers/ChatController.kt | 12 ++-- .../controllers/LocationPickerController.kt | 10 +-- .../talk/controllers/LockedController.kt | 20 ++---- .../talk/controllers/SettingsController.kt | 65 +++++++---------- .../nextcloud/talk/jobs/NotificationWorker.kt | 11 +-- .../talk/jobs/UploadAndShareFilesWorker.kt | 10 +-- .../nextcloud/talk/utils/DisplayUtils.java | 65 +++++------------ .../nextcloud/talk/utils/NotificationUtils.kt | 2 +- .../nextcloud/talk/utils/SecurityUtils.java | 5 -- .../permissions/PlatformPermissionUtilImpl.kt | 13 ++-- .../talk/utils/ssl/SSLSocketFactoryCompat.kt | 72 ++----------------- .../talk/webrtc/WebRtcAudioManager.java | 25 +++---- 17 files changed, 87 insertions(+), 257 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 05e1259db..db6336453 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/activities/BaseActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt index 538379357..a9e76d42a 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt @@ -22,7 +22,6 @@ package com.nextcloud.talk.activities import android.annotation.SuppressLint import android.content.Context -import android.os.Build import android.os.Bundle import android.util.Log import android.view.WindowManager @@ -76,9 +75,7 @@ open class BaseActivity : AppCompatActivity() { } if (appPreferences.isScreenLocked) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - SecurityUtils.createKey(appPreferences.screenLockTimeout) - } + SecurityUtils.createKey(appPreferences.screenLockTimeout) } } diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java index 95829d10b..cd667b435 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java +++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java @@ -732,10 +732,8 @@ public class CallActivity extends CallBaseActivity { } else { if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CALL)) { onPermissionsGranted(); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - requestPermissions(PERMISSIONS_CALL, 100); } else { - onRequestPermissionsResult(100, PERMISSIONS_CALL, new int[]{1, 1}); + requestPermissions(PERMISSIONS_CALL, 100); } } @@ -980,11 +978,7 @@ public class CallActivity extends CallBaseActivity { R.string.nc_microphone_permission_permanently_denied, R.string.nc_permissions_settings, (AppCompatActivity) this); } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - requestPermissions(PERMISSIONS_MICROPHONE, 100); - } else { - onRequestPermissionsResult(100, PERMISSIONS_MICROPHONE, new int[]{1}); - } + requestPermissions(PERMISSIONS_MICROPHONE, 100); } } @@ -1017,12 +1011,7 @@ public class CallActivity extends CallBaseActivity { R.string.nc_camera_permission_permanently_denied, R.string.nc_permissions_settings, (AppCompatActivity) this); } else { - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - requestPermissions(PERMISSIONS_CAMERA, 100); - } else { - onRequestPermissionsResult(100, PERMISSIONS_CAMERA, new int[]{1}); - } + requestPermissions(PERMISSIONS_CAMERA, 100); } } diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt index 9938c3dd5..f8055e66d 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt @@ -24,12 +24,10 @@ package com.nextcloud.talk.activities import android.app.KeyguardManager import android.content.Context import android.content.Intent -import android.os.Build import android.os.Bundle import android.provider.ContactsContract import android.text.TextUtils import android.util.Log -import androidx.annotation.RequiresApi import autodagger.AutoInjector import com.bluelinelabs.conductor.Conductor import com.bluelinelabs.conductor.Router @@ -148,10 +146,7 @@ class MainActivity : BaseActivity(), ActionBarProvider { super.onStart() Log.d(TAG, "onStart: Activity: " + System.identityHashCode(this).toString()) logRouterBackStack(router!!) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - lockScreenIfConditionsApply() - } + lockScreenIfConditionsApply() } override fun onResume() { @@ -323,7 +318,6 @@ class MainActivity : BaseActivity(), ActionBarProvider { }) } - @RequiresApi(api = Build.VERSION_CODES.M) fun lockScreenIfConditionsApply() { val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager if (keyguardManager.isKeyguardSecure && appPreferences.isScreenLocked) { diff --git a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt index cbad9f041..de58edb28 100644 --- a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt +++ b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt @@ -64,7 +64,6 @@ import com.nextcloud.talk.jobs.SignalingSettingsWorker import com.nextcloud.talk.ui.theme.ThemeModule import com.nextcloud.talk.utils.ClosedInterfaceImpl import com.nextcloud.talk.utils.DeviceUtils -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.NotificationUtils import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageModule import com.nextcloud.talk.utils.database.user.UserModule @@ -163,7 +162,6 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver { securityKeyManager.init(this, securityKeyConfig) initializeWebRtc() - DisplayUtils.useCompatVectorIfNeeded() buildComponent() DavUtils.registerCustomFactories() diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index 1f0b04a1a..1648429dd 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -1149,14 +1149,10 @@ class ChatController(args: Bundle) : } private fun isRecordAudioPermissionGranted(): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - return PermissionChecker.checkSelfPermission( - context, - Manifest.permission.RECORD_AUDIO - ) == PermissionChecker.PERMISSION_GRANTED - } else { - true - } + return PermissionChecker.checkSelfPermission( + context, + Manifest.permission.RECORD_AUDIO + ) == PermissionChecker.PERMISSION_GRANTED } private fun startAudioRecording(file: String) { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/LocationPickerController.kt b/app/src/main/java/com/nextcloud/talk/controllers/LocationPickerController.kt index 714e2fa4b..a440fb452 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/LocationPickerController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/LocationPickerController.kt @@ -442,23 +442,19 @@ class LocationPickerController(args: Bundle) : private fun isLocationPermissionsGranted(): Boolean { fun isCoarseLocationGranted(): Boolean { return PermissionChecker.checkSelfPermission( - context!!, + context, Manifest.permission.ACCESS_COARSE_LOCATION ) == PermissionChecker.PERMISSION_GRANTED } fun isFineLocationGranted(): Boolean { return PermissionChecker.checkSelfPermission( - context!!, + context, Manifest.permission.ACCESS_FINE_LOCATION ) == PermissionChecker.PERMISSION_GRANTED } - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - isCoarseLocationGranted() && isFineLocationGranted() - } else { - true - } + return isCoarseLocationGranted() && isFineLocationGranted() } private fun requestLocationPermissions() { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt b/app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt index e18bdbbd6..43f39435f 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt @@ -25,12 +25,10 @@ import android.app.Activity import android.app.KeyguardManager import android.content.Context import android.content.Intent -import android.os.Build import android.os.Handler import android.os.Looper import android.util.Log import android.view.View -import androidx.annotation.RequiresApi import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt.PromptInfo import androidx.core.content.res.ResourcesCompat @@ -62,15 +60,11 @@ class LockedController : BaseController(R.layout.controller_locked) { override fun onViewBound(view: View) { super.onViewBound(view) sharedApplication!!.componentApplication.inject(this) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - binding.unlockContainer.setOnClickListener { - unlock() - } + binding.unlockContainer.setOnClickListener { + unlock() } } - @RequiresApi(api = Build.VERSION_CODES.M) override fun onAttach(view: View) { super.onAttach(view) Log.d(TAG, "onAttach") @@ -92,12 +86,10 @@ class LockedController : BaseController(R.layout.controller_locked) { Log.d(TAG, "onDetach") } - @RequiresApi(api = Build.VERSION_CODES.M) fun unlock() { checkIfWeAreSecure() } - @RequiresApi(api = Build.VERSION_CODES.M) private fun showBiometricDialog() { val context: Context? = activity if (context != null) { @@ -140,11 +132,10 @@ class LockedController : BaseController(R.layout.controller_locked) { } } - @RequiresApi(api = Build.VERSION_CODES.M) private fun checkIfWeAreSecure() { val keyguardManager = activity?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager? - if (keyguardManager?.isKeyguardSecure == true && appPreferences!!.isScreenLocked) { - if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences!!.screenLockTimeout)) { + if (keyguardManager?.isKeyguardSecure == true && appPreferences.isScreenLocked) { + if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)) { Log.d(TAG, "showBiometricDialog because 'we are NOT authenticated'...") showBiometricDialog() } else { @@ -172,8 +163,7 @@ class LockedController : BaseController(R.layout.controller_locked) { if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) { if (resultCode == Activity.RESULT_OK) { if ( - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - SecurityUtils.checkIfWeAreAuthenticated(appPreferences!!.screenLockTimeout) + SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout) ) { Log.d(TAG, "All went well, dismiss locked controller") router.popCurrentController() diff --git a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt index b288900ee..95adb858f 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt @@ -157,18 +157,13 @@ class SettingsController : BaseController(R.layout.controller_settings) { binding.settingsIncognitoKeyboard.visibility = View.GONE } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - binding.settingsScreenLock.visibility = View.GONE - binding.settingsScreenLockTimeout.visibility = View.GONE - } else { - binding.settingsScreenLock.setSummary( - String.format( - Locale.getDefault(), - resources!!.getString(R.string.nc_settings_screen_lock_desc), - resources!!.getString(R.string.nc_app_product_name) - ) + binding.settingsScreenLock.setSummary( + String.format( + Locale.getDefault(), + resources!!.getString(R.string.nc_settings_screen_lock_desc), + resources!!.getString(R.string.nc_app_product_name) ) - } + ) setupPrivacyUrl() setupSourceCodeUrl() @@ -662,10 +657,8 @@ class SettingsController : BaseController(R.layout.controller_settings) { appPreferences.isKeyboardIncognito } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - (binding.settingsIncognitoKeyboard.findViewById(R.id.mp_checkable) as Checkable).isChecked = - appPreferences.isKeyboardIncognito - } + (binding.settingsIncognitoKeyboard.findViewById(R.id.mp_checkable) as Checkable).isChecked = + appPreferences.isKeyboardIncognito if (CapabilitiesUtilNew.isReadStatusAvailable(userManager.currentUser.blockingGet())) { (binding.settingsReadPrivacy.findViewById(R.id.mp_checkable) as Checkable).isChecked = @@ -679,29 +672,27 @@ class SettingsController : BaseController(R.layout.controller_settings) { } private fun setupScreenLockSetting() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager - if (keyguardManager.isKeyguardSecure) { - binding.settingsScreenLock.isEnabled = true - binding.settingsScreenLockTimeout.isEnabled = true - (binding.settingsScreenLock.findViewById(R.id.mp_checkable) as Checkable).isChecked = - appPreferences.isScreenLocked - binding.settingsScreenLockTimeout.isEnabled = appPreferences.isScreenLocked - if (appPreferences.isScreenLocked) { - binding.settingsScreenLockTimeout.alpha = ENABLED_ALPHA - } else { - binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA - } - binding.settingsScreenLock.alpha = ENABLED_ALPHA + val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + if (keyguardManager.isKeyguardSecure) { + binding.settingsScreenLock.isEnabled = true + binding.settingsScreenLockTimeout.isEnabled = true + (binding.settingsScreenLock.findViewById(R.id.mp_checkable) as Checkable).isChecked = + appPreferences.isScreenLocked + binding.settingsScreenLockTimeout.isEnabled = appPreferences.isScreenLocked + if (appPreferences.isScreenLocked) { + binding.settingsScreenLockTimeout.alpha = ENABLED_ALPHA } else { - binding.settingsScreenLock.isEnabled = false - binding.settingsScreenLockTimeout.isEnabled = false - appPreferences.removeScreenLock() - appPreferences.removeScreenLockTimeout() - (binding.settingsScreenLock.findViewById(R.id.mp_checkable) as Checkable).isChecked = false - binding.settingsScreenLock.alpha = DISABLED_ALPHA binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA } + binding.settingsScreenLock.alpha = ENABLED_ALPHA + } else { + binding.settingsScreenLock.isEnabled = false + binding.settingsScreenLockTimeout.isEnabled = false + appPreferences.removeScreenLock() + appPreferences.removeScreenLockTimeout() + (binding.settingsScreenLock.findViewById(R.id.mp_checkable) as Checkable).isChecked = false + binding.settingsScreenLock.alpha = DISABLED_ALPHA + binding.settingsScreenLockTimeout.alpha = DISABLED_ALPHA } } @@ -805,9 +796,7 @@ class SettingsController : BaseController(R.layout.controller_settings) { private inner class ScreenLockTimeoutListener : OnPreferenceValueChangedListener { override fun onChanged(newValue: String?) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - SecurityUtils.createKey(appPreferences.screenLockTimeout) - } + SecurityUtils.createKey(appPreferences.screenLockTimeout) } } diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt index d70735032..3f02ba7e9 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt @@ -447,11 +447,9 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor EmojiCompat.get().process(pushMessage.text!!) ) } - if (Build.VERSION.SDK_INT >= 23) { - // This method should exist since API 21, but some phones don't have it - // So as a safeguard, we don't use it until 23 - notificationBuilder.color = context!!.resources.getColor(R.color.colorPrimary) - } + + notificationBuilder.color = context!!.resources.getColor(R.color.colorPrimary) + val notificationInfoBundle = Bundle() notificationInfoBundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!) // could be an ID or a TOKEN @@ -694,7 +692,6 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor .subscribe(object : Observer { override fun onSubscribe(d: Disposable) = Unit - @RequiresApi(Build.VERSION_CODES.M) override fun onNext(participantsOverall: ParticipantsOverall) { val participantList: List = participantsOverall.ocs!!.data!! hasParticipantsInCall = participantList.isNotEmpty() @@ -726,7 +723,6 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor Log.e(TAG, "Error in getPeersForCall", e) } - @RequiresApi(Build.VERSION_CODES.M) override fun onComplete() { if (isCallNotificationVisible) { @@ -821,7 +817,6 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor return PendingIntent.getActivity(context, requestCode, intent, intentFlag) } - @RequiresApi(Build.VERSION_CODES.M) private fun isCallNotificationVisible(decryptedPushMessage: DecryptedPushMessage): Boolean { var isVisible = false diff --git a/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt index 5be620dd3..47ca9e8d8 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt @@ -295,7 +295,7 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa false } } - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> { + else -> { if (PermissionChecker.checkSelfPermission( context, Manifest.permission.WRITE_EXTERNAL_STORAGE @@ -308,10 +308,6 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa false } } - else -> { // permission is automatically granted on sdk<23 upon installation - Log.d(TAG, "Permission is granted") - true - } } } @@ -325,7 +321,7 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa REQUEST_PERMISSION ) } - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> { + else -> { controller.requestPermissions( arrayOf( Manifest.permission.WRITE_EXTERNAL_STORAGE @@ -333,8 +329,6 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa REQUEST_PERMISSION ) } - else -> { // permission is automatically granted on sdk<23 upon installation - } } } 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 fdd4d5605..a4ab68ed7 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java @@ -24,7 +24,6 @@ package com.nextcloud.talk.utils; -import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -67,9 +66,6 @@ import com.nextcloud.talk.utils.text.Spans; import org.greenrobot.eventbus.EventBus; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.text.DateFormat; import java.util.Date; import java.util.regex.Matcher; @@ -82,7 +78,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.XmlRes; -import androidx.appcompat.widget.AppCompatDrawableManager; import androidx.core.content.ContextCompat; import androidx.core.content.res.ResourcesCompat; import androidx.core.graphics.ColorUtils; @@ -155,31 +150,6 @@ public class DisplayUtils { return px / context.getResources().getDisplayMetrics().density; } - // Solution inspired by https://stackoverflow.com/questions/34936590/why-isnt-my-vector-drawable-scaling-as-expected - public static void useCompatVectorIfNeeded() { - if (Build.VERSION.SDK_INT < 23) { - try { - @SuppressLint("RestrictedApi") AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get(); - Class inflateDelegateClass = Class.forName( - "android.support.v7.widget.AppCompatDrawableManager$InflateDelegate"); - Class vdcInflateDelegateClass = Class.forName( - "android.support.v7.widget.AppCompatDrawableManager$VdcInflateDelegate"); - - Constructor constructor = vdcInflateDelegateClass.getDeclaredConstructor(); - constructor.setAccessible(true); - Object vdcInflateDelegate = constructor.newInstance(); - - Class args[] = {String.class, inflateDelegateClass}; - Method addDelegate = AppCompatDrawableManager.class.getDeclaredMethod("addDelegate", args); - addDelegate.setAccessible(true); - addDelegate.invoke(drawableManager, "vector", vdcInflateDelegate); - } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | - InvocationTargetException | IllegalAccessException e) { - Log.e(TAG, "Failed to use reflection to enable proper vector scaling"); - } - } - } - public static Drawable getTintedDrawable(Resources res, @DrawableRes int drawableResId, @ColorRes int colorResId) { Drawable drawable = ResourcesCompat.getDrawable(res, drawableResId, null); @@ -206,10 +176,8 @@ public class DisplayUtils { viewThemeUtils.material.colorChipDrawable(context, chip); } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - Configuration config = context.getResources().getConfiguration(); - chip.setLayoutDirection(config.getLayoutDirection()); - } + Configuration config = context.getResources().getConfiguration(); + chip.setLayoutDirection(config.getLayoutDirection()); int drawable; @@ -386,24 +354,23 @@ public class DisplayUtils { Window window = activity.getWindow(); boolean isLightTheme = lightTheme(color); if (window != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - View decor = window.getDecorView(); - if (isLightTheme) { - int systemUiFlagLightStatusBar; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - systemUiFlagLightStatusBar = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | - View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; - } else { - systemUiFlagLightStatusBar = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - } - decor.setSystemUiVisibility(systemUiFlagLightStatusBar); + + View decor = window.getDecorView(); + if (isLightTheme) { + int systemUiFlagLightStatusBar; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + systemUiFlagLightStatusBar = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | + View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; } else { - decor.setSystemUiVisibility(0); + systemUiFlagLightStatusBar = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; } - window.setStatusBarColor(color); - } else if (isLightTheme) { - window.setStatusBarColor(Color.BLACK); + decor.setSystemUiVisibility(systemUiFlagLightStatusBar); + } else { + decor.setSystemUiVisibility(0); } + window.setStatusBarColor(color); + } else if (isLightTheme) { + window.setStatusBarColor(Color.BLACK); } } 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 dbd264dea..5e2c80e95 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt @@ -218,7 +218,7 @@ object NotificationUtils { notification: Notification ) -> Unit ) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || conversationUser.id == -1L || context == null) { + if (conversationUser.id == -1L || context == null) { return } diff --git a/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java b/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java index 5420f44af..50f404eff 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java @@ -21,7 +21,6 @@ package com.nextcloud.talk.utils; import android.content.res.Resources; -import android.os.Build; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyProperties; @@ -50,7 +49,6 @@ import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; -import androidx.annotation.RequiresApi; import androidx.biometric.BiometricPrompt; public class SecurityUtils { @@ -60,7 +58,6 @@ public class SecurityUtils { private static BiometricPrompt.CryptoObject cryptoObject; - @RequiresApi(api = Build.VERSION_CODES.M) public static boolean checkIfWeAreAuthenticated(String screenLockTimeout) { try { KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); @@ -95,12 +92,10 @@ public class SecurityUtils { } } - @RequiresApi(api = Build.VERSION_CODES.M) public static BiometricPrompt.CryptoObject getCryptoObject() { return cryptoObject; } - @RequiresApi(api = Build.VERSION_CODES.M) public static void createKey(String validity) { try { KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); diff --git a/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtilImpl.kt b/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtilImpl.kt index 5f2b65976..27f4a6f71 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtilImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtilImpl.kt @@ -23,7 +23,6 @@ package com.nextcloud.talk.utils.permissions import android.Manifest import android.content.Context -import android.os.Build import androidx.core.content.PermissionChecker import com.nextcloud.talk.BuildConfig @@ -32,13 +31,9 @@ class PlatformPermissionUtilImpl(private val context: Context) : PlatformPermiss "${BuildConfig.APPLICATION_ID}.${BuildConfig.PERMISSION_LOCAL_BROADCAST}" override fun isCameraPermissionGranted(): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - return PermissionChecker.checkSelfPermission( - context, - Manifest.permission.CAMERA - ) == PermissionChecker.PERMISSION_GRANTED - } else { - true - } + return PermissionChecker.checkSelfPermission( + context, + Manifest.permission.CAMERA + ) == PermissionChecker.PERMISSION_GRANTED } } diff --git a/app/src/main/java/com/nextcloud/talk/utils/ssl/SSLSocketFactoryCompat.kt b/app/src/main/java/com/nextcloud/talk/utils/ssl/SSLSocketFactoryCompat.kt index b50113882..071226037 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ssl/SSLSocketFactoryCompat.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/ssl/SSLSocketFactoryCompat.kt @@ -8,12 +8,9 @@ package com.nextcloud.talk.utils.ssl -import android.os.Build -import java.io.IOException import java.net.InetAddress import java.net.Socket import java.security.GeneralSecurityException -import java.util.LinkedList import javax.net.ssl.KeyManager import javax.net.ssl.SSLContext import javax.net.ssl.SSLSocket @@ -35,69 +32,12 @@ class SSLSocketFactoryCompat( var cipherSuites: Array? = null init { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - // Since Android 6.0 (API level 23), - // - TLSv1.1 and TLSv1.2 is enabled by default - // - SSLv3 is disabled by default - // - all modern ciphers are activated by default - protocols = null - cipherSuites = null - } else { - val socket = SSLSocketFactory.getDefault().createSocket() as SSLSocket? - try { - socket?.let { - /* set reasonable protocol versions */ - // - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <5.0) - // - remove all SSL versions (especially SSLv3) because they're insecure now - val _protocols = LinkedList() - for (protocol in socket.supportedProtocols.filterNot { it.contains("SSL", true) }) - _protocols += protocol - protocols = _protocols.toTypedArray() - - /* set up reasonable cipher suites */ - val knownCiphers = arrayOf( - // TLS 1.2 - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - // maximum interoperability - "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "SSL_RSA_WITH_3DES_EDE_CBC_SHA", - "TLS_RSA_WITH_AES_128_CBC_SHA", - // additionally - "TLS_RSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" - ) - val availableCiphers = socket.supportedCipherSuites - - /* For maximum security, preferredCiphers should *replace* enabled ciphers (thus - * disabling ciphers which are enabled by default, but have become unsecure), but for - * the security level of DAVdroid and maximum compatibility, disabling of insecure - * ciphers should be a server-side task */ - - // for the final set of enabled ciphers, take the ciphers enabled by default, ... - val _cipherSuites = LinkedList() - _cipherSuites.addAll(socket.enabledCipherSuites) - // ... add explicitly allowed ciphers ... - _cipherSuites.addAll(knownCiphers) - // ... and keep only those which are actually available - _cipherSuites.retainAll(availableCiphers) - - cipherSuites = _cipherSuites.toTypedArray() - } - } catch (e: IOException) { - // Exception is to be ignored - } finally { - socket?.close() // doesn't implement Closeable on all supported Android versions - } - } + // Since Android 6.0 (API level 23), + // - TLSv1.1 and TLSv1.2 is enabled by default + // - SSLv3 is disabled by default + // - all modern ciphers are activated by default + protocols = null + cipherSuites = null } } diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/WebRtcAudioManager.java b/app/src/main/java/com/nextcloud/talk/webrtc/WebRtcAudioManager.java index 45c97cac3..dd0818a4f 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/WebRtcAudioManager.java +++ b/app/src/main/java/com/nextcloud/talk/webrtc/WebRtcAudioManager.java @@ -41,7 +41,6 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.media.AudioDeviceInfo; import android.media.AudioManager; -import android.os.Build; import android.util.Log; import com.nextcloud.talk.events.PeerConnectionEvent; @@ -388,22 +387,18 @@ public class WebRtcAudioManager { */ @Deprecated private boolean hasWiredHeadset() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return audioManager.isWiredHeadsetOn(); - } else { - @SuppressLint("WrongConstant") final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL); - for (AudioDeviceInfo device : devices) { - final int type = device.getType(); - if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET) { - Log.d(TAG, "hasWiredHeadset: found wired headset"); - return true; - } else if (type == AudioDeviceInfo.TYPE_USB_DEVICE) { - Log.d(TAG, "hasWiredHeadset: found USB audio device"); - return true; - } + @SuppressLint("WrongConstant") final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL); + for (AudioDeviceInfo device : devices) { + final int type = device.getType(); + if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET) { + Log.d(TAG, "hasWiredHeadset: found wired headset"); + return true; + } else if (type == AudioDeviceInfo.TYPE_USB_DEVICE) { + Log.d(TAG, "hasWiredHeadset: found USB audio device"); + return true; } - return false; } + return false; } public void updateAudioDeviceState() { From 2b646845bf2c79e46df07442e4ab284c698b8034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey?= Date: Fri, 2 Dec 2022 15:17:31 +0100 Subject: [PATCH 4/6] DirectReplyReceiver: fetch avatar in background MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Brey --- .../talk/receivers/DirectReplyReceiver.kt | 29 ++++++++++++------- .../nextcloud/talk/utils/NotificationUtils.kt | 1 - 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt index aa6806974..feec10aea 100644 --- a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt +++ b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt @@ -47,6 +47,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SYSTEM_NOTIFICATION_ID import io.reactivex.Observer +import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers @@ -159,19 +160,25 @@ class DirectReplyReceiver : BroadcastReceiver() { .extractMessagingStyleFromNotification(previousNotification) // Add reply - val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false) - val me = Person.Builder() - .setName(currentUser.displayName) - .setIcon(NotificationUtils.loadAvatarSync(avatarUrl, context)) - .build() - val message = NotificationCompat.MessagingStyle.Message(reply, System.currentTimeMillis(), me) - previousStyle?.addMessage(message) + Single.fromCallable { + val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false) + val me = Person.Builder() + .setName(currentUser.displayName) + .setIcon(NotificationUtils.loadAvatarSync(avatarUrl, context)) + .build() + val message = NotificationCompat.MessagingStyle.Message(reply, System.currentTimeMillis(), me) + previousStyle?.addMessage(message) - // Set the updated style - previousBuilder.setStyle(previousStyle) + // Set the updated style + previousBuilder.setStyle(previousStyle) - // Update the active notification. - NotificationManagerCompat.from(context).notify(systemNotificationId!!, previousBuilder.build()) + // Check if notification still exists + if (findActiveNotification(systemNotificationId!!) != null) { + NotificationManagerCompat.from(context).notify(systemNotificationId!!, previousBuilder.build()) + } + } + .subscribeOn(Schedulers.io()) + .subscribe() } companion object { 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 5e2c80e95..7ada7b797 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt @@ -330,7 +330,6 @@ object NotificationUtils { .data(avatarUrl) .transformations(CircleCropTransformation()) .placeholder(R.drawable.account_circle_96dp) - .placeholder(R.drawable.account_circle_96dp) .target( onSuccess = { result -> val bitmap = (result as BitmapDrawable).bitmap From ca63bc52c6c057d38f97ed43f82ef78a3ac91ebb Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Tue, 6 Dec 2022 19:09:14 +0100 Subject: [PATCH 5/6] improve lint score Signed-off-by: Andy Scherzinger --- app/src/main/res/layout/poll_result_voter_item.xml | 4 ++-- app/src/main/res/values-night/colors.xml | 2 -- app/src/main/res/values/colors.xml | 3 --- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/layout/poll_result_voter_item.xml b/app/src/main/res/layout/poll_result_voter_item.xml index 31f3be0fe..7b83b3f19 100644 --- a/app/src/main/res/layout/poll_result_voter_item.xml +++ b/app/src/main/res/layout/poll_result_voter_item.xml @@ -17,13 +17,13 @@ ~ You should have received a copy of the GNU General Public License ~ along with this program. If not, see . --> - + tools:background="@color/white" + tools:ignore="UseCompoundDrawables"> #818181 #353535 - #424242 - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 62b316e58..741d89889 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -105,8 +105,5 @@ #FFFFFF - #FFFFFF - - From 96934d7dd937799737c0c30dacf37e8e5ea7e263 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 6 Dec 2022 18:18:30 +0000 Subject: [PATCH 6/6] Analysis: update lint results to reflect reduced error/warning count Signed-off-by: github-actions --- scripts/analysis/lint-results.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/analysis/lint-results.txt b/scripts/analysis/lint-results.txt index 02dd5476d..e0e3dde3a 100644 --- a/scripts/analysis/lint-results.txt +++ b/scripts/analysis/lint-results.txt @@ -1,2 +1,2 @@ DO NOT TOUCH; GENERATED BY DRONE - Lint Report: 1 error and 112 warnings + Lint Report: 112 warnings