ConversationAdapter.java

  1package eu.siacs.conversations.ui.adapter;
  2
  3import android.content.Context;
  4import android.content.res.Resources;
  5import android.databinding.DataBindingUtil;
  6import android.graphics.Bitmap;
  7import android.graphics.Typeface;
  8import android.graphics.drawable.BitmapDrawable;
  9import android.graphics.drawable.Drawable;
 10import android.os.AsyncTask;
 11import android.support.annotation.NonNull;
 12import android.support.v7.widget.RecyclerView;
 13import android.util.Pair;
 14import android.view.LayoutInflater;
 15import android.view.View;
 16import android.view.ViewGroup;
 17import android.widget.FrameLayout;
 18import android.widget.ImageView;
 19import android.widget.TextView;
 20
 21import java.lang.ref.WeakReference;
 22import java.util.List;
 23import java.util.concurrent.RejectedExecutionException;
 24
 25import eu.siacs.conversations.R;
 26import eu.siacs.conversations.databinding.ConversationListRowBinding;
 27import eu.siacs.conversations.entities.Conversation;
 28import eu.siacs.conversations.entities.Message;
 29import eu.siacs.conversations.entities.Transferable;
 30import eu.siacs.conversations.ui.ConversationFragment;
 31import eu.siacs.conversations.ui.XmppActivity;
 32import eu.siacs.conversations.ui.util.StyledAttributes;
 33import eu.siacs.conversations.ui.widget.UnreadCountCustomView;
 34import eu.siacs.conversations.utils.EmojiWrapper;
 35import eu.siacs.conversations.utils.IrregularUnicodeDetector;
 36import eu.siacs.conversations.utils.UIHelper;
 37import rocks.xmpp.addr.Jid;
 38
 39public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapter.ConversationViewHolder> {
 40
 41    private XmppActivity activity;
 42    private List<Conversation> conversations;
 43    private OnConversationClickListener listener;
 44
 45    public ConversationAdapter(XmppActivity activity, List<Conversation> conversations) {
 46        this.activity = activity;
 47        this.conversations = conversations;
 48    }
 49
 50    private static boolean cancelPotentialWork(Conversation conversation, ImageView imageView) {
 51        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
 52
 53        if (bitmapWorkerTask != null) {
 54            final Conversation oldConversation = bitmapWorkerTask.conversation;
 55            if (oldConversation == null || conversation != oldConversation) {
 56                bitmapWorkerTask.cancel(true);
 57            } else {
 58                return false;
 59            }
 60        }
 61        return true;
 62    }
 63
 64    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
 65        if (imageView != null) {
 66            final Drawable drawable = imageView.getDrawable();
 67            if (drawable instanceof AsyncDrawable) {
 68                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
 69                return asyncDrawable.getBitmapWorkerTask();
 70            }
 71        }
 72        return null;
 73    }
 74
 75    @NonNull
 76    @Override
 77    public ConversationViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
 78        return new ConversationViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.conversation_list_row, parent, false));
 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.binding.conversationName.setText(IrregularUnicodeDetector.style(activity, (Jid) name));
 90        } else {
 91            viewHolder.binding.conversationName.setText(EmojiWrapper.transform(name));
 92        }
 93
 94        if (conversation == ConversationFragment.getConversation(activity)) {
 95            viewHolder.binding.frame.setBackgroundColor(StyledAttributes.getColor(activity, R.attr.color_background_tertiary));
 96        } else {
 97            viewHolder.binding.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.binding.unreadCount.setVisibility(View.VISIBLE);
106            viewHolder.binding.unreadCount.setUnreadCount(unreadCount);
107        } else {
108            viewHolder.binding.unreadCount.setVisibility(View.GONE);
109        }
110
111        if (isRead) {
112            viewHolder.binding.conversationName.setTypeface(null, Typeface.NORMAL);
113        } else {
114            viewHolder.binding.conversationName.setTypeface(null, Typeface.BOLD);
115        }
116
117        if (draft != null) {
118            viewHolder.binding.conversationLastmsgImg.setVisibility(View.GONE);
119            viewHolder.binding.conversationLastmsg.setText(EmojiWrapper.transform(draft.getMessage()));
120            viewHolder.binding.senderName.setText(R.string.draft);
121            viewHolder.binding.senderName.setVisibility(View.VISIBLE);
122            viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.NORMAL);
123            viewHolder.binding.senderName.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.binding.conversationLastmsgImg.setImageResource(imageResource);
154                viewHolder.binding.conversationLastmsgImg.setVisibility(View.VISIBLE);
155            } else {
156                viewHolder.binding.conversationLastmsgImg.setVisibility(View.GONE);
157                showPreviewText = true;
158            }
159            final Pair<CharSequence, Boolean> preview = UIHelper.getMessagePreview(activity, message, viewHolder.binding.conversationLastmsg.getCurrentTextColor());
160            if (showPreviewText) {
161                viewHolder.binding.conversationLastmsg.setText(EmojiWrapper.transform(UIHelper.shorten(preview.first)));
162            } else {
163                viewHolder.binding.conversationLastmsgImg.setContentDescription(preview.first);
164            }
165            viewHolder.binding.conversationLastmsg.setVisibility(showPreviewText ? View.VISIBLE : View.GONE);
166            if (preview.second) {
167                if (isRead) {
168                    viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.ITALIC);
169                    viewHolder.binding.senderName.setTypeface(null, Typeface.NORMAL);
170                } else {
171                    viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.BOLD_ITALIC);
172                    viewHolder.binding.senderName.setTypeface(null, Typeface.BOLD);
173                }
174            } else {
175                if (isRead) {
176                    viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.NORMAL);
177                    viewHolder.binding.senderName.setTypeface(null, Typeface.NORMAL);
178                } else {
179                    viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.BOLD);
180                    viewHolder.binding.senderName.setTypeface(null, Typeface.BOLD);
181                }
182            }
183            if (message.getStatus() == Message.STATUS_RECEIVED) {
184                if (conversation.getMode() == Conversation.MODE_MULTI) {
185                    viewHolder.binding.senderName.setVisibility(View.VISIBLE);
186                    viewHolder.binding.senderName.setText(UIHelper.getMessageDisplayName(message).split("\\s+")[0] + ':');
187                } else {
188                    viewHolder.binding.senderName.setVisibility(View.GONE);
189                }
190            } else if (message.getType() != Message.TYPE_STATUS) {
191                viewHolder.binding.senderName.setVisibility(View.VISIBLE);
192                viewHolder.binding.senderName.setText(activity.getString(R.string.me) + ':');
193            } else {
194                viewHolder.binding.senderName.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.binding.notificationStatus.setVisibility(View.VISIBLE);
201            int ic_notifications_off = activity.getThemeResource(R.attr.icon_notifications_off, R.drawable.ic_notifications_off_black_24dp);
202            viewHolder.binding.notificationStatus.setImageResource(ic_notifications_off);
203        } else if (muted_till >= System.currentTimeMillis()) {
204            viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
205            int ic_notifications_paused = activity.getThemeResource(R.attr.icon_notifications_paused, R.drawable.ic_notifications_paused_black_24dp);
206            viewHolder.binding.notificationStatus.setImageResource(ic_notifications_paused);
207        } else if (conversation.alwaysNotify()) {
208            viewHolder.binding.notificationStatus.setVisibility(View.GONE);
209        } else {
210            viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
211            int ic_notifications_none = activity.getThemeResource(R.attr.icon_notifications_none, R.drawable.ic_notifications_none_black_24dp);
212            viewHolder.binding.notificationStatus.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.binding.conversationLastupdate.setText(UIHelper.readableTimeDifference(activity, timestamp));
222        loadAvatar(conversation, viewHolder.binding.conversationImage);
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 interface OnConversationClickListener {
267        void onConversationClick(View view, Conversation conversation);
268    }
269
270    static class ConversationViewHolder extends RecyclerView.ViewHolder {
271        private final ConversationListRowBinding binding;
272
273        private ConversationViewHolder(ConversationListRowBinding binding) {
274            super(binding.getRoot());
275            this.binding = binding;
276        }
277
278    }
279
280    static class AsyncDrawable extends BitmapDrawable {
281        private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
282
283        AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
284            super(res, bitmap);
285            bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
286        }
287
288        BitmapWorkerTask getBitmapWorkerTask() {
289            return bitmapWorkerTaskReference.get();
290        }
291    }
292
293    static class BitmapWorkerTask extends AsyncTask<Conversation, Void, Bitmap> {
294        private final WeakReference<ImageView> imageViewReference;
295        private Conversation conversation = null;
296
297        BitmapWorkerTask(ImageView imageView) {
298            imageViewReference = new WeakReference<>(imageView);
299        }
300
301
302        @Override
303        protected Bitmap doInBackground(Conversation... params) {
304            this.conversation = params[0];
305            final XmppActivity activity = XmppActivity.find(imageViewReference);
306            if (activity == null) {
307                return null;
308            }
309            return activity.avatarService().get(this.conversation, activity.getPixel(56), isCancelled());
310        }
311
312        @Override
313        protected void onPostExecute(Bitmap bitmap) {
314            if (bitmap != null && !isCancelled()) {
315                final ImageView imageView = imageViewReference.get();
316                if (imageView != null) {
317                    imageView.setImageBitmap(bitmap);
318                    imageView.setBackgroundColor(0x00000000);
319                }
320            }
321        }
322    }
323}