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