Implement new Add Members UI.

This commit is contained in:
Alex Hart
2020-06-05 12:33:41 -03:00
parent 707e238e5c
commit 4a455ff958
31 changed files with 625 additions and 58 deletions

View File

@@ -247,7 +247,7 @@ public final class GroupManager {
} else {
GroupDatabase.GroupRecord groupRecord = DatabaseFactory.getGroupDatabase(context).requireGroup(groupId);
List<RecipientId> members = groupRecord.getMembers();
byte[] avatar = Util.readFully(AvatarHelper.getAvatar(context, groupRecord.getRecipientId()));
byte[] avatar = groupRecord.hasAvatar() ? Util.readFully(AvatarHelper.getAvatar(context, groupRecord.getRecipientId())) : null;
Set<RecipientId> addresses = new HashSet<>(members);
addresses.addAll(newMembers);

View File

@@ -17,6 +17,7 @@ import org.signal.zkgroup.groups.GroupMasterKey;
import org.signal.zkgroup.util.UUIDUtil;
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
@@ -88,6 +89,18 @@ public final class GroupProtoUtil {
return Recipient.externalPush(context, uuid, null);
}
@WorkerThread
public static @NonNull RecipientId uuidByteStringToRecipientId(@NonNull ByteString uuidByteString) {
UUID uuid = UUIDUtil.deserialize(uuidByteString.toByteArray());
if (uuid.equals(GroupsV2Operations.UNKNOWN_UUID)) {
return RecipientId.UNKNOWN;
}
return RecipientId.from(uuid, null);
}
public static boolean isMember(@NonNull UUID uuid, @NonNull List<DecryptedMember> membersList) {
ByteString uuidBytes = UuidUtil.toByteString(uuid);

View File

@@ -0,0 +1,5 @@
package org.thoughtcrime.securesms.groups.ui;
public interface AddMembersResultCallback {
void onMembersAdded(int numberOfMembersAdded);
}

View File

@@ -0,0 +1,115 @@
package org.thoughtcrime.securesms.groups.ui.addmembers;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.ViewModelProviders;
import org.thoughtcrime.securesms.ContactSelectionActivity;
import org.thoughtcrime.securesms.PushContactSelectionActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
public class AddMembersActivity extends PushContactSelectionActivity {
public static final String GROUP_ID = "group_id";
private View done;
private AlertDialog alert;
private AddMembersViewModel viewModel;
@Override
protected void onCreate(Bundle icicle, boolean ready) {
getIntent().putExtra(ContactSelectionActivity.EXTRA_LAYOUT_RES_ID, R.layout.add_members_activity);
super.onCreate(icicle, ready);
AddMembersViewModel.Factory factory = new AddMembersViewModel.Factory(getGroupId());
done = findViewById(R.id.done);
alert = buildConfirmationAlertDialog();
viewModel = ViewModelProviders.of(this, factory)
.get(AddMembersViewModel.class);
viewModel.getAddMemberDialogState().observe(this, state -> AddMembersActivity.updateAlertMessage(alert, state));
//noinspection CodeBlock2Expr
done.setOnClickListener(v -> {
viewModel.setDialogStateForSelectedContacts(contactsFragment.getSelectedContacts());
alert.show();
});
disableDone();
}
@Override
protected void initializeToolbar() {
getToolbar().setNavigationIcon(R.drawable.ic_arrow_left_24);
getToolbar().setNavigationOnClickListener(v -> {
setResult(RESULT_CANCELED);
finish();
});
}
@Override
public void onContactSelected(Optional<RecipientId> recipientId, String number) {
if (contactsFragment.hasQueryFilter()) {
getToolbar().clear();
}
if (contactsFragment.getSelectedContactsCount() >= 1) {
enableDone();
}
}
@Override
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {
if (contactsFragment.hasQueryFilter()) {
getToolbar().clear();
}
if (contactsFragment.getSelectedContactsCount() < 1) {
disableDone();
}
}
private void enableDone() {
done.setEnabled(true);
done.animate().alpha(1f);
}
private void disableDone() {
done.setEnabled(false);
done.animate().alpha(0.5f);
}
private GroupId getGroupId() {
return GroupId.parseOrThrow(getIntent().getStringExtra(GROUP_ID));
}
private AlertDialog buildConfirmationAlertDialog() {
return new AlertDialog.Builder(this)
.setMessage(" ")
.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.cancel())
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
dialog.dismiss();
onFinishedSelection();
})
.setCancelable(true)
.create();
}
private static void updateAlertMessage(@NonNull AlertDialog alertDialog, @NonNull AddMembersViewModel.AddMemberDialogMessageState state) {
Context context = alertDialog.getContext();
Recipient recipient = Util.firstNonNull(state.getRecipient(), Recipient.UNKNOWN);
alertDialog.setMessage(context.getResources().getQuantityString(R.plurals.AddMembersActivity__add_d_members_to_s, state.getSelectionCount(),
recipient.getDisplayName(context), state.getGroupTitle(), state.getSelectionCount()));
}
}

