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