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