ConversationAdapter.java

  1package eu.siacs.conversations.ui.adapter;
  2
  3import android.content.Context;
  4import android.content.res.Resources;
  5import android.graphics.Bitmap;
  6import android.graphics.Typeface;
  7import android.graphics.drawable.BitmapDrawable;
  8import android.graphics.drawable.Drawable;
  9import android.os.AsyncTask;
 10import android.support.annotation.NonNull;
 11import android.support.v7.widget.RecyclerView;
 12import android.util.Pair;
 13import android.view.LayoutInflater;
 14import android.view.View;
 15import android.view.ViewGroup;
 16import android.widget.FrameLayout;
 17import android.widget.ImageView;
 18import android.widget.TextView;
 19
 20import java.lang.ref.WeakReference;
 21import java.util.List;
 22import java.util.concurrent.RejectedExecutionException;
 23
 24import eu.siacs.conversations.R;
 25import eu.siacs.conversations.entities.Conversation;
 26import eu.siacs.conversations.entities.Message;
 27import eu.siacs.conversations.entities.Transferable;
 28import eu.siacs.conversations.ui.ConversationFragment;
 29import eu.siacs.conversations.ui.XmppActivity;
 30import eu.siacs.conversations.ui.util.StyledAttributes;
 31import eu.siacs.conversations.ui.widget.UnreadCountCustomView;
 32import eu.siacs.conversations.utils.EmojiWrapper;
 33import eu.siacs.conversations.utils.IrregularUnicodeDetector;
 34import eu.siacs.conversations.utils.UIHelper;
 35import rocks.xmpp.addr.Jid;
 36
 37public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapter.ConversationViewHolder> {
 38
 39	private XmppActivity activity;
 40	private List<Conversation> conversations;
 41	private OnConversationClickListener listener;
 42
 43	public ConversationAdapter(XmppActivity activity, List<Conversation> conversations) {
 44		this.activity = activity;
 45		this.conversations = conversations;
 46	}
 47
 48	private static boolean cancelPotentialWork(Conversation conversation, ImageView imageView) {
 49		final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
 50
 51		if (bitmapWorkerTask != null) {
 52			final Conversation oldConversation = bitmapWorkerTask.conversation;
 53			if (oldConversation == null || conversation != oldConversation) {
 54				bitmapWorkerTask.cancel(true);
 55			} else {
 56				return false;
 57			}
 58		}
 59		return true;
 60	}
 61
 62	private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
 63		if (imageView != null) {
 64			final Drawable drawable = imageView.getDrawable();
 65			if (drawable instanceof AsyncDrawable) {
 66				final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
 67				return asyncDrawable.getBitmapWorkerTask();
 68			}
 69		}
 70		return null;
 71	}
 72
 73	@NonNull
 74	@Override
 75	public ConversationViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
 76		LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 77		View view = inflater.inflate(R.layout.conversation_list_row, parent, false);
 78		return ConversationViewHolder.get(view);
 79	}
 80
 81	@Override
 82	public void onBindViewHolder(@NonNull ConversationViewHolder viewHolder, int position) {
 83		Conversation conversation = conversations.get(position);
 84		if (conversation == null) {
 85			return;
 86		}
 87		CharSequence name = conversation.getName();
 88		if (name instanceof Jid) {
 89			viewHolder.name.setText(IrregularUnicodeDetector.style(activity, (Jid) name));
 90		} else {
 91			viewHolder.name.setText(EmojiWrapper.transform(name));
 92		}
 93
 94		if (conversation == ConversationFragment.getConversation(activity)) {
 95			viewHolder.frame.setBackgroundColor(StyledAttributes.getColor(activity,R.attr.color_background_tertiary));
 96		} else {
 97			viewHolder.frame.setBackgroundColor(StyledAttributes.getColor(activity,R.attr.color_background_primary));
 98		}
 99
100		Message message = conversation.getLatestMessage();
101		final int unreadCount = conversation.unreadCount();
102		final boolean isRead = conversation.isRead();
103		final Conversation.Draft draft = isRead ? conversation.getDraft() : null;
104		if (unreadCount > 0) {
105			viewHolder.unreadCount.setVisibility(View.VISIBLE);
106			viewHolder.unreadCount.setUnreadCount(unreadCount);
107		} else {
108			viewHolder.unreadCount.setVisibility(View.GONE);
109		}
110
111		if (isRead) {
112			viewHolder.name.setTypeface(null, Typeface.NORMAL);
113		} else {
114			viewHolder.name.setTypeface(null, Typeface.BOLD);
115		}
116
117		if (draft != null) {
118			viewHolder.lastMessageIcon.setVisibility(View.GONE);
119			viewHolder.lastMessage.setText(EmojiWrapper.transform(draft.getMessage()));
120			viewHolder.sender.setText(R.string.draft);
121			viewHolder.sender.setVisibility(View.VISIBLE);
122			viewHolder.lastMessage.setTypeface(null, Typeface.NORMAL);
123			viewHolder.sender.setTypeface(null, Typeface.ITALIC);
124		} else {
125			final boolean fileAvailable = !message.isDeleted();
126			final boolean showPreviewText;
127			if (fileAvailable && (message.isFileOrImage() || message.treatAsDownloadable() || message.isGeoUri())) {
128				final int imageResource;
129				if (message.isGeoUri()) {
130					imageResource = activity.getThemeResource(R.attr.ic_attach_location, R.drawable.ic_attach_location);
131					showPreviewText = false;
132				} else {
133					final String mime = message.getMimeType();
134					switch (mime == null ? "" : mime.split("/")[0]) {
135						case "image":
136							imageResource = activity.getThemeResource(R.attr.ic_attach_photo, R.drawable.ic_attach_photo);
137							showPreviewText = false;
138							break;
139						case "video":
140							imageResource = activity.getThemeResource(R.attr.ic_attach_videocam, R.drawable.ic_attach_videocam);
141							showPreviewText = false;
142							break;
143						case "audio":
144							imageResource = activity.getThemeResource(R.attr.ic_attach_record, R.drawable.ic_attach_record);
145							showPreviewText = false;
146							break;
147						default:
148							imageResource = activity.getThemeResource(R.attr.ic_attach_document, R.drawable.ic_attach_document);
149							showPreviewText = true;
150							break;
151					}
152				}
153				viewHolder.lastMessageIcon.setImageResource(imageResource);
154				viewHolder.lastMessageIcon.setVisibility(View.VISIBLE);
155			} else {
156				viewHolder.lastMessageIcon.setVisibility(View.GONE);
157				showPreviewText = true;
158			}
159			final Pair<CharSequence, Boolean> preview = UIHelper.getMessagePreview(activity, message, viewHolder.lastMessage.getCurrentTextColor());
160			if (showPreviewText) {
161				viewHolder.lastMessage.setText(EmojiWrapper.transform(UIHelper.shorten(preview.first)));
162			} else {
163				viewHolder.lastMessageIcon.setContentDescription(preview.first);
164			}
165			viewHolder.lastMessage.setVisibility(showPreviewText ? View.VISIBLE : View.GONE);
166			if (preview.second) {
167				if (isRead) {
168					viewHolder.lastMessage.setTypeface(null, Typeface.ITALIC);
169					viewHolder.sender.setTypeface(null, Typeface.NORMAL);
170				} else {
171					viewHolder.lastMessage.setTypeface(null, Typeface.BOLD_ITALIC);
172					viewHolder.sender.setTypeface(null, Typeface.BOLD);
173				}
174			} else {
175				if (isRead) {
176					viewHolder.lastMessage.setTypeface(null, Typeface.NORMAL);
177					viewHolder.sender.setTypeface(null, Typeface.NORMAL);
178				} else {
179					viewHolder.lastMessage.setTypeface(null, Typeface.BOLD);
180					viewHolder.sender.setTypeface(null, Typeface.BOLD);
181				}
182			}
183			if (message.getStatus() == Message.STATUS_RECEIVED) {
184				if (conversation.getMode() == Conversation.MODE_MULTI) {
185					viewHolder.sender.setVisibility(View.VISIBLE);
186					viewHolder.sender.setText(UIHelper.getMessageDisplayName(message).split("\\s+")[0] + ':');
187				} else {
188					viewHolder.sender.setVisibility(View.GONE);
189				}
190			} else if (message.getType() != Message.TYPE_STATUS) {
191				viewHolder.sender.setVisibility(View.VISIBLE);
192				viewHolder.sender.setText(activity.getString(R.string.me) + ':');
193			} else {
194				viewHolder.sender.setVisibility(View.GONE);
195			}
196		}
197
198		long muted_till = conversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
199		if (muted_till == Long.MAX_VALUE) {
200			viewHolder.notificationIcon.setVisibility(View.VISIBLE);
201			int ic_notifications_off = activity.getThemeResource(R.attr.icon_notifications_off, R.drawable.ic_notifications_off_black_24dp);
202			viewHolder.notificationIcon.setImageResource(ic_notifications_off);
203		} else if (muted_till >= System.currentTimeMillis()) {
204			viewHolder.notificationIcon.setVisibility(View.VISIBLE);
205			int ic_notifications_paused = activity.getThemeResource(R.attr.icon_notifications_paused, R.drawable.ic_notifications_paused_black_24dp);
206			viewHolder.notificationIcon.setImageResource(ic_notifications_paused);
207		} else if (conversation.alwaysNotify()) {
208			viewHolder.notificationIcon.setVisibility(View.GONE);
209		} else {
210			viewHolder.notificationIcon.setVisibility(View.VISIBLE);
211			int ic_notifications_none = activity.getThemeResource(R.attr.icon_notifications_none, R.drawable.ic_notifications_none_black_24dp);
212			viewHolder.notificationIcon.setImageResource(ic_notifications_none);
213		}
214
215		long timestamp;
216		if (draft != null) {
217			timestamp = draft.getTimestamp();
218		} else {
219			timestamp = conversation.getLatestMessage().getTimeSent();
220		}
221		viewHolder.timestamp.setText(UIHelper.readableTimeDifference(activity, timestamp));
222		loadAvatar(conversation, viewHolder.avatar);
223		viewHolder.itemView.setOnClickListener(v -> listener.onConversationClick(v,conversation));
224	}
225
226	@Override
227	public int getItemCount() {
228		return conversations.size();
229	}
230
231	public void setConversationClickListener(OnConversationClickListener listener) {
232		this.listener = listener;
233	}
234
235	private void loadAvatar(Conversation conversation, ImageView imageView) {
236		if (cancelPotentialWork(conversation, imageView)) {
237			final Bitmap bm = activity.avatarService().get(conversation, activity.getPixel(56), true);
238			if (bm != null) {
239				cancelPotentialWork(conversation, imageView);
240				imageView.setImageBitmap(bm);
241				imageView.setBackgroundColor(0x00000000);
242			} else {
243				imageView.setBackgroundColor(UIHelper.getColorForName(conversation.getName().toString()));
244				imageView.setImageDrawable(null);
245				final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
246				final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
247				imageView.setImageDrawable(asyncDrawable);
248				try {
249					task.execute(conversation);
250				} catch (final RejectedExecutionException ignored) {
251				}
252			}
253		}
254	}
255
256	public void insert(Conversation c, int position) {
257		conversations.add(position,c);
258		notifyDataSetChanged();
259	}
260
261	public void remove(Conversation conversation,int position) {
262		conversations.remove(conversation);
263		notifyItemRemoved(position);
264	}
265
266	public static class ConversationViewHolder extends RecyclerView.ViewHolder {
267		private TextView name;
268		private TextView lastMessage;
269		private ImageView lastMessageIcon;
270		private TextView sender;
271		private TextView timestamp;
272		private ImageView notificationIcon;
273		private UnreadCountCustomView unreadCount;
274		private ImageView avatar;
275		private FrameLayout frame;
276
277		private ConversationViewHolder(View view) {
278			super(view);
279		}
280
281		public static ConversationViewHolder get(View layout) {
282			ConversationViewHolder conversationViewHolder = (ConversationViewHolder) layout.getTag();
283			if (conversationViewHolder == null) {
284				conversationViewHolder = new ConversationViewHolder(layout);
285				conversationViewHolder.frame = layout.findViewById(R.id.frame);
286				conversationViewHolder.name = layout.findViewById(R.id.conversation_name);
287				conversationViewHolder.lastMessage = layout.findViewById(R.id.conversation_lastmsg);
288				conversationViewHolder.lastMessageIcon = layout.findViewById(R.id.conversation_lastmsg_img);
289				conversationViewHolder.timestamp = layout.findViewById(R.id.conversation_lastupdate);
290				conversationViewHolder.sender = layout.findViewById(R.id.sender_name);
291				conversationViewHolder.notificationIcon = layout.findViewById(R.id.notification_status);
292				conversationViewHolder.unreadCount = layout.findViewById(R.id.unread_count);
293				conversationViewHolder.avatar = layout.findViewById(R.id.conversation_image);
294				layout.setTag(conversationViewHolder);
295			}
296			return conversationViewHolder;
297		}
298	}
299
300	static class AsyncDrawable extends BitmapDrawable {
301		private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
302
303		public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
304			super(res, bitmap);
305			bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
306		}
307
308		public BitmapWorkerTask getBitmapWorkerTask() {
309			return bitmapWorkerTaskReference.get();
310		}
311	}
312
313	class BitmapWorkerTask extends AsyncTask<Conversation, Void, Bitmap> {
314		private final WeakReference<ImageView> imageViewReference;
315		private Conversation conversation = null;
316
317		public BitmapWorkerTask(ImageView imageView) {
318			imageViewReference = new WeakReference<>(imageView);
319		}
320
321		@Override
322		protected Bitmap doInBackground(Conversation... params) {
323			this.conversation = params[0];
324			return activity.avatarService().get(this.conversation, activity.getPixel(56), isCancelled());
325		}
326
327		@Override
328		protected void onPostExecute(Bitmap bitmap) {
329			if (bitmap != null && !isCancelled()) {
330				final ImageView imageView = imageViewReference.get();
331				if (imageView != null) {
332					imageView.setImageBitmap(bitmap);
333					imageView.setBackgroundColor(0x00000000);
334				}
335			}
336		}
337	}
338
339	public interface OnConversationClickListener {
340		void onConversationClick(View view, Conversation conversation);
341	}
342}