Signal-Android/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListAdapter.java

274 lines
9.5 KiB
Java

package org.thoughtcrime.securesms.conversationlist;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.paging.PagedListAdapter;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.BindableConversationListItem;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.util.CachedInflater;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
class ConversationListAdapter extends PagedListAdapter<Conversation, RecyclerView.ViewHolder> {
private static final int TYPE_THREAD = 1;
private static final int TYPE_ACTION = 2;
private static final int TYPE_PLACEHOLDER = 3;
private static final int TYPE_HEADER = 4;
private enum Payload {
TYPING_INDICATOR,
SELECTION
}
private final GlideRequests glideRequests;
private final OnConversationClickListener onConversationClickListener;
private final Map<Long, Conversation> batchSet = Collections.synchronizedMap(new HashMap<>());
private boolean batchMode = false;
private final Set<Long> typingSet = new HashSet<>();
protected ConversationListAdapter(@NonNull GlideRequests glideRequests,
@NonNull OnConversationClickListener onConversationClickListener)
{
super(new ConversationDiffCallback());
this.glideRequests = glideRequests;
this.onConversationClickListener = onConversationClickListener;
}
@Override
public @NonNull RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == TYPE_ACTION) {
ConversationViewHolder holder = new ConversationViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.conversation_list_item_action, parent, false));
holder.itemView.setOnClickListener(v -> {
if (holder.getAdapterPosition() != RecyclerView.NO_POSITION) {
onConversationClickListener.onShowArchiveClick();
}
});
return holder;
} else if (viewType == TYPE_THREAD) {
ConversationViewHolder holder = new ConversationViewHolder(CachedInflater.from(parent.getContext())
.inflate(R.layout.conversation_list_item_view, parent, false));
holder.itemView.setOnClickListener(v -> {
int position = holder.getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
onConversationClickListener.onConversationClick(getItem(position));
}
});
holder.itemView.setOnLongClickListener(v -> {
int position = holder.getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
return onConversationClickListener.onConversationLongClick(getItem(position));
}
return false;
});
return holder;
} else if (viewType == TYPE_PLACEHOLDER) {
View v = new FrameLayout(parent.getContext());
v.setLayoutParams(new FrameLayout.LayoutParams(1, ViewUtil.dpToPx(100)));
return new PlaceholderViewHolder(v);
} else if (viewType == TYPE_HEADER) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.conversation_list_item_header, parent, false);
return new HeaderViewHolder(v);
} else {
throw new IllegalStateException("Unknown type! " + viewType);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position);
} else if (holder instanceof ConversationViewHolder) {
for (Object payloadObject : payloads) {
if (payloadObject instanceof Payload) {
Payload payload = (Payload) payloadObject;
if (payload == Payload.SELECTION) {
((ConversationViewHolder) holder).getConversationListItem().setBatchMode(batchMode);
} else {
((ConversationViewHolder) holder).getConversationListItem().updateTypingIndicator(typingSet);
}
}
}
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == TYPE_ACTION || holder.getItemViewType() == TYPE_THREAD) {
ConversationViewHolder casted = (ConversationViewHolder) holder;
Conversation conversation = Objects.requireNonNull(getItem(position));
casted.getConversationListItem().bind(conversation.getThreadRecord(),
glideRequests,
Locale.getDefault(),
typingSet,
getBatchSelectionIds(),
batchMode);
} else if (holder.getItemViewType() == TYPE_HEADER) {
HeaderViewHolder casted = (HeaderViewHolder) holder;
Conversation conversation = Objects.requireNonNull(getItem(position));
switch (conversation.getType()) {
case PINNED_HEADER:
casted.headerText.setText(R.string.conversation_list__pinned);
break;
case UNPINNED_HEADER:
casted.headerText.setText(R.string.conversation_list__chats);
break;
default:
throw new IllegalArgumentException();
}
}
}
@Override
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
if (holder instanceof ConversationViewHolder) {
((ConversationViewHolder) holder).getConversationListItem().unbind();
}
}
void setTypingThreads(@NonNull Set<Long> typingThreadSet) {
this.typingSet.clear();
this.typingSet.addAll(typingThreadSet);
notifyItemRangeChanged(0, getItemCount(), Payload.TYPING_INDICATOR);
}
void toggleConversationInBatchSet(@NonNull Conversation conversation) {
if (batchSet.containsKey(conversation.getThreadRecord().getThreadId())) {
batchSet.remove(conversation.getThreadRecord().getThreadId());
} else if (conversation.getThreadRecord().getThreadId() != -1) {
batchSet.put(conversation.getThreadRecord().getThreadId(), conversation);
}
notifyItemRangeChanged(0, getItemCount(), Payload.SELECTION);
}
Collection<Conversation> getBatchSelection() {
return batchSet.values();
}
@Override
public int getItemViewType(int position) {
Conversation conversation = getItem(position);
if (conversation == null) {
return TYPE_PLACEHOLDER;
}
switch (conversation.getType()) {
case PINNED_HEADER:
case UNPINNED_HEADER:
return TYPE_HEADER;
case ARCHIVED_FOOTER:
return TYPE_ACTION;
case THREAD:
return TYPE_THREAD;
default:
throw new IllegalArgumentException();
}
}
@NonNull Set<Long> getBatchSelectionIds() {
return batchSet.keySet();
}
void selectAllThreads() {
for (int i = 0; i < super.getItemCount(); i++) {
Conversation conversation = getItem(i);
if (conversation != null && conversation.getThreadRecord().getThreadId() >= 0) {
batchSet.put(conversation.getThreadRecord().getThreadId(), conversation);
}
}
notifyItemRangeChanged(0, getItemCount(), Payload.SELECTION);
}
void initializeBatchMode(boolean toggle) {
this.batchMode = toggle;
unselectAllThreads();
}
private void unselectAllThreads() {
batchSet.clear();
notifyItemRangeChanged(0, getItemCount(), Payload.SELECTION);
}
static final class ConversationViewHolder extends RecyclerView.ViewHolder {
private final BindableConversationListItem conversationListItem;
ConversationViewHolder(@NonNull View itemView) {
super(itemView);
conversationListItem = (BindableConversationListItem) itemView;
}
public BindableConversationListItem getConversationListItem() {
return conversationListItem;
}
}
private static final class ConversationDiffCallback extends DiffUtil.ItemCallback<Conversation> {
@Override
public boolean areItemsTheSame(@NonNull Conversation oldItem, @NonNull Conversation newItem) {
return oldItem.getThreadRecord().getThreadId() == newItem.getThreadRecord().getThreadId();
}
@Override
public boolean areContentsTheSame(@NonNull Conversation oldItem, @NonNull Conversation newItem) {
return oldItem.equals(newItem);
}
}
private static class PlaceholderViewHolder extends RecyclerView.ViewHolder {
PlaceholderViewHolder(@NonNull View itemView) {
super(itemView);
}
}
static class HeaderViewHolder extends RecyclerView.ViewHolder {
private TextView headerText;
public HeaderViewHolder(@NonNull View itemView) {
super(itemView);
headerText = (TextView) itemView;
}
}
interface OnConversationClickListener {
void onConversationClick(Conversation conversation);
boolean onConversationLongClick(Conversation conversation);
void onShowArchiveClick();
}
}