Signal-Android/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupMemberListAdapter.java

435 lines
18 KiB
Java

package org.thoughtcrime.securesms.groups.ui;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.LifecycleRecyclerAdapter;
import org.thoughtcrime.securesms.util.LifecycleViewHolder;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberListAdapter.ViewHolder> {
private static final int FULL_MEMBER = 0;
private static final int OWN_INVITE_PENDING = 1;
private static final int OTHER_INVITE_PENDING_COUNT = 2;
private static final int NEW_GROUP_CANDIDATE = 3;
private final List<GroupMemberEntry> data = new ArrayList<>();
private final Set<GroupMemberEntry> selection = new HashSet<>();
private final SelectionChangeListener selectionChangeListener = new SelectionChangeListener();
private final boolean selectable;
@Nullable private AdminActionsListener adminActionsListener;
@Nullable private RecipientClickListener recipientClickListener;
@Nullable private RecipientLongClickListener recipientLongClickListener;
@Nullable private RecipientSelectionChangeListener recipientSelectionChangeListener;
GroupMemberListAdapter(boolean selectable) {
this.selectable = selectable;
}
void updateData(@NonNull List<? extends GroupMemberEntry> recipients) {
if (data.isEmpty()) {
data.addAll(recipients);
notifyDataSetChanged();
} else {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallback(data, recipients));
data.clear();
data.addAll(recipients);
if (!selection.isEmpty()) {
Set<GroupMemberEntry> newSelection = new HashSet<>();
for (GroupMemberEntry entry : recipients) {
if (selection.contains(entry)) {
newSelection.add(entry);
}
}
selection.clear();
selection.addAll(newSelection);
if (recipientSelectionChangeListener != null) {
recipientSelectionChangeListener.onSelectionChanged(selection);
}
}
diffResult.dispatchUpdatesTo(this);
}
}
@Override
public @NonNull ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType) {
case FULL_MEMBER:
return new FullMemberViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.group_recipient_list_item, parent, false),
recipientClickListener,
recipientLongClickListener,
adminActionsListener,
selectionChangeListener);
case OWN_INVITE_PENDING:
return new OwnInvitePendingMemberViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.group_recipient_list_item, parent, false),
recipientClickListener,
recipientLongClickListener,
adminActionsListener,
selectionChangeListener);
case OTHER_INVITE_PENDING_COUNT:
return new UnknownPendingMemberCountViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.group_recipient_list_item, parent, false),
adminActionsListener,
selectionChangeListener);
case NEW_GROUP_CANDIDATE:
return new NewGroupInviteeViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.group_new_candidate_recipient_list_item, parent, false),
recipientClickListener,
recipientLongClickListener,
selectionChangeListener);
default:
throw new AssertionError();
}
}
void setAdminActionsListener(@Nullable AdminActionsListener adminActionsListener) {
this.adminActionsListener = adminActionsListener;
}
void setRecipientClickListener(@Nullable RecipientClickListener recipientClickListener) {
this.recipientClickListener = recipientClickListener;
}
void setRecipientLongClickListener(@Nullable RecipientLongClickListener recipientLongClickListener) {
this.recipientLongClickListener = recipientLongClickListener;
}
void setRecipientSelectionChangeListener(@Nullable RecipientSelectionChangeListener recipientSelectionChangeListener) {
this.recipientSelectionChangeListener = recipientSelectionChangeListener;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
GroupMemberEntry entry = data.get(position);
holder.bind(entry, selection.contains(entry));
}
@Override
public int getItemViewType(int position) {
GroupMemberEntry groupMemberEntry = data.get(position);
if (groupMemberEntry instanceof GroupMemberEntry.FullMember) {
return FULL_MEMBER;
} else if (groupMemberEntry instanceof GroupMemberEntry.PendingMember) {
return OWN_INVITE_PENDING;
} else if (groupMemberEntry instanceof GroupMemberEntry.UnknownPendingMemberCount) {
return OTHER_INVITE_PENDING_COUNT;
} else if (groupMemberEntry instanceof GroupMemberEntry.NewGroupCandidate) {
return NEW_GROUP_CANDIDATE;
}
throw new AssertionError();
}
@Override
public int getItemCount() {
return data.size();
}
static abstract class ViewHolder extends LifecycleViewHolder {
final Context context;
final AvatarImageView avatar;
final TextView recipient;
final CheckBox selected;
final PopupMenuView popupMenu;
final View popupMenuContainer;
final ProgressBar busyProgress;
final View admin;
final SelectionChangeListener selectionChangeListener;
@Nullable final RecipientClickListener recipientClickListener;
@Nullable final AdminActionsListener adminActionsListener;
@Nullable final RecipientLongClickListener recipientLongClickListener;
ViewHolder(@NonNull View itemView,
@Nullable RecipientClickListener recipientClickListener,
@Nullable RecipientLongClickListener recipientLongClickListener,
@Nullable AdminActionsListener adminActionsListener,
@NonNull SelectionChangeListener selectionChangeListener)
{
super(itemView);
this.context = itemView.getContext();
this.avatar = itemView.findViewById(R.id.recipient_avatar);
this.recipient = itemView.findViewById(R.id.recipient_name);
this.selected = itemView.findViewById(R.id.recipient_selected);
this.popupMenu = itemView.findViewById(R.id.popupMenu);
this.popupMenuContainer = itemView.findViewById(R.id.popupMenuProgressContainer);
this.busyProgress = itemView.findViewById(R.id.menuBusyProgress);
this.admin = itemView.findViewById(R.id.admin);
this.recipientClickListener = recipientClickListener;
this.recipientLongClickListener = recipientLongClickListener;
this.adminActionsListener = adminActionsListener;
this.selectionChangeListener = selectionChangeListener;
}
void bindRecipient(@NonNull Recipient recipient) {
String displayName = recipient.isLocalNumber() ? context.getString(R.string.GroupMembersDialog_you)
: recipient.getDisplayName(itemView.getContext());
bindImageAndText(recipient, displayName);
}
void bindImageAndText(@NonNull Recipient recipient, @NonNull String displayText) {
this.recipient.setText(displayText);
this.avatar.setRecipient(recipient);
}
void bindRecipientClick(@NonNull Recipient recipient) {
if (recipient.equals(Recipient.self())) {
this.itemView.setEnabled(false);
return;
}
this.itemView.setEnabled(true);
this.itemView.setOnClickListener(v -> {
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
if (recipientClickListener != null) {
recipientClickListener.onClick(recipient);
}
if (selected != null) {
selectionChangeListener.onSelectionChange(getAdapterPosition(), !selected.isChecked());
}
}
});
this.itemView.setOnLongClickListener(v -> {
if (recipientLongClickListener != null && getAdapterPosition() != RecyclerView.NO_POSITION) {
return recipientLongClickListener.onLongClick(recipient);
}
return false;
});
}
void bind(@NonNull GroupMemberEntry memberEntry, boolean isSelected) {
busyProgress.setVisibility(View.GONE);
admin.setVisibility(View.GONE);
hideMenu();
itemView.setOnClickListener(null);
memberEntry.getBusy().observe(this, busy -> {
busyProgress.setVisibility(busy ? View.VISIBLE : View.GONE);
popupMenu.setVisibility(busy ? View.GONE : View.VISIBLE);
});
selected.setChecked(isSelected);
}
void hideMenu() {
popupMenuContainer.setVisibility(View.GONE);
popupMenu.setVisibility(View.GONE);
}
void showMenu() {
popupMenuContainer.setVisibility(View.VISIBLE);
popupMenu.setVisibility(View.VISIBLE);
}
}
final static class FullMemberViewHolder extends ViewHolder {
FullMemberViewHolder(@NonNull View itemView,
@Nullable RecipientClickListener recipientClickListener,
@Nullable RecipientLongClickListener recipientLongClickListener,
@Nullable AdminActionsListener adminActionsListener,
@NonNull SelectionChangeListener selectionChangeListener)
{
super(itemView, recipientClickListener, recipientLongClickListener, adminActionsListener, selectionChangeListener);
}
@Override
void bind(@NonNull GroupMemberEntry memberEntry, boolean isSelected) {
super.bind(memberEntry, isSelected);
GroupMemberEntry.FullMember fullMember = (GroupMemberEntry.FullMember) memberEntry;
bindRecipient(fullMember.getMember());
bindRecipientClick(fullMember.getMember());
admin.setVisibility(fullMember.isAdmin() ? View.VISIBLE : View.INVISIBLE);
}
}
final static class NewGroupInviteeViewHolder extends ViewHolder {
private final View smsContact;
private final View smsWarning;
NewGroupInviteeViewHolder(@NonNull View itemView,
@Nullable RecipientClickListener recipientClickListener,
@Nullable RecipientLongClickListener recipientLongClickListener,
@NonNull SelectionChangeListener selectionChangeListener)
{
super(itemView, recipientClickListener, recipientLongClickListener, null, selectionChangeListener);
smsContact = itemView.findViewById(R.id.sms_contact);
smsWarning = itemView.findViewById(R.id.sms_warning);
}
@Override
void bind(@NonNull GroupMemberEntry memberEntry, boolean isSelected) {
GroupMemberEntry.NewGroupCandidate newGroupCandidate = (GroupMemberEntry.NewGroupCandidate) memberEntry;
bindRecipient(newGroupCandidate.getMember());
bindRecipientClick(newGroupCandidate.getMember());
int smsWarningVisibility = newGroupCandidate.getMember().isRegistered() ? View.GONE : View.VISIBLE;
smsContact.setVisibility(smsWarningVisibility);
smsWarning.setVisibility(smsWarningVisibility);
}
}
final static class OwnInvitePendingMemberViewHolder extends ViewHolder {
OwnInvitePendingMemberViewHolder(@NonNull View itemView,
@Nullable RecipientClickListener recipientClickListener,
@Nullable RecipientLongClickListener recipientLongClickListener,
@Nullable AdminActionsListener adminActionsListener,
@NonNull SelectionChangeListener selectionChangeListener)
{
super(itemView, recipientClickListener, recipientLongClickListener, adminActionsListener, selectionChangeListener);
}
@Override
void bind(@NonNull GroupMemberEntry memberEntry, boolean isSelected) {
super.bind(memberEntry, isSelected);
GroupMemberEntry.PendingMember pendingMember = (GroupMemberEntry.PendingMember) memberEntry;
bindRecipient(pendingMember.getInvitee());
bindRecipientClick(pendingMember.getInvitee());
if (pendingMember.isCancellable() && adminActionsListener != null) {
popupMenu.setMenu(R.menu.own_invite_pending_menu,
item -> {
if (item == R.id.revoke_invite) {
adminActionsListener.onRevokeInvite(pendingMember);
return true;
}
return false;
});
showMenu();
}
}
}
final static class UnknownPendingMemberCountViewHolder extends ViewHolder {
UnknownPendingMemberCountViewHolder(@NonNull View itemView,
@Nullable AdminActionsListener adminActionsListener,
@NonNull SelectionChangeListener selectionChangeListener)
{
super(itemView, null, null, adminActionsListener, selectionChangeListener);
}
@Override
void bind(@NonNull GroupMemberEntry memberEntry, boolean isSelected) {
super.bind(memberEntry, isSelected);
GroupMemberEntry.UnknownPendingMemberCount pendingMembers = (GroupMemberEntry.UnknownPendingMemberCount) memberEntry;
Recipient inviter = pendingMembers.getInviter();
String displayName = inviter.getDisplayName(itemView.getContext());
String displayText = context.getResources().getQuantityString(R.plurals.GroupMemberList_invited,
pendingMembers.getInviteCount(),
displayName, pendingMembers.getInviteCount());
bindImageAndText(inviter, displayText);
if (pendingMembers.isCancellable() && adminActionsListener != null) {
popupMenu.setMenu(R.menu.others_invite_pending_menu,
item -> {
if (item.getItemId() == R.id.revoke_invites) {
item.setTitle(context.getResources().getQuantityString(R.plurals.PendingMembersActivity_revoke_d_invites, pendingMembers.getInviteCount(),
pendingMembers.getInviteCount()));
return true;
}
return true;
},
item -> {
if (item == R.id.revoke_invites) {
adminActionsListener.onRevokeAllInvites(pendingMembers);
return true;
}
return false;
});
showMenu();
}
}
}
private final class SelectionChangeListener {
void onSelectionChange(int position, boolean isChecked) {
if (selectable) {
if (isChecked) {
selection.add(data.get(position));
} else {
selection.remove(data.get(position));
}
notifyItemChanged(position);
if (recipientSelectionChangeListener != null) {
recipientSelectionChangeListener.onSelectionChanged(selection);
}
}
}
}
private final static class DiffCallback extends DiffUtil.Callback {
private final List<? extends GroupMemberEntry> oldData;
private final List<? extends GroupMemberEntry> newData;
DiffCallback(List<? extends GroupMemberEntry> oldData, List<? extends GroupMemberEntry> newData) {
this.oldData = oldData;
this.newData = newData;
}
@Override
public int getOldListSize() {
return oldData.size();
}
@Override
public int getNewListSize() {
return newData.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
GroupMemberEntry oldItem = oldData.get(oldItemPosition);
GroupMemberEntry newItem = newData.get(newItemPosition);
return oldItem.sameId(newItem);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
GroupMemberEntry oldItem = oldData.get(oldItemPosition);
GroupMemberEntry newItem = newData.get(newItemPosition);
return oldItem.equals(newItem);
}
}
}