View File

@@ -0,0 +1,24 @@
package org.thoughtcrime.securesms.groups.ui.addmembers;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.core.util.Consumer;
import org.thoughtcrime.securesms.contacts.SelectedContact;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
class AddMembersRepository {
private final Context context;
AddMembersRepository() {
this.context = ApplicationDependencies.getApplication();
}
void getOrCreateRecipientId(@NonNull SelectedContact selectedContact, @NonNull Consumer<RecipientId> consumer) {
SignalExecutors.BOUNDED.execute(() -> consumer.accept(selectedContact.getOrCreateRecipientId(context)));
}
}

View File

@@ -0,0 +1,142 @@
package org.thoughtcrime.securesms.groups.ui.addmembers;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.SelectedContact;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.LiveGroup;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import org.whispersystems.libsignal.util.guava.Preconditions;
import java.util.List;
import java.util.Objects;
public final class AddMembersViewModel extends ViewModel {
private final AddMembersRepository repository;
private final LiveData<AddMemberDialogMessageState> addMemberDialogState;
private final MutableLiveData<AddMemberDialogMessageStatePartial> partialState;
private AddMembersViewModel(@NonNull GroupId groupId) {
repository = new AddMembersRepository();
partialState = new MutableLiveData<>();
addMemberDialogState = LiveDataUtil.combineLatest(Transformations.map(new LiveGroup(groupId).getTitle(), AddMembersViewModel::titleOrDefault),
Transformations.switchMap(partialState, AddMembersViewModel::getStateWithoutGroupTitle),
AddMembersViewModel::getStateWithGroupTitle);
}
LiveData<AddMemberDialogMessageState> getAddMemberDialogState() {
return addMemberDialogState;
}
void setDialogStateForSelectedContacts(@NonNull List<SelectedContact> selectedContacts) {
if (selectedContacts.size() == 1) {
setDialogStateForSingleRecipient(selectedContacts.get(0));
} else {
setDialogStateForMultipleRecipients(selectedContacts.size());
}
}
private void setDialogStateForSingleRecipient(@NonNull SelectedContact selectedContact) {
//noinspection CodeBlock2Expr
repository.getOrCreateRecipientId(selectedContact, recipientId -> {
partialState.postValue(new AddMemberDialogMessageStatePartial(recipientId));
});
}
private void setDialogStateForMultipleRecipients(int recipientCount) {
partialState.setValue(new AddMemberDialogMessageStatePartial(recipientCount));
}
private static LiveData<AddMemberDialogMessageState> getStateWithoutGroupTitle(@NonNull AddMemberDialogMessageStatePartial partialState) {
if (partialState.recipientId != null) {
return Transformations.map(Recipient.live(partialState.recipientId).getLiveData(), r -> new AddMemberDialogMessageState(r, ""));
} else {
return new DefaultValueLiveData<>(new AddMemberDialogMessageState(partialState.memberCount, ""));
}
}
private static AddMemberDialogMessageState getStateWithGroupTitle(@NonNull String title, @NonNull AddMemberDialogMessageState stateWithoutTitle) {
return new AddMemberDialogMessageState(stateWithoutTitle.recipient, stateWithoutTitle.selectionCount, title);
}
private static @NonNull String titleOrDefault(@Nullable String title) {
return TextUtils.isEmpty(title) ? ApplicationDependencies.getApplication().getString(R.string.Recipient_unknown)
: Objects.requireNonNull(title);
}
private static final class AddMemberDialogMessageStatePartial {
private final RecipientId recipientId;
private final int memberCount;
private AddMemberDialogMessageStatePartial(@NonNull RecipientId recipientId) {
this.recipientId = recipientId;
this.memberCount = 1;
}
private AddMemberDialogMessageStatePartial(int memberCount) {
Preconditions.checkArgument(memberCount > 1);
this.memberCount = memberCount;
this.recipientId = null;
}
}
public static final class AddMemberDialogMessageState {
private final Recipient recipient;
private final String groupTitle;
private final int selectionCount;
private AddMemberDialogMessageState(@NonNull Recipient recipient, @NonNull String groupTitle) {
this(recipient, 1, groupTitle);
}
private AddMemberDialogMessageState(int selectionCount, @NonNull String groupTitle) {
this(null, selectionCount, groupTitle);
}
private AddMemberDialogMessageState(@Nullable Recipient recipient, int selectionCount, @NonNull String groupTitle) {
this.recipient = recipient;
this.groupTitle = groupTitle;
this.selectionCount = selectionCount;
}
public Recipient getRecipient() {
return recipient;
}
public int getSelectionCount() {
return selectionCount;
}
public @NonNull String getGroupTitle() {
return groupTitle;
}
}
public static class Factory implements ViewModelProvider.Factory {
private final GroupId groupId;
public Factory(@NonNull GroupId groupId) {
this.groupId = groupId;
}
@Override
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return Objects.requireNonNull(modelClass.cast(new AddMembersViewModel(groupId)));
}
}
}

