Fix chip jank and other groups v2 ux issues.
This commit is contained in:
parent
00996f0d7a
commit
903c3989b9
10 changed files with 120 additions and 151 deletions
|
@ -3,12 +3,21 @@ package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
|
||||||
public class ClearProfileAvatarActivity extends Activity {
|
public class ClearProfileAvatarActivity extends Activity {
|
||||||
|
|
||||||
private static final String ARG_TITLE = "arg_title";
|
private static final String ARG_TITLE = "arg_title";
|
||||||
|
|
||||||
|
private final DynamicTheme theme = new DynamicNoActionBarTheme();
|
||||||
|
|
||||||
public static Intent createForUserProfilePhoto() {
|
public static Intent createForUserProfilePhoto() {
|
||||||
return new Intent("org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO");
|
return new Intent("org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO");
|
||||||
}
|
}
|
||||||
|
@ -19,14 +28,23 @@ public class ClearProfileAvatarActivity extends Activity {
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
theme.onCreate(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
|
theme.onResume(this);
|
||||||
|
|
||||||
int titleId = getIntent().getIntExtra(ARG_TITLE, R.string.ClearProfileActivity_remove_profile_photo);
|
int titleId = getIntent().getIntExtra(ARG_TITLE, R.string.ClearProfileActivity_remove_profile_photo);
|
||||||
|
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setTitle(titleId)
|
.setMessage(titleId)
|
||||||
.setNegativeButton(android.R.string.cancel, (dialog, which) -> finish())
|
.setNegativeButton(android.R.string.cancel, (dialog, which) -> finish())
|
||||||
.setPositiveButton(R.string.ClearProfileActivity_remove, (dialog, which) -> {
|
.setPositiveButton(R.string.ClearProfileActivity_remove, (dialog, which) -> {
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.animation.LayoutTransition;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
@ -36,6 +37,8 @@ import android.widget.Toast;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintSet;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.loader.app.LoaderManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
|
@ -43,6 +46,8 @@ import androidx.loader.content.Loader;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
import androidx.transition.AutoTransition;
|
||||||
|
import androidx.transition.TransitionManager;
|
||||||
|
|
||||||
import com.google.android.material.chip.ChipGroup;
|
import com.google.android.material.chip.ChipGroup;
|
||||||
import com.pnikosis.materialishprogress.ProgressWheel;
|
import com.pnikosis.materialishprogress.ProgressWheel;
|
||||||
|
@ -89,13 +94,15 @@ public final class ContactSelectionListFragment extends Fragment
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = Log.tag(ContactSelectionListFragment.class);
|
private static final String TAG = Log.tag(ContactSelectionListFragment.class);
|
||||||
|
|
||||||
|
private static final int CHIP_GROUP_EMPTY_COUNT = 1;
|
||||||
|
private static final int CHIP_GROUP_REVEAL_DURATION_MS = 150;
|
||||||
|
|
||||||
public static final String DISPLAY_MODE = "display_mode";
|
public static final String DISPLAY_MODE = "display_mode";
|
||||||
public static final String MULTI_SELECT = "multi_select";
|
public static final String MULTI_SELECT = "multi_select";
|
||||||
public static final String REFRESHABLE = "refreshable";
|
public static final String REFRESHABLE = "refreshable";
|
||||||
public static final String RECENTS = "recents";
|
public static final String RECENTS = "recents";
|
||||||
|
|
||||||
private final Debouncer scrollDebounce = new Debouncer(100);
|
private ConstraintLayout constraintLayout;
|
||||||
|
|
||||||
private TextView emptyText;
|
private TextView emptyText;
|
||||||
private OnContactSelectedListener onContactSelectedListener;
|
private OnContactSelectedListener onContactSelectedListener;
|
||||||
private SwipeRefreshLayout swipeRefresh;
|
private SwipeRefreshLayout swipeRefresh;
|
||||||
|
@ -178,13 +185,12 @@ public final class ContactSelectionListFragment extends Fragment
|
||||||
showContactsProgress = view.findViewById(R.id.progress);
|
showContactsProgress = view.findViewById(R.id.progress);
|
||||||
chipGroup = view.findViewById(R.id.chipGroup);
|
chipGroup = view.findViewById(R.id.chipGroup);
|
||||||
chipGroupScrollContainer = view.findViewById(R.id.chipGroupScrollContainer);
|
chipGroupScrollContainer = view.findViewById(R.id.chipGroupScrollContainer);
|
||||||
|
constraintLayout = view.findViewById(R.id.container);
|
||||||
|
|
||||||
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
|
|
||||||
swipeRefresh.setEnabled(requireActivity().getIntent().getBooleanExtra(REFRESHABLE, true));
|
swipeRefresh.setEnabled(requireActivity().getIntent().getBooleanExtra(REFRESHABLE, true));
|
||||||
|
|
||||||
autoScrollOnNewItem();
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,7 +451,7 @@ public final class ContactSelectionListFragment extends Fragment
|
||||||
cursorRecyclerViewAdapter.addSelectedContact(selectedContact);
|
cursorRecyclerViewAdapter.addSelectedContact(selectedContact);
|
||||||
listItem.setChecked(true);
|
listItem.setChecked(true);
|
||||||
if (isMulti() && FeatureFlags.newGroupUI()) {
|
if (isMulti() && FeatureFlags.newGroupUI()) {
|
||||||
chipGroup.addView(newChipForContact(listItem, selectedContact));
|
addChipForContact(listItem, selectedContact);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,28 +468,65 @@ public final class ContactSelectionListFragment extends Fragment
|
||||||
chipGroup.removeView(v);
|
chipGroup.removeView(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (chipGroup.getChildCount() == CHIP_GROUP_EMPTY_COUNT) {
|
||||||
|
setChipGroupVisibility(ConstraintSet.GONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private View newChipForContact(@NonNull ContactSelectionListItem contact, @NonNull SelectedContact selectedContact) {
|
private void addChipForContact(@NonNull ContactSelectionListItem contact, @NonNull SelectedContact selectedContact) {
|
||||||
final ContactChip chip = new ContactChip(requireContext());
|
final ContactChip chip = new ContactChip(requireContext());
|
||||||
|
|
||||||
|
if (chipGroup.getChildCount() == CHIP_GROUP_EMPTY_COUNT) {
|
||||||
|
setChipGroupVisibility(ConstraintSet.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
chip.setText(contact.getChipName());
|
chip.setText(contact.getChipName());
|
||||||
chip.setContact(selectedContact);
|
chip.setContact(selectedContact);
|
||||||
|
chip.setCloseIconVisible(true);
|
||||||
|
chip.setOnCloseIconClickListener(view -> markContactUnselected(selectedContact, contact));
|
||||||
|
|
||||||
|
chipGroup.getLayoutTransition().addTransitionListener(new LayoutTransition.TransitionListener() {
|
||||||
|
@Override
|
||||||
|
public void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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());
|
||||||
|
chipGroup.post(ContactSelectionListFragment.this::smoothScrollChipsToEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
LiveRecipient recipient = contact.getRecipient();
|
LiveRecipient recipient = contact.getRecipient();
|
||||||
if (recipient != null) {
|
if (recipient != null) {
|
||||||
recipient.observe(getViewLifecycleOwner(), resolved -> {
|
chip.setAvatar(glideRequests, recipient.get(), () -> chipGroup.addView(chip));
|
||||||
chip.setAvatar(glideRequests, resolved);
|
} else {
|
||||||
chip.setText(resolved.getShortDisplayName(chip.getContext()));
|
chipGroup.addView(chip);
|
||||||
}
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chip.setCloseIconVisible(true);
|
private void registerChipRecipientObserver(@NonNull ContactChip chip, @Nullable LiveRecipient recipient) {
|
||||||
chip.setOnCloseIconClickListener(view -> {
|
if (recipient != null) {
|
||||||
markContactUnselected(selectedContact, contact);
|
recipient.observe(getViewLifecycleOwner(), resolved -> {
|
||||||
chipGroup.removeView(chip);
|
if (chip.isAttachedToWindow()) {
|
||||||
|
chip.setAvatar(glideRequests, resolved, null);
|
||||||
|
chip.setText(resolved.getShortDisplayName(chip.getContext()));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return chip;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setChipGroupVisibility(int visibility) {
|
||||||
|
TransitionManager.beginDelayedTransition(constraintLayout, new AutoTransition().setDuration(CHIP_GROUP_REVEAL_DURATION_MS));
|
||||||
|
|
||||||
|
ConstraintSet constraintSet = new ConstraintSet();
|
||||||
|
constraintSet.clone(constraintLayout);
|
||||||
|
constraintSet.setVisibility(R.id.chipGroupScrollContainer, visibility);
|
||||||
|
constraintSet.applyTo(constraintLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
|
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
|
||||||
|
@ -494,14 +537,6 @@ public final class ContactSelectionListFragment extends Fragment
|
||||||
this.swipeRefresh.setOnRefreshListener(onRefreshListener);
|
this.swipeRefresh.setOnRefreshListener(onRefreshListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void autoScrollOnNewItem() {
|
|
||||||
chipGroup.addOnLayoutChangeListener((view1, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
|
||||||
if (right > oldRight) {
|
|
||||||
scrollDebounce.publish(this::smoothScrollChipsToEnd);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void smoothScrollChipsToEnd() {
|
private void smoothScrollChipsToEnd() {
|
||||||
int x = chipGroupScrollContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR ? chipGroup.getWidth() : 0;
|
int x = chipGroupScrollContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR ? chipGroup.getWidth() : 0;
|
||||||
chipGroupScrollContainer.smoothScrollTo(x, 0);
|
chipGroupScrollContainer.smoothScrollTo(x, 0);
|
||||||
|
|
|
@ -44,17 +44,21 @@ public final class ContactChip extends Chip {
|
||||||
return contact;
|
return contact;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient) {
|
public void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, @Nullable Runnable onAvatarSet) {
|
||||||
if (recipient != null) {
|
if (recipient != null) {
|
||||||
requestManager.clear(this);
|
requestManager.clear(this);
|
||||||
|
|
||||||
Drawable fallbackContactPhotoDrawable = recipient.getFallbackContactPhotoDrawable(getContext(), false);
|
Drawable fallbackContactPhotoDrawable = new HalfScaleDrawable(recipient.getFallbackContactPhotoDrawable(getContext(), false));
|
||||||
ContactPhoto contactPhoto = recipient.getContactPhoto();
|
ContactPhoto contactPhoto = recipient.getContactPhoto();
|
||||||
|
|
||||||
if (contactPhoto == null) {
|
if (contactPhoto == null) {
|
||||||
setChipIcon(new HalfScaleDrawable(fallbackContactPhotoDrawable));
|
setChipIcon(fallbackContactPhotoDrawable);
|
||||||
|
if (onAvatarSet != null) {
|
||||||
|
onAvatarSet.run();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
requestManager.load(contactPhoto)
|
requestManager.load(contactPhoto)
|
||||||
|
.placeholder(fallbackContactPhotoDrawable)
|
||||||
.fallback(fallbackContactPhotoDrawable)
|
.fallback(fallbackContactPhotoDrawable)
|
||||||
.error(fallbackContactPhotoDrawable)
|
.error(fallbackContactPhotoDrawable)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||||
|
@ -63,6 +67,9 @@ public final class ContactChip extends Chip {
|
||||||
@Override
|
@Override
|
||||||
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
|
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
|
||||||
setChipIcon(resource);
|
setChipIcon(resource);
|
||||||
|
if (onAvatarSet != null) {
|
||||||
|
onAvatarSet.run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -35,7 +35,6 @@ public abstract class GroupMemberEntry {
|
||||||
|
|
||||||
public final static class NewGroupCandidate extends GroupMemberEntry {
|
public final static class NewGroupCandidate extends GroupMemberEntry {
|
||||||
|
|
||||||
private final DefaultValueLiveData<Boolean> isSelected = new DefaultValueLiveData<>(false);
|
|
||||||
private final Recipient member;
|
private final Recipient member;
|
||||||
|
|
||||||
public NewGroupCandidate(@NonNull Recipient member) {
|
public NewGroupCandidate(@NonNull Recipient member) {
|
||||||
|
@ -46,14 +45,6 @@ public abstract class GroupMemberEntry {
|
||||||
return member;
|
return member;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull LiveData<Boolean> isSelected() {
|
|
||||||
return isSelected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelected(boolean isSelected) {
|
|
||||||
this.isSelected.postValue(isSelected);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean sameId(@NonNull GroupMemberEntry newItem) {
|
boolean sameId(@NonNull GroupMemberEntry newItem) {
|
||||||
if (getClass() != newItem.getClass()) return false;
|
if (getClass() != newItem.getClass()) return false;
|
||||||
|
|
|
@ -246,9 +246,6 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||||
bindRecipient(newGroupCandidate.getMember());
|
bindRecipient(newGroupCandidate.getMember());
|
||||||
bindRecipientClick(newGroupCandidate.getMember());
|
bindRecipientClick(newGroupCandidate.getMember());
|
||||||
|
|
||||||
itemView.setSelected(false);
|
|
||||||
newGroupCandidate.isSelected().observe(this, itemView::setSelected);
|
|
||||||
|
|
||||||
int smsWarningVisibility = newGroupCandidate.getMember().isRegistered() ? View.GONE : View.VISIBLE;
|
int smsWarningVisibility = newGroupCandidate.getMember().isRegistered() ? View.GONE : View.VISIBLE;
|
||||||
|
|
||||||
smsContact.setVisibility(smsWarningVisibility);
|
smsContact.setVisibility(smsWarningVisibility);
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.widget.Toast;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
|
@ -58,38 +59,6 @@ public class AddGroupDetailsFragment extends Fragment {
|
||||||
private Drawable avatarPlaceholder;
|
private Drawable avatarPlaceholder;
|
||||||
private EditText name;
|
private EditText name;
|
||||||
private Toolbar toolbar;
|
private Toolbar toolbar;
|
||||||
private ActionMode actionMode;
|
|
||||||
|
|
||||||
private ActionMode.Callback recipientActionModeCallback = new ActionMode.Callback() {
|
|
||||||
@Override
|
|
||||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
|
||||||
mode.getMenuInflater().inflate(R.menu.add_group_details_fragment_context_menu, menu);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.action_delete) {
|
|
||||||
viewModel.deleteSelected();
|
|
||||||
mode.finish();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyActionMode(ActionMode mode) {
|
|
||||||
actionMode = null;
|
|
||||||
viewModel.clearSelected();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(@NonNull Context context) {
|
public void onAttach(@NonNull Context context) {
|
||||||
|
@ -142,7 +111,6 @@ public class AddGroupDetailsFragment extends Fragment {
|
||||||
|
|
||||||
avatar.setOnClickListener(v -> AvatarSelectionBottomSheetDialogFragment.create(viewModel.hasAvatar(), true, REQUEST_CODE_AVATAR, true)
|
avatar.setOnClickListener(v -> AvatarSelectionBottomSheetDialogFragment.create(viewModel.hasAvatar(), true, REQUEST_CODE_AVATAR, true)
|
||||||
.show(getChildFragmentManager(), "BOTTOM"));
|
.show(getChildFragmentManager(), "BOTTOM"));
|
||||||
members.setRecipientLongClickListener(this::handleRecipientLongClick);
|
|
||||||
members.setRecipientClickListener(this::handleRecipientClick);
|
members.setRecipientClickListener(this::handleRecipientClick);
|
||||||
name.addTextChangedListener(new AfterTextChanged(editable -> viewModel.setName(editable.toString())));
|
name.addTextChangedListener(new AfterTextChanged(editable -> viewModel.setName(editable.toString())));
|
||||||
toolbar.setNavigationOnClickListener(unused -> callback.onNavigationButtonPressed());
|
toolbar.setNavigationOnClickListener(unused -> callback.onNavigationButtonPressed());
|
||||||
|
@ -219,29 +187,15 @@ public class AddGroupDetailsFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRecipientClick(@NonNull Recipient recipient) {
|
private void handleRecipientClick(@NonNull Recipient recipient) {
|
||||||
if (actionMode == null) {
|
new AlertDialog.Builder(requireContext())
|
||||||
return;
|
.setMessage(getString(R.string.AddGroupDetailsFragment__remove_s_from_this_group, recipient.getDisplayName(requireContext())))
|
||||||
}
|
.setCancelable(true)
|
||||||
|
.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.cancel())
|
||||||
int size = viewModel.toggleSelected(recipient);
|
.setPositiveButton(R.string.AddGroupDetailsFragment__remove, (dialog, which) -> {
|
||||||
if (size == 0) {
|
viewModel.delete(recipient.getId());
|
||||||
actionMode.finish();
|
dialog.dismiss();
|
||||||
}
|
})
|
||||||
}
|
.show();
|
||||||
|
|
||||||
private boolean handleRecipientLongClick(@NonNull Recipient recipient) {
|
|
||||||
if (actionMode != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
actionMode = toolbar.startActionMode(recipientActionModeCallback);
|
|
||||||
|
|
||||||
if (actionMode != null) {
|
|
||||||
viewModel.toggleSelected(recipient);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleGroupCreateResult(@NonNull GroupCreateResult groupCreateResult) {
|
private void handleGroupCreateResult(@NonNull GroupCreateResult groupCreateResult) {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
|
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
|
||||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -28,7 +29,6 @@ import java.util.Set;
|
||||||
public final class AddGroupDetailsViewModel extends ViewModel {
|
public final class AddGroupDetailsViewModel extends ViewModel {
|
||||||
|
|
||||||
private final LiveData<List<GroupMemberEntry.NewGroupCandidate>> members;
|
private final LiveData<List<GroupMemberEntry.NewGroupCandidate>> members;
|
||||||
private final DefaultValueLiveData<Set<RecipientId>> selected = new DefaultValueLiveData<>(new HashSet<>());
|
|
||||||
private final DefaultValueLiveData<Set<RecipientId>> deleted = new DefaultValueLiveData<>(new HashSet<>());
|
private final DefaultValueLiveData<Set<RecipientId>> deleted = new DefaultValueLiveData<>(new HashSet<>());
|
||||||
private final MutableLiveData<String> name = new MutableLiveData<>("");
|
private final MutableLiveData<String> name = new MutableLiveData<>("");
|
||||||
private final MutableLiveData<byte[]> avatar = new MutableLiveData<>();
|
private final MutableLiveData<byte[]> avatar = new MutableLiveData<>();
|
||||||
|
@ -37,17 +37,14 @@ public final class AddGroupDetailsViewModel extends ViewModel {
|
||||||
private final LiveData<Boolean> canSubmitForm = Transformations.map(name, name -> !TextUtils.isEmpty(name));
|
private final LiveData<Boolean> canSubmitForm = Transformations.map(name, name -> !TextUtils.isEmpty(name));
|
||||||
private final AddGroupDetailsRepository repository;
|
private final AddGroupDetailsRepository repository;
|
||||||
|
|
||||||
AddGroupDetailsViewModel(@NonNull RecipientId[] recipientIds,
|
private AddGroupDetailsViewModel(@NonNull RecipientId[] recipientIds,
|
||||||
@NonNull AddGroupDetailsRepository repository)
|
@NonNull AddGroupDetailsRepository repository)
|
||||||
{
|
{
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
|
||||||
MutableLiveData<List<GroupMemberEntry.NewGroupCandidate>> initialMembers = new MutableLiveData<>();
|
MutableLiveData<List<GroupMemberEntry.NewGroupCandidate>> initialMembers = new MutableLiveData<>();
|
||||||
LiveData<List<GroupMemberEntry.NewGroupCandidate>> membersWithoutDeleted = LiveDataUtil.combineLatest(initialMembers,
|
|
||||||
deleted,
|
|
||||||
AddGroupDetailsViewModel::filterDeletedMembers);
|
|
||||||
|
|
||||||
members = LiveDataUtil.combineLatest(membersWithoutDeleted, selected, AddGroupDetailsViewModel::updateSelectedMembers);
|
members = LiveDataUtil.combineLatest(initialMembers, deleted, AddGroupDetailsViewModel::filterDeletedMembers);
|
||||||
isMms = Transformations.map(members, this::isAnyForcedSms);
|
isMms = Transformations.map(members, this::isAnyForcedSms);
|
||||||
|
|
||||||
repository.resolveMembers(recipientIds, initialMembers::postValue);
|
repository.resolveMembers(recipientIds, initialMembers::postValue);
|
||||||
|
@ -85,27 +82,10 @@ public final class AddGroupDetailsViewModel extends ViewModel {
|
||||||
this.name.setValue(name);
|
this.name.setValue(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
int toggleSelected(@NonNull Recipient recipient) {
|
void delete(@NonNull RecipientId recipientId) {
|
||||||
Set<RecipientId> selected = this.selected.getValue();
|
|
||||||
|
|
||||||
if (!selected.add(recipient.getId())) {
|
|
||||||
selected.remove(recipient.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.selected.setValue(selected);
|
|
||||||
|
|
||||||
return selected.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearSelected() {
|
|
||||||
this.selected.setValue(new HashSet<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
void deleteSelected() {
|
|
||||||
Set<RecipientId> selected = this.selected.getValue();
|
|
||||||
Set<RecipientId> deleted = this.deleted.getValue();
|
Set<RecipientId> deleted = this.deleted.getValue();
|
||||||
|
|
||||||
deleted.addAll(selected);
|
deleted.add(recipientId);
|
||||||
this.deleted.setValue(deleted);
|
this.deleted.setValue(deleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,14 +119,6 @@ public final class AddGroupDetailsViewModel extends ViewModel {
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @NonNull List<GroupMemberEntry.NewGroupCandidate> updateSelectedMembers(@NonNull List<GroupMemberEntry.NewGroupCandidate> members, @NonNull Set<RecipientId> selected) {
|
|
||||||
for (GroupMemberEntry.NewGroupCandidate member : members) {
|
|
||||||
member.setSelected(selected.contains(member.getMember().getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return members;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isAnyForcedSms(@NonNull List<GroupMemberEntry.NewGroupCandidate> members) {
|
private boolean isAnyForcedSms(@NonNull List<GroupMemberEntry.NewGroupCandidate> members) {
|
||||||
return Stream.of(members)
|
return Stream.of(members)
|
||||||
.anyMatch(member -> !member.getMember().isRegistered());
|
.anyMatch(member -> !member.getMember().isRegistered());
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
@ -112,20 +113,22 @@
|
||||||
<HorizontalScrollView
|
<HorizontalScrollView
|
||||||
android:id="@+id/chipGroupScrollContainer"
|
android:id="@+id/chipGroupScrollContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="56dp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:paddingBottom="8dp"
|
android:paddingBottom="8dp"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.5"
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
<com.google.android.material.chip.ChipGroup
|
<com.google.android.material.chip.ChipGroup
|
||||||
android:id="@+id/chipGroup"
|
android:id="@+id/chipGroup"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="56dp"
|
||||||
android:layout_marginEnd="96dp"
|
android:layout_marginEnd="96dp"
|
||||||
android:animateLayoutChanges="true"
|
android:animateLayoutChanges="true"
|
||||||
app:singleLine="true">
|
app:singleLine="true">
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_delete"
|
|
||||||
android:icon="?attr/menu_trash_icon"
|
|
||||||
android:title="@string/AddGroupDetailsFragment__remove"
|
|
||||||
app:showAsAction="always" />
|
|
||||||
</menu>
|
|
|
@ -500,6 +500,7 @@
|
||||||
<string name="AddGroupDetailsFragment__youve_selected_a_contact_that_doesnt">You\'ve selected a contact that doesn\'t support Signal groups, so this group will be MMS.</string>
|
<string name="AddGroupDetailsFragment__youve_selected_a_contact_that_doesnt">You\'ve selected a contact that doesn\'t support Signal groups, so this group will be MMS.</string>
|
||||||
<string name="AddGroupDetailsFragment__remove">Remove</string>
|
<string name="AddGroupDetailsFragment__remove">Remove</string>
|
||||||
<string name="AddGroupDetailsFragment__sms_contact">SMS contact</string>
|
<string name="AddGroupDetailsFragment__sms_contact">SMS contact</string>
|
||||||
|
<string name="AddGroupDetailsFragment__remove_s_from_this_group">Remove %1$s from this group?</string>
|
||||||
|
|
||||||
<!-- ManageGroupActivity -->
|
<!-- ManageGroupActivity -->
|
||||||
<string name="ManageGroupActivity_disappearing_messages">Disappearing messages</string>
|
<string name="ManageGroupActivity_disappearing_messages">Disappearing messages</string>
|
||||||
|
|
Loading…
Add table
Reference in a new issue