Implement Coil for conversations list

Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2019-10-24 16:50:28 +02:00
parent 82a9c1d616
commit 7bd0918bd7
5 changed files with 319 additions and 314 deletions

View File

@ -1,311 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.adapters.items;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.emoji.widget.EmojiTextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import coil.ImageLoader;
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.models.database.UserEntity;
import com.nextcloud.talk.models.json.chat.ChatMessage;
import com.nextcloud.talk.models.json.conversations.Conversation;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.DisplayUtils;
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.utils.FlexibleUtils;
import eu.davidea.viewholders.FlexibleViewHolder;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
public class ConversationItem
extends AbstractFlexibleItem<ConversationItem.ConversationItemViewHolder> implements
IFilterable<String> {
private Conversation conversation;
private UserEntity userEntity;
private Context context;
public ConversationItem(Conversation conversation, UserEntity userEntity,
Context activityContext) {
this.conversation = conversation;
this.userEntity = userEntity;
this.context = activityContext;
}
@Override
public boolean equals(Object o) {
if (o instanceof ConversationItem) {
ConversationItem inItem = (ConversationItem) o;
Conversation comparedConversation = inItem.conversation;
return (conversation.getConversationId().equals(comparedConversation.getConversationId())
&& Objects.equals(conversation.getToken(), comparedConversation.getToken())
&& Objects.equals(conversation.getName(), comparedConversation.getName())
&& Objects.equals(conversation.getDisplayName(), comparedConversation.getDisplayName())
&& Objects.equals(conversation.getType(), comparedConversation.getType())
&& Objects.equals(conversation.getLastMessage(), comparedConversation.getLastMessage())
&& Objects.equals(conversation.getFavorite(), comparedConversation.getFavorite())
&& Objects.equals(conversation.getHasPassword(), comparedConversation.getHasPassword())
&& Objects.equals(conversation.getUnreadMessages(),
comparedConversation.getUnreadMessages())
&& Objects.equals(conversation.getUnreadMention(),
comparedConversation.getUnreadMention())
&& Objects.equals(conversation.getObjectType(), comparedConversation.getObjectType())
&& Objects.equals(conversation.getChanging(), comparedConversation.getChanging())
&& Objects.equals(userEntity.getId(), inItem.userEntity.getId())
);
}
return false;
}
public Conversation getModel() {
return conversation;
}
@Override
public int hashCode() {
return Objects.hash(conversation.getConversationId(), conversation.getToken(),
userEntity.getId());
}
@Override
public int getLayoutRes() {
return R.layout.rv_item_conversation_with_last_message;
}
@Override
public ConversationItemViewHolder createViewHolder(View view,
FlexibleAdapter<IFlexible> adapter) {
return new ConversationItemViewHolder(view, adapter);
}
@Override
public void bindViewHolder(FlexibleAdapter<IFlexible> adapter, ConversationItemViewHolder holder,
int position, List<Object> payloads) {
Context appContext =
NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext();
holder.dialogAvatar.setController(null);
if (conversation.getChanging()) {
holder.progressBar.setVisibility(View.VISIBLE);
} else {
holder.progressBar.setVisibility(View.GONE);
}
if (adapter.hasFilter()) {
FlexibleUtils.highlightText(holder.dialogName, conversation.getDisplayName(),
String.valueOf(adapter.getFilter(String.class)),
NextcloudTalkApplication.Companion.getSharedApplication()
.getResources().getColor(R.color.colorPrimary));
} else {
holder.dialogName.setText(conversation.getDisplayName());
}
if (conversation.getUnreadMessages() > 0) {
holder.dialogUnreadBubble.setVisibility(View.VISIBLE);
if (conversation.getUnreadMessages() < 100) {
holder.dialogUnreadBubble.setText(Long.toString(conversation.getUnreadMessages()));
} else {
holder.dialogUnreadBubble.setText("99+");
}
if (conversation.getUnreadMention()) {
holder.dialogUnreadBubble.setBackground(
context.getDrawable(R.drawable.bubble_circle_unread_mention));
} else {
holder.dialogUnreadBubble.setBackground(
context.getDrawable(R.drawable.bubble_circle_unread));
}
} else {
holder.dialogUnreadBubble.setVisibility(View.GONE);
}
if (conversation.getHasPassword()) {
holder.passwordProtectedRoomImageView.setVisibility(View.VISIBLE);
} else {
holder.passwordProtectedRoomImageView.setVisibility(View.GONE);
}
if (conversation.getFavorite()) {
holder.pinnedConversationImageView.setVisibility(View.VISIBLE);
} else {
holder.pinnedConversationImageView.setVisibility(View.GONE);
}
if (conversation.getLastMessage() != null) {
holder.dialogDate.setVisibility(View.VISIBLE);
holder.dialogDate.setText(
DateUtils.getRelativeTimeSpanString(conversation.getLastActivity() * 1000L,
System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE));
if (!TextUtils.isEmpty(conversation.getLastMessage().getSystemMessage())
|| Conversation.ConversationType.SYSTEM_CONVERSATION.equals(conversation.getType())) {
holder.dialogLastMessage.setText(conversation.getLastMessage().getText());
} else {
String authorDisplayName = "";
conversation.getLastMessage().setActiveUser(userEntity);
String text;
if (conversation.getLastMessage()
.getMessageType()
.equals(ChatMessage.MessageType.REGULAR_TEXT_MESSAGE)) {
if (conversation.getLastMessage().getActorId().equals(userEntity.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.dialogLastMessage.setText(text);
}
} else {
holder.dialogDate.setVisibility(View.GONE);
holder.dialogLastMessage.setText(R.string.nc_no_messages_yet);
}
holder.dialogAvatar.setVisibility(View.VISIBLE);
boolean shouldLoadAvatar = true;
String objectType;
if (!TextUtils.isEmpty(objectType = conversation.getObjectType())) {
switch (objectType) {
case "share:password":
shouldLoadAvatar = false;
holder.dialogAvatar.getHierarchy().setImage(new BitmapDrawable(DisplayUtils
.getRoundedBitmapFromVectorDrawableResource(context.getResources(),
R.drawable.ic_file_password_request)), 100, true);
break;
case "file":
shouldLoadAvatar = false;
holder.dialogAvatar.getHierarchy().setImage(new BitmapDrawable(DisplayUtils
.getRoundedBitmapFromVectorDrawableResource(context.getResources(),
R.drawable.ic_file_icon)), 100, true);
break;
default:
break;
}
}
if (Conversation.ConversationType.SYSTEM_CONVERSATION.equals(conversation.getType())) {
Drawable[] layers = new Drawable[2];
layers[0] = context.getDrawable(R.drawable.ic_launcher_background);
layers[1] = context.getDrawable(R.drawable.ic_launcher_foreground);
LayerDrawable layerDrawable = new LayerDrawable(layers);
holder.dialogAvatar.getHierarchy()
.setPlaceholderImage(DisplayUtils.getRoundedDrawable(layerDrawable));
shouldLoadAvatar = false;
}
if (shouldLoadAvatar) {
switch (conversation.getType()) {
case ONE_TO_ONE_CONVERSATION:
if (!TextUtils.isEmpty(conversation.getName())) {
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
.setOldController(holder.dialogAvatar.getController())
.setAutoPlayAnimations(true)
.setImageRequest(DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatarWithName(userEntity.getBaseUrl(),
conversation.getName(), R.dimen.avatar_size), null))
.build();
holder.dialogAvatar.setController(draweeController);
} else {
holder.dialogAvatar.setVisibility(View.GONE);
}
break;
case GROUP_CONVERSATION:
holder.dialogAvatar.getHierarchy()
.setImage(new BitmapDrawable(
DisplayUtils.getRoundedBitmapFromVectorDrawableResource(context.getResources(),
R.drawable.ic_people_group_white_24px)), 100, true);
break;
case PUBLIC_CONVERSATION:
holder.dialogAvatar.getHierarchy().setImage(new BitmapDrawable(DisplayUtils
.getRoundedBitmapFromVectorDrawableResource(context.getResources(),
R.drawable.ic_link_white_24px)), 100, true);
break;
default:
holder.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();
}
static class ConversationItemViewHolder extends FlexibleViewHolder {
@BindView(R.id.dialogAvatar)
SimpleDraweeView dialogAvatar;
@BindView(R.id.dialogName)
EmojiTextView dialogName;
@BindView(R.id.dialogDate)
TextView dialogDate;
@BindView(R.id.dialogLastMessage)
EmojiTextView dialogLastMessage;
@BindView(R.id.dialogUnreadBubble)
TextView dialogUnreadBubble;
@BindView(R.id.passwordProtectedRoomImageView)
ImageView passwordProtectedRoomImageView;
@BindView(R.id.favoriteConversationImageView)
ImageView pinnedConversationImageView;
@BindView(R.id.actionProgressBar)
ProgressBar progressBar;
ConversationItemViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter);
ButterKnife.bind(this, view);
}
}
}

View File

@ -0,0 +1,310 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.adapters.items
import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.text.TextUtils
import android.text.format.DateUtils
import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import androidx.emoji.widget.EmojiTextView
import butterknife.BindView
import butterknife.ButterKnife
import coil.api.load
import coil.transform.CircleCropTransformation
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.utils.ApiUtils
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.utils.FlexibleUtils
import eu.davidea.viewholders.FlexibleViewHolder
import java.util.Objects
import java.util.regex.Pattern
class ConversationItem(
val model: Conversation,
private val userEntity: UserEntity,
private val context: Context
) : AbstractFlexibleItem<ConversationItem.ConversationItemViewHolder>(), IFilterable<String> {
override fun equals(other: Any?): Boolean {
if (other is ConversationItem) {
val inItem = other as ConversationItem?
val comparedConversation = inItem!!.model
return (model.conversationId == comparedConversation.conversationId
&& model.token == comparedConversation.token
&& model.name == comparedConversation.name
&& model.displayName == comparedConversation.displayName
&& model.type == comparedConversation.type
&& model.lastMessage == comparedConversation.lastMessage
&& model.favorite == comparedConversation.favorite
&& model.hasPassword == comparedConversation.hasPassword
&& model.unreadMessages == comparedConversation.unreadMessages
&& model.unreadMention == comparedConversation.unreadMention
&& model.objectType == comparedConversation.objectType
&& model.changing == comparedConversation.changing
&& userEntity.id == inItem.userEntity.id)
}
return false
}
override fun hashCode(): Int {
return Objects.hash(
model.conversationId, model.token,
userEntity.id
)
}
override fun getLayoutRes(): Int {
return R.layout.rv_item_conversation_with_last_message
}
override fun createViewHolder(
view: View,
adapter: FlexibleAdapter<IFlexible<*>>
): ConversationItemViewHolder {
return ConversationItemViewHolder(view, adapter)
}
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<*>>,
holder: ConversationItemViewHolder,
position: Int,
payloads: List<Any>
) {
val appContext = NextcloudTalkApplication.sharedApplication!!.applicationContext
if (model.changing) {
holder.progressBar!!.visibility = View.VISIBLE
} else {
holder.progressBar!!.visibility = View.GONE
}
if (adapter.hasFilter()) {
FlexibleUtils.highlightText(
holder.dialogName!!, model.displayName,
adapter.getFilter(String::class.java).toString(),
NextcloudTalkApplication.sharedApplication!!
.resources.getColor(R.color.colorPrimary)
)
} else {
holder.dialogName!!.text = model.displayName
}
if (model.unreadMessages > 0) {
holder.dialogUnreadBubble!!.visibility = View.VISIBLE
if (model.unreadMessages < 100) {
holder.dialogUnreadBubble!!.text = model.unreadMessages.toLong().toString()
} else {
holder.dialogUnreadBubble!!.text = "99+"
}
if (model.unreadMention) {
holder.dialogUnreadBubble!!.background =
context.getDrawable(R.drawable.bubble_circle_unread_mention)
} else {
holder.dialogUnreadBubble!!.background =
context.getDrawable(R.drawable.bubble_circle_unread)
}
} else {
holder.dialogUnreadBubble!!.visibility = View.GONE
}
if (model.hasPassword) {
holder.passwordProtectedRoomImageView!!.visibility = View.VISIBLE
} else {
holder.passwordProtectedRoomImageView!!.visibility = View.GONE
}
if (model.favorite) {
holder.pinnedConversationImageView!!.visibility = View.VISIBLE
} else {
holder.pinnedConversationImageView!!.visibility = View.GONE
}
if (model.lastMessage != null) {
holder.dialogDate!!.visibility = View.VISIBLE
holder.dialogDate!!.text = DateUtils.getRelativeTimeSpanString(
model.lastActivity * 1000L,
System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE
)
if (!TextUtils.isEmpty(
model.lastMessage!!.systemMessage
) || Conversation.ConversationType.SYSTEM_CONVERSATION == model.type
) {
holder.dialogLastMessage!!.text = model.lastMessage!!.text
} else {
var authorDisplayName = ""
model.lastMessage!!.activeUser = userEntity
val text: String
if (model.lastMessage!!
.messageType == ChatMessage.MessageType.REGULAR_TEXT_MESSAGE
) {
if (model.lastMessage!!.actorId == userEntity.userId) {
text = String.format(
appContext.getString(R.string.nc_formatted_message_you),
model.lastMessage!!.lastMessageDisplayText
)
} else {
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.dialogLastMessage?.text = text
}
} else {
holder.dialogDate?.visibility = View.GONE
holder.dialogLastMessage!!.setText(R.string.nc_no_messages_yet)
}
holder.dialogAvatar?.visibility = View.VISIBLE
var shouldLoadAvatar = true
val objectType: String? = model.objectType
if (!TextUtils.isEmpty(objectType)) {
when (objectType) {
"share:password" -> {
shouldLoadAvatar = false
holder.dialogAvatar?.load(R.drawable.ic_file_password_request) {
transformations(CircleCropTransformation())
}
}
"file" -> {
shouldLoadAvatar = false
holder.dialogAvatar?.load(R.drawable.ic_file_icon) {
transformations(CircleCropTransformation())
}
}
else -> {
}
}
}
if (Conversation.ConversationType.SYSTEM_CONVERSATION == model.type) {
val layers = arrayOfNulls<Drawable>(2)
layers[0] = context.getDrawable(R.drawable.ic_launcher_background)
layers[1] = context.getDrawable(R.drawable.ic_launcher_foreground)
val layerDrawable = LayerDrawable(layers)
holder.dialogAvatar?.load(layerDrawable) {
transformations(CircleCropTransformation())
}
shouldLoadAvatar = false
}
if (shouldLoadAvatar) {
when (model.type) {
Conversation.ConversationType.ONE_TO_ONE_CONVERSATION -> if (!TextUtils.isEmpty(
model.name
)
) {
holder.dialogAvatar?.load(
ApiUtils.getUrlForAvatarWithName(
userEntity.baseUrl,
model.name, R.dimen.avatar_size
)
) {
transformations(CircleCropTransformation())
}
} else {
holder.dialogAvatar?.visibility = View.GONE
}
Conversation.ConversationType.GROUP_CONVERSATION ->
holder.dialogAvatar?.load(R.drawable.ic_people_group_white_24px) {
transformations(CircleCropTransformation())
}
Conversation.ConversationType.PUBLIC_CONVERSATION ->
holder.dialogAvatar?.load(R.drawable.ic_link_white_24px) {
transformations(CircleCropTransformation())
}
else -> holder.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()
}
class ConversationItemViewHolder(
view: View,
adapter: FlexibleAdapter<*>
) : FlexibleViewHolder(view, adapter) {
@JvmField
@BindView(R.id.dialogAvatar)
var dialogAvatar: ImageView? = null
@JvmField
@BindView(R.id.dialogName)
var dialogName: EmojiTextView? = null
@JvmField
@BindView(R.id.dialogDate)
var dialogDate: TextView? = null
@JvmField
@BindView(R.id.dialogLastMessage)
var dialogLastMessage: EmojiTextView? = null
@JvmField
@BindView(R.id.dialogUnreadBubble)
var dialogUnreadBubble: TextView? = null
@JvmField
@BindView(R.id.passwordProtectedRoomImageView)
var passwordProtectedRoomImageView: ImageView? = null
@JvmField
@BindView(R.id.favoriteConversationImageView)
var pinnedConversationImageView: ImageView? = null
@JvmField
@BindView(R.id.actionProgressBar)
var progressBar: ProgressBar? = null
init {
ButterKnife.bind(this, view)
}
}
}

View File

@ -35,6 +35,8 @@ import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import autodagger.AutoComponent
import autodagger.AutoInjector
import coil.Coil
import coil.ImageLoader
import com.facebook.cache.disk.DiskCacheConfig
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig
@ -65,6 +67,7 @@ import de.cotech.hw.SecurityKeyManager
import de.cotech.hw.SecurityKeyManagerConfig
import okhttp3.OkHttpClient
import org.conscrypt.Conscrypt
import org.koin.android.ext.android.inject
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
@ -87,6 +90,8 @@ class NextcloudTalkApplication : Application(), LifecycleObserver {
private set
//endregion
val imageLoader: ImageLoader by inject()
//region Getters
@Inject
@ -138,6 +143,7 @@ class NextcloudTalkApplication : Application(), LifecycleObserver {
DavUtils.registerCustomFactories()
componentApplication.inject(this)
Coil.setDefaultImageLoader(imageLoader)
setAppTheme(appPreferences.theme)
super.onCreate()

View File

@ -293,8 +293,8 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
for (conversation in sortedConversationsList) {
newConversations.add(
ConversationItem(
conversation, viewModel.currentUserLiveData.value,
activity
conversation, viewModel.currentUserLiveData.value!!,
activity!!
)
)
}

View File

@ -42,7 +42,7 @@
android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/margin_between_elements">
<com.facebook.drawee.view.SimpleDraweeView
<ImageView
android:id="@id/dialogAvatar"
android:layout_width="@dimen/small_item_height"
android:layout_height="@dimen/small_item_height"