View File

@@ -44,7 +44,7 @@ public class CreateGroupActivity extends ContactSelectionActivity {
: ContactsCursorLoader.DisplayMode.FLAG_PUSH;
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
intent.putExtra(ContactSelectionListFragment.SELECTION_LIMIT, FeatureFlags.gv2GroupCapacity() - 1);
intent.putExtra(ContactSelectionListFragment.TOTAL_CAPACITY, FeatureFlags.gv2GroupCapacity() - 1);
return intent;
}

View File

@@ -19,8 +19,11 @@ import androidx.appcompat.widget.Toolbar;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.ViewModelProviders;
import com.google.android.material.snackbar.Snackbar;
import org.thoughtcrime.securesms.AvatarPreviewActivity;
import org.thoughtcrime.securesms.MediaPreviewActivity;
import org.thoughtcrime.securesms.MuteDialog;
@@ -44,6 +47,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.LifecycleCursorWrapper;
import org.thoughtcrime.securesms.util.ThemeUtil;
import java.util.List;
@@ -322,6 +326,8 @@ public class ManageGroupFragment extends Fragment {
} else {
customNotificationsRow.setVisibility(View.GONE);
}
viewModel.getSnackbarEvents().observe(getViewLifecycleOwner(), this::handleSnackbarEvent);
}
public boolean onMenuItemSelected(@NonNull MenuItem item) {
@@ -349,6 +355,8 @@ public class ManageGroupFragment extends Fragment {
if (context == null) return;
if (this.cursorFactory != null) {
Cursor cursor = this.cursorFactory.create();
getViewLifecycleOwner().getLifecycle().addObserver(new LifecycleCursorWrapper(cursor));
threadPhotoRailView.setCursor(GlideApp.with(context), cursor);
groupMediaCard.setVisibility(cursor.getCount() > 0 ? View.VISIBLE : View.GONE);
} else {
@@ -357,6 +365,16 @@ public class ManageGroupFragment extends Fragment {
}
}
private void handleSnackbarEvent(@NonNull ManageGroupViewModel.SnackbarEvent snackbarEvent) {
Snackbar.make(requireView(), buildSnackbarString(snackbarEvent), Snackbar.LENGTH_SHORT).show();
}
private @NonNull String buildSnackbarString(@NonNull ManageGroupViewModel.SnackbarEvent snackbarEvent) {
return getResources().getQuantityString(R.plurals.ManageGroupActivity_added,
snackbarEvent.getNumberOfMembersAdded(),
snackbarEvent.getNumberOfMembersAdded());
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);

View File

@@ -6,7 +6,11 @@ import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import androidx.core.util.Consumer;
import com.annimon.stream.Stream;
import com.google.protobuf.ByteString;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.zkgroup.util.UUIDUtil;
import org.thoughtcrime.securesms.ContactSelectionListFragment;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
@@ -18,7 +22,9 @@ import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupInsufficientRightsException;
import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.groups.GroupNotAMemberException;
import org.thoughtcrime.securesms.groups.GroupProtoUtil;
import org.thoughtcrime.securesms.groups.MembershipNotSuitableForV2Exception;
import org.thoughtcrime.securesms.groups.ui.AddMembersResultCallback;
import org.thoughtcrime.securesms.groups.ui.GroupChangeErrorCallback;
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.logging.Log;
@@ -27,9 +33,12 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
final class ManageGroupRepository {
@@ -55,10 +64,17 @@ final class ManageGroupRepository {
SimpleTask.run(SignalExecutors.BOUNDED, () -> {
GroupDatabase.GroupRecord groupRecord = DatabaseFactory.getGroupDatabase(context).getGroup(groupId).get();
if (groupRecord.isV2Group()) {
DecryptedGroup decryptedGroup = groupRecord.requireV2GroupProperties().getDecryptedGroup();
return new GroupCapacityResult(decryptedGroup.getMembersCount(), decryptedGroup.getPendingMembersCount(), FeatureFlags.gv2GroupCapacity());
DecryptedGroup decryptedGroup = groupRecord.requireV2GroupProperties().getDecryptedGroup();
List<RecipientId> pendingMembers = Stream.of(decryptedGroup.getPendingMembersList())
.map(member -> GroupProtoUtil.uuidByteStringToRecipientId(member.getUuid()))
.toList();
List<RecipientId> members = new LinkedList<>(groupRecord.getMembers());
members.addAll(pendingMembers);
return new GroupCapacityResult(members, FeatureFlags.gv2GroupCapacity());
} else {
return new GroupCapacityResult(groupRecord.getMembers().size(), 0, ContactSelectionListFragment.NO_LIMIT);
return new GroupCapacityResult(groupRecord.getMembers(), ContactSelectionListFragment.NO_LIMIT);
}
}, onGroupCapacityLoaded::accept);
}
@@ -130,10 +146,11 @@ final class ManageGroupRepository {
});
}
void addMembers(@NonNull List<RecipientId> selected, @NonNull GroupChangeErrorCallback error) {
void addMembers(@NonNull List<RecipientId> selected, @NonNull AddMembersResultCallback addMembersResultCallback, @NonNull GroupChangeErrorCallback error) {
SignalExecutors.UNBOUNDED.execute(() -> {
try {
GroupManager.addMembers(context, groupId, selected);
addMembersResultCallback.onMembersAdded(selected.size());
} catch (GroupInsufficientRightsException | GroupNotAMemberException e) {
Log.w(TAG, e);
error.onError(GroupChangeFailureReason.NO_RIGHTS);
@@ -169,18 +186,20 @@ final class ManageGroupRepository {
}
static final class GroupCapacityResult {
private final int fullMembers;
private final int pendingMembers;
private final int totalCapacity;
private final List<RecipientId> members;
private final int totalCapacity;
GroupCapacityResult(int fullMembers, int pendingMembers, int totalCapacity) {
this.fullMembers = fullMembers;
this.pendingMembers = pendingMembers;
GroupCapacityResult(@NonNull List<RecipientId> members, int totalCapacity) {
this.members = members;
this.totalCapacity = totalCapacity;
}
public int getRemainingCapacity() {
return totalCapacity - fullMembers - pendingMembers;
public @NonNull List<RecipientId> getMembers() {
return members;
}
public int getTotalCapacity() {
return totalCapacity;
}
}

View File

@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.groups.ui.managegroup;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.text.TextUtils;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -18,7 +19,6 @@ import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.BlockUnblockDialog;
import org.thoughtcrime.securesms.ContactSelectionListFragment;
import org.thoughtcrime.securesms.ExpirationDialog;
import org.thoughtcrime.securesms.PushContactSelectionActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
import org.thoughtcrime.securesms.database.MediaDatabase;
@@ -30,14 +30,17 @@ import org.thoughtcrime.securesms.groups.LiveGroup;
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.groups.ui.GroupErrors;
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.groups.ui.addmembers.AddMembersActivity;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import java.util.ArrayList;
import java.util.List;
public class ManageGroupViewModel extends ViewModel {
@@ -46,6 +49,7 @@ public class ManageGroupViewModel extends ViewModel {
private final Context context;
private final ManageGroupRepository manageGroupRepository;
private final SingleLiveEvent<SnackbarEvent> snackbarEvents = new SingleLiveEvent<>();
private final LiveData<String> title;
private final LiveData<Boolean> isAdmin;
private final LiveData<Boolean> canEditGroupAttributes;
@@ -72,7 +76,9 @@ public class ManageGroupViewModel extends ViewModel {
LiveGroup liveGroup = new LiveGroup(manageGroupRepository.getGroupId());
this.title = liveGroup.getTitle();
this.title = Transformations.map(liveGroup.getTitle(),
title -> TextUtils.isEmpty(title) ? context.getString(R.string.Recipient_unknown)
: title);
this.isAdmin = liveGroup.isSelfAdmin();
this.canCollapseMemberList = LiveDataUtil.combineLatest(memberListCollapseState,
Transformations.map(liveGroup.getFullMembers(), m -> m.size() > MAX_COLLAPSED_MEMBERS),
@@ -162,6 +168,10 @@ public class ManageGroupViewModel extends ViewModel {
return hasCustomNotifications;
}
SingleLiveEvent<SnackbarEvent> getSnackbarEvents() {
return snackbarEvents;
}
public LiveData<Boolean> getCanCollapseMemberList() {
return canCollapseMemberList;
}
@@ -187,7 +197,7 @@ public class ManageGroupViewModel extends ViewModel {
}
void onAddMembers(List<RecipientId> selected) {
manageGroupRepository.addMembers(selected, this::showErrorToast);
manageGroupRepository.addMembers(selected, this::showSuccessSnackbar, this::showErrorToast);
}
void setMuteUntil(long muteUntil) {
@@ -212,6 +222,11 @@ public class ManageGroupViewModel extends ViewModel {
}
}
@WorkerThread
private void showSuccessSnackbar(int numberOfMembersAdded) {
snackbarEvents.postValue(new SnackbarEvent(numberOfMembersAdded));
}
@WorkerThread
private void showErrorToast(@NonNull GroupChangeFailureReason e) {
Util.runOnMain(() -> Toast.makeText(context, GroupErrors.getUserDisplayMessage(e), Toast.LENGTH_LONG).show());
@@ -219,13 +234,15 @@ public class ManageGroupViewModel extends ViewModel {
public void onAddMembersClick(@NonNull Fragment fragment, int resultCode) {
manageGroupRepository.getGroupCapacity(capacity -> {
int remainingCapacity = capacity.getRemainingCapacity();
int remainingCapacity = capacity.getTotalCapacity();
if (remainingCapacity <= 0) {
Toast.makeText(fragment.requireContext(), R.string.ContactSelectionListFragment_the_group_is_full, Toast.LENGTH_SHORT).show();
} else {
Intent intent = new Intent(fragment.requireActivity(), PushContactSelectionActivity.class);
Intent intent = new Intent(fragment.requireActivity(), AddMembersActivity.class);
intent.putExtra(AddMembersActivity.GROUP_ID, manageGroupRepository.getGroupId().toString());
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, ContactsCursorLoader.DisplayMode.FLAG_PUSH);
intent.putExtra(ContactSelectionListFragment.SELECTION_LIMIT, remainingCapacity);
intent.putExtra(ContactSelectionListFragment.TOTAL_CAPACITY, remainingCapacity);
intent.putParcelableArrayListExtra(ContactSelectionListFragment.CURRENT_SELECTION, new ArrayList<>(capacity.getMembers()));
fragment.startActivityForResult(intent, resultCode);
}
});
@@ -276,6 +293,18 @@ public class ManageGroupViewModel extends ViewModel {
}
}
static final class SnackbarEvent {
private final int numberOfMembersAdded;
private SnackbarEvent(int numberOfMembersAdded) {
this.numberOfMembersAdded = numberOfMembersAdded;
}
public int getNumberOfMembersAdded() {
return numberOfMembersAdded;
}
}
private enum CollapseState {
OPEN,
COLLAPSED