1package eu.siacs.conversations.ui.adapter;
2
3import android.content.res.ColorStateList;
4import android.graphics.Typeface;
5import android.util.Pair;
6import android.view.LayoutInflater;
7import android.view.View;
8import android.view.ViewGroup;
9
10import androidx.annotation.DrawableRes;
11import androidx.annotation.NonNull;
12import androidx.core.widget.ImageViewCompat;
13import androidx.databinding.DataBindingUtil;
14import androidx.recyclerview.widget.RecyclerView;
15
16import com.google.android.material.color.MaterialColors;
17import com.google.common.base.Optional;
18
19import eu.siacs.conversations.R;
20import eu.siacs.conversations.databinding.ItemConversationBinding;
21import eu.siacs.conversations.entities.Conversation;
22import eu.siacs.conversations.entities.Conversational;
23import eu.siacs.conversations.entities.Message;
24import eu.siacs.conversations.ui.ConversationFragment;
25import eu.siacs.conversations.ui.XmppActivity;
26import eu.siacs.conversations.ui.util.Attachment;
27import eu.siacs.conversations.ui.util.AvatarWorkerTask;
28import eu.siacs.conversations.utils.IrregularUnicodeDetector;
29import eu.siacs.conversations.utils.UIHelper;
30import eu.siacs.conversations.xmpp.Jid;
31import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
32
33import java.util.List;
34
35public class ConversationAdapter
36 extends RecyclerView.Adapter<ConversationAdapter.ConversationViewHolder> {
37
38 private final XmppActivity activity;
39 private final List<Conversation> conversations;
40 private OnConversationClickListener listener;
41
42 public ConversationAdapter(XmppActivity activity, List<Conversation> conversations) {
43 this.activity = activity;
44 this.conversations = conversations;
45 }
46
47 @NonNull
48 @Override
49 public ConversationViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
50 return new ConversationViewHolder(
51 DataBindingUtil.inflate(
52 LayoutInflater.from(parent.getContext()),
53 R.layout.item_conversation,
54 parent,
55 false));
56 }
57
58 @Override
59 public void onBindViewHolder(@NonNull ConversationViewHolder viewHolder, int position) {
60 Conversation conversation = conversations.get(position);
61 if (conversation == null) {
62 return;
63 }
64 CharSequence name = conversation.getName();
65 if (name instanceof Jid) {
66 viewHolder.binding.conversationName.setText(
67 IrregularUnicodeDetector.style(activity, (Jid) name));
68 } else {
69 viewHolder.binding.conversationName.setText(name);
70 }
71
72 if (conversation == ConversationFragment.getConversation(activity)) {
73 viewHolder.binding.frame.setBackgroundResource(
74 R.drawable.background_selected_item_conversation);
75 // viewHolder.binding.frame.setBackgroundColor(MaterialColors.getColor(viewHolder.binding.frame, com.google.android.material.R.attr.colorSurfaceDim));
76 } else {
77 viewHolder.binding.frame.setBackgroundColor(
78 MaterialColors.getColor(
79 viewHolder.binding.frame,
80 com.google.android.material.R.attr.colorSurface));
81 }
82
83 final Message message = conversation.getLatestMessage();
84 final int status = message.getStatus();
85 final int unreadCount = conversation.unreadCount();
86 final boolean isRead = conversation.isRead();
87 final @DrawableRes Integer messageStatusDrawable =
88 MessageAdapter.getMessageStatusAsDrawable(message, status);
89 if (message.getType() == Message.TYPE_RTP_SESSION) {
90 viewHolder.binding.messageStatus.setVisibility(View.GONE);
91 } else if (messageStatusDrawable == null) {
92 if (status <= Message.STATUS_RECEIVED) {
93 viewHolder.binding.messageStatus.setVisibility(View.GONE);
94 } else {
95 viewHolder.binding.messageStatus.setVisibility(View.INVISIBLE);
96 }
97 } else {
98 viewHolder.binding.messageStatus.setImageResource(messageStatusDrawable);
99 if (status == Message.STATUS_SEND_DISPLAYED) {
100 viewHolder.binding.messageStatus.setImageResource(R.drawable.ic_done_all_bold_24dp);
101 ImageViewCompat.setImageTintList(
102 viewHolder.binding.messageStatus,
103 ColorStateList.valueOf(
104 MaterialColors.getColor(
105 viewHolder.binding.messageStatus,
106 com.google.android.material.R.attr.colorPrimary)));
107 } else {
108 ImageViewCompat.setImageTintList(
109 viewHolder.binding.messageStatus,
110 ColorStateList.valueOf(
111 MaterialColors.getColor(
112 viewHolder.binding.messageStatus,
113 com.google.android.material.R.attr.colorControlNormal)));
114 }
115 viewHolder.binding.messageStatus.setVisibility(View.VISIBLE);
116 }
117 final Conversation.Draft draft = isRead ? conversation.getDraft() : null;
118 if (unreadCount > 0) {
119 viewHolder.binding.unreadCount.setVisibility(View.VISIBLE);
120 viewHolder.binding.unreadCount.setUnreadCount(unreadCount);
121 } else {
122 viewHolder.binding.unreadCount.setVisibility(View.GONE);
123 }
124
125 if (isRead) {
126 viewHolder.binding.conversationName.setTypeface(null, Typeface.NORMAL);
127 } else {
128 viewHolder.binding.conversationName.setTypeface(null, Typeface.BOLD);
129 }
130
131 if (draft != null) {
132 viewHolder.binding.conversationLastmsgImg.setVisibility(View.GONE);
133 viewHolder.binding.conversationLastmsg.setText(draft.getMessage());
134 viewHolder.binding.senderName.setText(R.string.draft);
135 viewHolder.binding.senderName.setVisibility(View.VISIBLE);
136 viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.NORMAL);
137 viewHolder.binding.senderName.setTypeface(null, Typeface.ITALIC);
138 } else {
139 final boolean fileAvailable = !message.isDeleted();
140 final boolean showPreviewText;
141 if (fileAvailable
142 && (message.isFileOrImage()
143 || message.treatAsDownloadable()
144 || message.isGeoUri())) {
145 final var attachment = Attachment.of(message);
146 final @DrawableRes int imageResource = MediaAdapter.getImageDrawable(attachment);
147 showPreviewText = false;
148 viewHolder.binding.conversationLastmsgImg.setImageResource(imageResource);
149 viewHolder.binding.conversationLastmsgImg.setVisibility(View.VISIBLE);
150 } else {
151 viewHolder.binding.conversationLastmsgImg.setVisibility(View.GONE);
152 showPreviewText = true;
153 }
154 final Pair<CharSequence, Boolean> preview =
155 UIHelper.getMessagePreview(
156 activity,
157 message,
158 viewHolder.binding.conversationLastmsg.getCurrentTextColor());
159 if (showPreviewText) {
160 viewHolder.binding.conversationLastmsg.setText(UIHelper.shorten(preview.first));
161 } else {
162 viewHolder.binding.conversationLastmsgImg.setContentDescription(preview.first);
163 }
164 viewHolder.binding.conversationLastmsg.setVisibility(
165 showPreviewText ? View.VISIBLE : View.GONE);
166 if (preview.second) {
167 if (isRead) {
168 viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.ITALIC);
169 viewHolder.binding.senderName.setTypeface(null, Typeface.NORMAL);
170 } else {
171 viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.BOLD_ITALIC);
172 viewHolder.binding.senderName.setTypeface(null, Typeface.BOLD);
173 }
174 } else {
175 if (isRead) {
176 viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.NORMAL);
177 viewHolder.binding.senderName.setTypeface(null, Typeface.NORMAL);
178 } else {
179 viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.BOLD);
180 viewHolder.binding.senderName.setTypeface(null, Typeface.BOLD);
181 }
182 }
183 if (status == Message.STATUS_RECEIVED) {
184 if (conversation.getMode() == Conversation.MODE_MULTI) {
185 viewHolder.binding.senderName.setVisibility(View.VISIBLE);
186 final var displayName = UIHelper.getMessageDisplayName(message);
187 final var displayNameParts = displayName.split("\\s+");
188 // Skip when nickname only consists of blank chars
189 if (displayNameParts.length == 0) {
190 viewHolder.binding.senderName.setText(String.format("%s:", displayName));
191 } else {
192 viewHolder.binding.senderName.setText(
193 String.format("%s:", displayNameParts[0]));
194 }
195 } else {
196 viewHolder.binding.senderName.setVisibility(View.GONE);
197 }
198 } else if (message.getType() != Message.TYPE_STATUS) {
199 viewHolder.binding.senderName.setVisibility(View.VISIBLE);
200 viewHolder.binding.senderName.setText(
201 String.format("%s:", activity.getString(R.string.me)));
202 } else {
203 viewHolder.binding.senderName.setVisibility(View.GONE);
204 }
205 }
206
207 final Optional<OngoingRtpSession> ongoingCall;
208 if (conversation.getMode() == Conversational.MODE_MULTI) {
209 ongoingCall = Optional.absent();
210 } else {
211 ongoingCall =
212 activity.xmppConnectionService
213 .getJingleConnectionManager()
214 .getOngoingRtpConnection(conversation.getContact());
215 }
216
217 if (ongoingCall.isPresent()) {
218 viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
219 viewHolder.binding.notificationStatus.setImageResource(
220 R.drawable.ic_phone_in_talk_24dp);
221 } else {
222 final long muted_till =
223 conversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
224 if (muted_till == Long.MAX_VALUE) {
225 viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
226 viewHolder.binding.notificationStatus.setImageResource(
227 R.drawable.ic_notifications_off_24dp);
228 } else if (muted_till >= System.currentTimeMillis()) {
229 viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
230 viewHolder.binding.notificationStatus.setImageResource(
231 R.drawable.ic_notifications_paused_24dp);
232 } else if (conversation.alwaysNotify()) {
233 viewHolder.binding.notificationStatus.setVisibility(View.GONE);
234 } else {
235 viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
236 viewHolder.binding.notificationStatus.setImageResource(
237 R.drawable.ic_notifications_none_24dp);
238 }
239 }
240
241 long timestamp;
242 if (draft != null) {
243 timestamp = draft.getTimestamp();
244 } else {
245 timestamp = conversation.getLatestMessage().getTimeSent();
246 }
247 viewHolder.binding.pinnedOnTop.setVisibility(
248 conversation.getBooleanAttribute(Conversation.ATTRIBUTE_PINNED_ON_TOP, false)
249 ? View.VISIBLE
250 : View.GONE);
251 viewHolder.binding.conversationLastupdate.setText(
252 UIHelper.readableTimeDifference(activity, timestamp));
253 AvatarWorkerTask.loadAvatar(
254 conversation,
255 viewHolder.binding.conversationImage,
256 R.dimen.avatar_on_conversation_overview);
257 viewHolder.itemView.setOnClickListener(v -> listener.onConversationClick(v, conversation));
258 }
259
260 @Override
261 public int getItemCount() {
262 return conversations.size();
263 }
264
265 public void setConversationClickListener(OnConversationClickListener listener) {
266 this.listener = listener;
267 }
268
269 public void insert(Conversation c, int position) {
270 conversations.add(position, c);
271 notifyDataSetChanged();
272 }
273
274 public void remove(Conversation conversation, int position) {
275 conversations.remove(conversation);
276 notifyItemRemoved(position);
277 }
278
279 public interface OnConversationClickListener {
280 void onConversationClick(View view, Conversation conversation);
281 }
282
283 public static class ConversationViewHolder extends RecyclerView.ViewHolder {
284 public final ItemConversationBinding binding;
285
286 private ConversationViewHolder(final ItemConversationBinding binding) {
287 super(binding.getRoot());
288 this.binding = binding;
289 }
290 }
291}