1package eu.siacs.conversations.ui.adapter;
2
3import android.graphics.Typeface;
4import android.util.Pair;
5import android.view.MenuItem;
6import android.view.LayoutInflater;
7import android.view.View;
8import android.view.ViewGroup;
9
10import androidx.annotation.NonNull;
11import androidx.core.graphics.ColorUtils;
12import androidx.databinding.DataBindingUtil;
13import androidx.recyclerview.widget.RecyclerView;
14
15import com.google.common.base.Optional;
16import com.google.common.base.Strings;
17
18import java.util.List;
19
20import eu.siacs.conversations.R;
21import eu.siacs.conversations.databinding.ConversationListRowBinding;
22import eu.siacs.conversations.entities.Conversation;
23import eu.siacs.conversations.entities.Conversational;
24import eu.siacs.conversations.entities.Message;
25import eu.siacs.conversations.ui.ConversationFragment;
26import eu.siacs.conversations.ui.XmppActivity;
27import eu.siacs.conversations.ui.util.AvatarWorkerTask;
28import eu.siacs.conversations.ui.util.StyledAttributes;
29import eu.siacs.conversations.utils.IrregularUnicodeDetector;
30import eu.siacs.conversations.utils.MimeUtils;
31import eu.siacs.conversations.utils.UIHelper;
32import eu.siacs.conversations.xmpp.Jid;
33import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
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.conversation_list_row,
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 (activity.xmppConnectionService != null && activity.xmppConnectionService.getAccounts().size() > 1) {
73 viewHolder.binding.frame.setBackgroundColor(conversation.getAccount().getColor(activity.isDarkTheme()));
74 }
75
76 Message message = conversation.getLatestMessage();
77 final int unreadCount = conversation.unreadCount();
78 final boolean isRead = conversation.isRead();
79 final Conversation.Draft draft = isRead ? conversation.getDraft() : null;
80 if (unreadCount > 0) {
81 viewHolder.binding.unreadCount.setVisibility(View.VISIBLE);
82 viewHolder.binding.unreadCount.setUnreadCount(unreadCount);
83 } else {
84 viewHolder.binding.unreadCount.setVisibility(View.GONE);
85 }
86
87 if (isRead) {
88 viewHolder.binding.conversationName.setTypeface(null, Typeface.NORMAL);
89 } else {
90 viewHolder.binding.conversationName.setTypeface(null, Typeface.BOLD);
91 }
92
93 if (draft != null) {
94 viewHolder.binding.conversationLastmsgImg.setVisibility(View.GONE);
95 viewHolder.binding.conversationLastmsg.setText(draft.getMessage());
96 viewHolder.binding.senderName.setText(R.string.draft);
97 viewHolder.binding.senderName.setVisibility(View.VISIBLE);
98 viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.NORMAL);
99 viewHolder.binding.senderName.setTypeface(null, Typeface.ITALIC);
100 } else {
101 final boolean fileAvailable = !message.isDeleted();
102 final boolean showPreviewText;
103 if (fileAvailable
104 && (message.isFileOrImage()
105 || message.treatAsDownloadable()
106 || message.isGeoUri())) {
107 final int imageResource;
108 if (message.isGeoUri()) {
109 imageResource =
110 activity.getThemeResource(
111 R.attr.ic_attach_location, R.drawable.ic_attach_location);
112 showPreviewText = false;
113 } else {
114 // TODO move this into static MediaPreview method and use same icons as in
115 // MediaAdapter
116 final String mime = message.getMimeType();
117 if (MimeUtils.AMBIGUOUS_CONTAINER_FORMATS.contains(mime)) {
118 final Message.FileParams fileParams = message.getFileParams();
119 if (fileParams.width > 0 && fileParams.height > 0) {
120 imageResource =
121 activity.getThemeResource(
122 R.attr.ic_attach_videocam,
123 R.drawable.ic_attach_videocam);
124 showPreviewText = false;
125 } else if (fileParams.runtime > 0) {
126 imageResource =
127 activity.getThemeResource(
128 R.attr.ic_attach_record, R.drawable.ic_attach_record);
129 showPreviewText = false;
130 } else {
131 imageResource =
132 activity.getThemeResource(
133 R.attr.ic_attach_document,
134 R.drawable.ic_attach_document);
135 showPreviewText = true;
136 }
137 } else {
138 switch (Strings.nullToEmpty(mime).split("/")[0]) {
139 case "image":
140 imageResource =
141 activity.getThemeResource(
142 R.attr.ic_attach_photo, R.drawable.ic_attach_photo);
143 showPreviewText = false;
144 break;
145 case "video":
146 imageResource =
147 activity.getThemeResource(
148 R.attr.ic_attach_videocam,
149 R.drawable.ic_attach_videocam);
150 showPreviewText = false;
151 break;
152 case "audio":
153 imageResource =
154 activity.getThemeResource(
155 R.attr.ic_attach_record,
156 R.drawable.ic_attach_record);
157 showPreviewText = false;
158 break;
159 default:
160 imageResource =
161 activity.getThemeResource(
162 R.attr.ic_attach_document,
163 R.drawable.ic_attach_document);
164 showPreviewText = true;
165 break;
166 }
167 }
168 }
169 viewHolder.binding.conversationLastmsgImg.setImageResource(imageResource);
170 viewHolder.binding.conversationLastmsgImg.setVisibility(View.VISIBLE);
171 } else {
172 viewHolder.binding.conversationLastmsgImg.setVisibility(View.GONE);
173 showPreviewText = true;
174 }
175 final Pair<CharSequence, Boolean> preview =
176 UIHelper.getMessagePreview(
177 activity.xmppConnectionService,
178 message,
179 viewHolder.binding.conversationLastmsg.getCurrentTextColor());
180 if (showPreviewText) {
181 viewHolder.binding.conversationLastmsg.setText(UIHelper.shorten(preview.first));
182 } else {
183 viewHolder.binding.conversationLastmsgImg.setContentDescription(preview.first);
184 }
185 viewHolder.binding.conversationLastmsg.setVisibility(
186 showPreviewText ? View.VISIBLE : View.GONE);
187 if (preview.second) {
188 if (isRead) {
189 viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.ITALIC);
190 viewHolder.binding.senderName.setTypeface(null, Typeface.NORMAL);
191 } else {
192 viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.BOLD_ITALIC);
193 viewHolder.binding.senderName.setTypeface(null, Typeface.BOLD);
194 }
195 } else {
196 if (isRead) {
197 viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.NORMAL);
198 viewHolder.binding.senderName.setTypeface(null, Typeface.NORMAL);
199 } else {
200 viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.BOLD);
201 viewHolder.binding.senderName.setTypeface(null, Typeface.BOLD);
202 }
203 }
204 if (message.getStatus() == Message.STATUS_RECEIVED) {
205 if (conversation.getMode() == Conversation.MODE_MULTI) {
206 viewHolder.binding.senderName.setVisibility(View.VISIBLE);
207 final String dname = UIHelper.getMessageDisplayName(message);
208 final String[] words = dname.split("\\s+");
209 viewHolder.binding.senderName.setText((words.length > 0 ? words[0] : dname) + ':');
210 } else {
211 viewHolder.binding.senderName.setVisibility(View.GONE);
212 }
213 } else if (message.getType() != Message.TYPE_STATUS) {
214 viewHolder.binding.senderName.setVisibility(View.VISIBLE);
215 viewHolder.binding.senderName.setText(activity.getString(R.string.me) + ':');
216 } else {
217 viewHolder.binding.senderName.setVisibility(View.GONE);
218 }
219 }
220
221 final Optional<OngoingRtpSession> ongoingCall;
222 if (conversation.getMode() == Conversational.MODE_MULTI) {
223 ongoingCall = Optional.absent();
224 } else {
225 ongoingCall =
226 activity.xmppConnectionService
227 .getJingleConnectionManager()
228 .getOngoingRtpConnection(conversation.getContact());
229 }
230
231 if (ongoingCall.isPresent()) {
232 viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
233 final int ic_ongoing_call =
234 activity.getThemeResource(
235 R.attr.ic_ongoing_call_hint, R.drawable.ic_phone_in_talk_black_18dp);
236 viewHolder.binding.notificationStatus.setImageResource(ic_ongoing_call);
237 } else {
238 final long muted_till =
239 conversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
240 if (muted_till == Long.MAX_VALUE) {
241 viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
242 int ic_notifications_off =
243 activity.getThemeResource(
244 R.attr.icon_notifications_off,
245 R.drawable.ic_notifications_off_black_24dp);
246 viewHolder.binding.notificationStatus.setImageResource(ic_notifications_off);
247 } else if (muted_till >= System.currentTimeMillis()) {
248 viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
249 int ic_notifications_paused =
250 activity.getThemeResource(
251 R.attr.icon_notifications_paused,
252 R.drawable.ic_notifications_paused_black_24dp);
253 viewHolder.binding.notificationStatus.setImageResource(ic_notifications_paused);
254 } else if (conversation.alwaysNotify()) {
255 viewHolder.binding.notificationStatus.setVisibility(View.GONE);
256 } else {
257 viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
258 int ic_notifications_none =
259 activity.getThemeResource(
260 R.attr.icon_notifications_none,
261 R.drawable.ic_notifications_none_black_24dp);
262 viewHolder.binding.notificationStatus.setImageResource(ic_notifications_none);
263 }
264 }
265
266 long timestamp;
267 if (draft != null) {
268 timestamp = draft.getTimestamp();
269 } else {
270 timestamp = conversation.getLatestMessage().getTimeSent();
271 }
272 viewHolder.binding.pinnedOnTop.setVisibility(
273 conversation.getBooleanAttribute(Conversation.ATTRIBUTE_PINNED_ON_TOP, false)
274 ? View.VISIBLE
275 : View.GONE);
276 viewHolder.binding.conversationLastupdate.setText(
277 UIHelper.readableTimeDifference(activity, timestamp));
278 AvatarWorkerTask.loadAvatar(
279 conversation,
280 viewHolder.binding.conversationImage,
281 R.dimen.avatar_on_conversation_overview);
282 viewHolder.itemView.setOnClickListener(v -> listener.onConversationClick(v, conversation));
283 }
284
285 @Override
286 public int getItemCount() {
287 return conversations.size();
288 }
289
290 public void setConversationClickListener(OnConversationClickListener listener) {
291 this.listener = listener;
292 }
293
294 public void insert(Conversation c, int position) {
295 conversations.add(position, c);
296 notifyDataSetChanged();
297 }
298
299 public void remove(Conversation conversation, int position) {
300 conversations.remove(conversation);
301 notifyItemRemoved(position);
302 }
303
304 public interface OnConversationClickListener {
305 void onConversationClick(View view, Conversation conversation);
306 }
307
308 static class ConversationViewHolder extends RecyclerView.ViewHolder {
309 private final ConversationListRowBinding binding;
310
311 private ConversationViewHolder(ConversationListRowBinding binding) {
312 super(binding.getRoot());
313 this.binding = binding;
314 binding.getRoot().setLongClickable(true);
315 }
316 }
317}