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