diff --git a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java index eb66a21606..24b6e4592b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java @@ -44,6 +44,7 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.loader.app.LoaderManager; import androidx.loader.content.Loader; +import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -195,6 +196,12 @@ public final class ContactSelectionListFragment extends Fragment constraintLayout = view.findViewById(R.id.container); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + recyclerView.setItemAnimator(new DefaultItemAnimator() { + @Override + public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) { + return true; + } + }); swipeRefresh.setEnabled(requireActivity().getIntent().getBooleanExtra(REFRESHABLE, true)); @@ -335,6 +342,10 @@ public final class ContactSelectionListFragment extends Fragment swipeRefresh.setRefreshing(false); } + public boolean hasQueryFilter() { + return !TextUtils.isEmpty(cursorFilter); + } + public void setRefreshing(boolean refreshing) { swipeRefresh.setRefreshing(refreshing); } @@ -455,7 +466,8 @@ public final class ContactSelectionListFragment extends Fragment if (uuid.isPresent()) { Recipient recipient = Recipient.externalUsername(requireContext(), uuid.get(), contact.getNumber()); SelectedContact selected = SelectedContact.forUsername(recipient.getId(), contact.getNumber()); - markContactSelected(selected, contact); + markContactSelected(selected); + cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE); if (onContactSelectedListener != null) { onContactSelectedListener.onContactSelected(Optional.of(recipient.getId()), null); @@ -469,14 +481,16 @@ public final class ContactSelectionListFragment extends Fragment } }); } else { - markContactSelected(selectedContact, contact); + markContactSelected(selectedContact); + cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE); if (onContactSelectedListener != null) { onContactSelectedListener.onContactSelected(contact.getRecipientId(), contact.getNumber()); } } } else { - markContactUnselected(selectedContact, contact); + markContactUnselected(selectedContact); + cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE); if (onContactSelectedListener != null) { onContactSelectedListener.onContactDeselected(contact.getRecipientId(), contact.getNumber()); @@ -488,17 +502,16 @@ public final class ContactSelectionListFragment extends Fragment return getChipCount() >= selectionLimit; } - private void markContactSelected(@NonNull SelectedContact selectedContact, @NonNull ContactSelectionListItem listItem) { + private void markContactSelected(@NonNull SelectedContact selectedContact) { cursorRecyclerViewAdapter.addSelectedContact(selectedContact); - listItem.setChecked(true); if (isMulti() && FeatureFlags.newGroupUI()) { - addChipForContact(listItem, selectedContact); + addChipForSelectedContact(selectedContact); } } - private void markContactUnselected(@NonNull SelectedContact selectedContact, @NonNull ContactSelectionListItem listItem) { + private void markContactUnselected(@NonNull SelectedContact selectedContact) { cursorRecyclerViewAdapter.removeFromSelectedContacts(selectedContact); - listItem.setChecked(false); + cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE); removeChipForContact(selectedContact); } @@ -517,17 +530,23 @@ public final class ContactSelectionListFragment extends Fragment } } - private void addChipForContact(@NonNull ContactSelectionListItem contact, @NonNull SelectedContact selectedContact) { + private void addChipForSelectedContact(@NonNull SelectedContact selectedContact) { + SimpleTask.run(getViewLifecycleOwner().getLifecycle(), + () -> Recipient.resolved(selectedContact.getOrCreateRecipientId(requireContext())), + resolved -> addChipForRecipient(resolved, selectedContact)); + } + + private void addChipForRecipient(@NonNull Recipient recipient, @NonNull SelectedContact selectedContact) { final ContactChip chip = new ContactChip(requireContext()); if (getChipCount() == 0) { setChipGroupVisibility(ConstraintSet.VISIBLE); } - chip.setText(contact.getChipName()); + chip.setText(recipient.getShortDisplayName(requireContext())); chip.setContact(selectedContact); chip.setCloseIconVisible(true); - chip.setOnCloseIconClickListener(view -> markContactUnselected(selectedContact, contact)); + chip.setOnCloseIconClickListener(view -> markContactUnselected(selectedContact)); chipGroup.getLayoutTransition().addTransitionListener(new LayoutTransition.TransitionListener() { @Override @@ -538,18 +557,13 @@ public final class ContactSelectionListFragment extends Fragment public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) { if (view == chip && transitionType == LayoutTransition.APPEARING) { chipGroup.getLayoutTransition().removeTransitionListener(this); - registerChipRecipientObserver(chip, contact.getRecipient()); + registerChipRecipientObserver(chip, recipient.live()); chipGroup.post(ContactSelectionListFragment.this::smoothScrollChipsToEnd); } } }); - LiveRecipient recipient = contact.getRecipient(); - if (recipient != null) { - chip.setAvatar(glideRequests, recipient.get(), () -> addChip(chip)); - } else { - addChip(chip); - } + chip.setAvatar(glideRequests, recipient, () -> addChip(chip)); } private void addChip(@NonNull ContactChip chip) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java index f3d3530335..4e4a3361a5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java @@ -64,6 +64,8 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter payloads) { + if (!arePayloadsValid(payloads)) { + throw new AssertionError(); + } + + String rawId = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN)); + RecipientId id = rawId != null ? RecipientId.from(rawId) : null; + int numberType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_TYPE_COLUMN)); + String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactRepository.NUMBER_COLUMN)); + + if (numberType == ContactRepository.NEW_USERNAME_TYPE) { + viewHolder.setChecked(selectedContacts.contains(SelectedContact.forUsername(id, number))); + } else { + viewHolder.setChecked(selectedContacts.contains(SelectedContact.forPhone(id, number))); + } + } + @Override public int getItemViewType(@NonNull Cursor cursor) { if (cursor.getInt(cursor.getColumnIndexOrThrow(ContactRepository.CONTACT_TYPE_COLUMN)) == ContactRepository.DIVIDER_TYPE) { @@ -225,7 +246,6 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter payloads) { + return payloads.size() == 1 && payloads.get(0).equals(PAYLOAD_SELECTION_CHANGE); + } + @Override public void onItemViewRecycled(ViewHolder holder) { holder.unbind(glideRequests); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/CursorRecyclerViewAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/database/CursorRecyclerViewAdapter.java index d3f5218c56..b5e54471c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/CursorRecyclerViewAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/CursorRecyclerViewAdapter.java @@ -31,6 +31,8 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; import org.thoughtcrime.securesms.R; +import java.util.List; + /** * RecyclerView.Adapter that manages a Cursor, comparable to the CursorAdapter usable in ListView/GridView. */ @@ -173,6 +175,16 @@ public abstract class CursorRecyclerViewAdapter payloads) { + if (arePayloadsValid(payloads) && !isHeaderPosition(position) && !isFooterPosition(position)) { + if (isFastAccessPosition(position)) onBindFastAccessItemViewHolder((VH)viewHolder, position, payloads); + else onBindItemViewHolder((VH)viewHolder, getCursorAtPositionOrThrow(position), payloads); + } else { + onBindViewHolder(viewHolder, position); + } + } + @SuppressWarnings("unchecked") @Override public final void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { @@ -188,8 +200,17 @@ public abstract class CursorRecyclerViewAdapter payloads) { + return false; + } + protected void onBindItemViewHolder(VH viewHolder, @NonNull Cursor cursor, @NonNull List payloads) { + } + + protected void onBindFastAccessItemViewHolder(VH viewHolder, int position) { + } + + protected void onBindFastAccessItemViewHolder(VH viewHolder, int position, @NonNull List payloads) { } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java index a3e1167ce7..9a87e818b3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java @@ -82,6 +82,10 @@ public class CreateGroupActivity extends ContactSelectionActivity { @Override public void onContactSelected(Optional recipientId, String number) { + if (contactsFragment.hasQueryFilter()) { + getToolbar().clear(); + } + if (contactsFragment.getSelectedContactsCount() >= MINIMUM_GROUP_SIZE) { enableNext(); } @@ -89,6 +93,10 @@ public class CreateGroupActivity extends ContactSelectionActivity { @Override public void onContactDeselected(Optional recipientId, String number) { + if (contactsFragment.hasQueryFilter()) { + getToolbar().clear(); + } + if (contactsFragment.getSelectedContactsCount() < MINIMUM_GROUP_SIZE) { disableNext(); }