package awais.instagrabber.adapters; import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.AdapterListUpdateCallback; import androidx.recyclerview.widget.AsyncDifferConfig; import androidx.recyclerview.widget.AsyncListDiffer; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.RecyclerView; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.function.Function; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemActionLogViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemAnimatedMediaViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemDefaultViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemLikeViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemLinkViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemMediaShareViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemMediaViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemPlaceholderViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemProfileViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemRavenMediaViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemReelShareViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemStoryShareViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemTextViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemVideoCallEventViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemVoiceMediaViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemXmaViewHolder; import awais.instagrabber.customviews.emoji.Emoji; import awais.instagrabber.databinding.LayoutDmActionLogBinding; import awais.instagrabber.databinding.LayoutDmAnimatedMediaBinding; import awais.instagrabber.databinding.LayoutDmBaseBinding; import awais.instagrabber.databinding.LayoutDmHeaderBinding; import awais.instagrabber.databinding.LayoutDmLikeBinding; import awais.instagrabber.databinding.LayoutDmLinkBinding; import awais.instagrabber.databinding.LayoutDmMediaBinding; import awais.instagrabber.databinding.LayoutDmMediaShareBinding; import awais.instagrabber.databinding.LayoutDmProfileBinding; import awais.instagrabber.databinding.LayoutDmRavenMediaBinding; import awais.instagrabber.databinding.LayoutDmReelShareBinding; import awais.instagrabber.databinding.LayoutDmStoryShareBinding; import awais.instagrabber.databinding.LayoutDmTextBinding; import awais.instagrabber.databinding.LayoutDmVoiceMediaBinding; import awais.instagrabber.models.enums.DirectItemType; import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare; import awais.instagrabber.repositories.responses.directmessages.DirectThread; public final class DirectItemsAdapter extends RecyclerView.Adapter { private static final String TAG = DirectItemsAdapter.class.getSimpleName(); private List items; private DirectThread thread; private DirectItemViewHolder selectedViewHolder; private final User currentUser; private final DirectItemCallback callback; private final AsyncListDiffer differ; private final DirectItemInternalLongClickListener longClickListener; private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() { @Override public boolean areItemsTheSame(@NonNull final DirectItemOrHeader oldItem, @NonNull final DirectItemOrHeader newItem) { final boolean bothHeaders = oldItem.isHeader() && newItem.isHeader(); final boolean bothItems = !oldItem.isHeader() && !newItem.isHeader(); boolean areSameType = bothHeaders || bothItems; if (!areSameType) return false; if (bothHeaders) { return oldItem.date.equals(newItem.date); } if (oldItem.item != null && newItem.item != null) { String oldClientContext = oldItem.item.getClientContext(); if (oldClientContext == null) { oldClientContext = oldItem.item.getItemId(); } String newClientContext = newItem.item.getClientContext(); if (newClientContext == null) { newClientContext = newItem.item.getItemId(); } return oldClientContext.equals(newClientContext); } return false; } @Override public boolean areContentsTheSame(@NonNull final DirectItemOrHeader oldItem, @NonNull final DirectItemOrHeader newItem) { final boolean bothHeaders = oldItem.isHeader() && newItem.isHeader(); final boolean bothItems = !oldItem.isHeader() && !newItem.isHeader(); boolean areSameType = bothHeaders || bothItems; if (!areSameType) return false; if (bothHeaders) { return oldItem.date.equals(newItem.date); } final boolean timestampEqual = oldItem.item.getTimestamp() == newItem.item.getTimestamp(); final boolean bothPending = oldItem.item.isPending() == newItem.item.isPending(); final boolean reactionSame = Objects.equals(oldItem.item.getReactions(), newItem.item.getReactions()); return timestampEqual && bothPending && reactionSame; } }; public DirectItemsAdapter(@NonNull final User currentUser, @NonNull final DirectThread thread, @NonNull final DirectItemCallback callback, @NonNull final DirectItemLongClickListener itemLongClickListener) { this.currentUser = currentUser; this.thread = thread; this.callback = callback; differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), new AsyncDifferConfig.Builder<>(diffCallback).build()); longClickListener = (position, viewHolder) -> { if (selectedViewHolder != null) { selectedViewHolder.setSelected(false); } selectedViewHolder = viewHolder; viewHolder.setSelected(true); itemLongClickListener.onLongClick(position); }; } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) { final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); if (type == -1) { // header return new HeaderViewHolder(LayoutDmHeaderBinding.inflate(layoutInflater, parent, false)); } final LayoutDmBaseBinding baseBinding = LayoutDmBaseBinding.inflate(layoutInflater, parent, false); final DirectItemType directItemType = DirectItemType.Companion.getId(type); final DirectItemViewHolder itemViewHolder = getItemViewHolder(layoutInflater, baseBinding, directItemType); itemViewHolder.setLongClickListener(longClickListener); return itemViewHolder; } @NonNull private DirectItemViewHolder getItemViewHolder(final LayoutInflater layoutInflater, final LayoutDmBaseBinding baseBinding, @NonNull final DirectItemType directItemType) { switch (directItemType) { case TEXT: { final LayoutDmTextBinding binding = LayoutDmTextBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemTextViewHolder(baseBinding, binding, currentUser, thread, callback); } case LIKE: { final LayoutDmLikeBinding binding = LayoutDmLikeBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemLikeViewHolder(baseBinding, binding, currentUser, thread, callback); } case LINK: { final LayoutDmLinkBinding binding = LayoutDmLinkBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemLinkViewHolder(baseBinding, binding, currentUser, thread, callback); } case ACTION_LOG: { final LayoutDmActionLogBinding binding = LayoutDmActionLogBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemActionLogViewHolder(baseBinding, binding, currentUser, thread, callback); } case VIDEO_CALL_EVENT: { final LayoutDmActionLogBinding binding = LayoutDmActionLogBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemVideoCallEventViewHolder(baseBinding, binding, currentUser, thread, callback); } case PLACEHOLDER: { final LayoutDmStoryShareBinding binding = LayoutDmStoryShareBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemPlaceholderViewHolder(baseBinding, binding, currentUser, thread, callback); } case ANIMATED_MEDIA: { final LayoutDmAnimatedMediaBinding binding = LayoutDmAnimatedMediaBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemAnimatedMediaViewHolder(baseBinding, binding, currentUser, thread, callback); } case VOICE_MEDIA: { final LayoutDmVoiceMediaBinding binding = LayoutDmVoiceMediaBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemVoiceMediaViewHolder(baseBinding, binding, currentUser, thread, callback); } case LOCATION: case PROFILE: { final LayoutDmProfileBinding binding = LayoutDmProfileBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemProfileViewHolder(baseBinding, binding, currentUser, thread, callback); } case MEDIA: { final LayoutDmMediaBinding binding = LayoutDmMediaBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemMediaViewHolder(baseBinding, binding, currentUser, thread, callback); } case CLIP: case FELIX_SHARE: case MEDIA_SHARE: { final LayoutDmMediaShareBinding binding = LayoutDmMediaShareBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemMediaShareViewHolder(baseBinding, binding, currentUser, thread, callback); } case STORY_SHARE: { final LayoutDmStoryShareBinding binding = LayoutDmStoryShareBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemStoryShareViewHolder(baseBinding, binding, currentUser, thread, callback); } case REEL_SHARE: { final LayoutDmReelShareBinding binding = LayoutDmReelShareBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemReelShareViewHolder(baseBinding, binding, currentUser, thread, callback); } case RAVEN_MEDIA: { final LayoutDmRavenMediaBinding binding = LayoutDmRavenMediaBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemRavenMediaViewHolder(baseBinding, binding, currentUser, thread, callback); } case XMA: { final LayoutDmAnimatedMediaBinding binding = LayoutDmAnimatedMediaBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemXmaViewHolder(baseBinding, binding, currentUser, thread, callback); } case UNKNOWN: default: { final LayoutDmTextBinding binding = LayoutDmTextBinding.inflate(layoutInflater, baseBinding.message, false); return new DirectItemDefaultViewHolder(baseBinding, binding, currentUser, thread, callback); } } } @Override public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) { final DirectItemOrHeader itemOrHeader = getItem(position); if (itemOrHeader.isHeader()) { ((HeaderViewHolder) holder).bind(itemOrHeader.date); return; } if (thread == null) return; ((DirectItemViewHolder) holder).bind(position, itemOrHeader.item); } protected DirectItemOrHeader getItem(int position) { return differ.getCurrentList().get(position); } @Override public int getItemCount() { return differ.getCurrentList().size(); } @Override public int getItemViewType(final int position) { final DirectItemOrHeader itemOrHeader = getItem(position); if (itemOrHeader.isHeader()) { return -1; } final DirectItemType itemType = itemOrHeader.item.getItemType(); if (itemType == null) { return 0; } return itemType.getId(); } @Override public long getItemId(final int position) { final DirectItemOrHeader itemOrHeader = getItem(position); if (itemOrHeader.isHeader()) { return itemOrHeader.date.hashCode(); } if (itemOrHeader.item.getClientContext() == null) { return itemOrHeader.item.getItemId().hashCode(); } return itemOrHeader.item.getClientContext().hashCode(); } public void setThread(final DirectThread thread) { if (thread == null) return; this.thread = thread; // notifyDataSetChanged(); } public void submitList(@Nullable final List list) { if (list == null) { differ.submitList(null); return; } differ.submitList(sectionAndSort(list)); this.items = list; } public void submitList(@Nullable final List list, @Nullable final Runnable commitCallback) { if (list == null) { differ.submitList(null, commitCallback); return; } differ.submitList(sectionAndSort(list), commitCallback); this.items = list; } private List sectionAndSort(final List list) { final List itemOrHeaders = new ArrayList<>(); LocalDate prevSectionDate = null; for (int i = 0; i < list.size(); i++) { final DirectItem item = list.get(i); if (item == null || item.getDate() == null) continue; final DirectItemOrHeader prev = itemOrHeaders.isEmpty() ? null : itemOrHeaders.get(itemOrHeaders.size() - 1); if (prev != null && prev.item != null && prev.item.getDate() != null && prev.item.getDate().toLocalDate().isEqual(item.getDate().toLocalDate())) { // just add item final DirectItemOrHeader itemOrHeader = new DirectItemOrHeader(); itemOrHeader.item = item; itemOrHeaders.add(itemOrHeader); if (i == list.size() - 1) { // add header final DirectItemOrHeader itemOrHeader2 = new DirectItemOrHeader(); itemOrHeader2.date = prevSectionDate; itemOrHeaders.add(itemOrHeader2); } continue; } if (prevSectionDate != null) { // add header final DirectItemOrHeader itemOrHeader = new DirectItemOrHeader(); itemOrHeader.date = prevSectionDate; itemOrHeaders.add(itemOrHeader); } // Add item final DirectItemOrHeader itemOrHeader = new DirectItemOrHeader(); itemOrHeader.item = item; itemOrHeaders.add(itemOrHeader); prevSectionDate = item.getDate().toLocalDate(); } return itemOrHeaders; } public List getList() { return differ.getCurrentList(); } public List getItems() { return items; } @Override public void onViewRecycled(@NonNull final RecyclerView.ViewHolder holder) { if (holder instanceof DirectItemViewHolder) { ((DirectItemViewHolder) holder).cleanup(); } } @Override public void onViewDetachedFromWindow(@NonNull final RecyclerView.ViewHolder holder) { if (holder instanceof DirectItemViewHolder) { ((DirectItemViewHolder) holder).cleanup(); } } public DirectThread getThread() { return thread; } public static class DirectItemOrHeader { LocalDate date; public DirectItem item; public boolean isHeader() { return date != null; } @NonNull @Override public String toString() { return "DirectItemOrHeader{" + "date=" + date + ", item=" + (item != null ? item.getItemType() : null) + '}'; } } public static class HeaderViewHolder extends RecyclerView.ViewHolder { private final LayoutDmHeaderBinding binding; public HeaderViewHolder(@NonNull final LayoutDmHeaderBinding binding) { super(binding.getRoot()); this.binding = binding; } public void bind(final LocalDate date) { if (date == null) { binding.header.setText(""); return; } final DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT); binding.header.setText(dateFormatter.format(date)); } } public interface DirectItemCallback { void onHashtagClick(String hashtag); void onMentionClick(String mention); void onLocationClick(long locationId); void onURLClick(String url); void onEmailClick(String email); void onMediaClick(Media media, int index); void onStoryClick(DirectItemStoryShare storyShare); void onReaction(DirectItem item, Emoji emoji); void onReactionClick(DirectItem item, int position); void onOptionSelect(DirectItem item, @IdRes int itemId, final Function callback); void onAddReactionListener(DirectItem item); } public interface DirectItemInternalLongClickListener { void onLongClick(int position, DirectItemViewHolder viewHolder); } public interface DirectItemLongClickListener { void onLongClick(int position); } }