Prevent rejected/kicked group members from joining again via group link.

This commit is contained in:
Cody Henthorne
2022-03-11 10:08:32 -05:00
parent 3503c60fd1
commit eed45b57a1
29 changed files with 687 additions and 149 deletions

View File

@@ -12,9 +12,11 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
import org.signal.zkgroup.VerificationFailedException;
import org.signal.zkgroup.groups.GroupMasterKey;
import org.signal.zkgroup.groups.GroupSecretParams;
import org.signal.zkgroup.groups.UuidCiphertext;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl;
import org.thoughtcrime.securesms.groups.v2.GroupLinkPassword;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
@@ -22,9 +24,11 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -157,11 +161,11 @@ public final class GroupManager {
}
@WorkerThread
public static void ejectFromGroup(@NonNull Context context, @NonNull GroupId.V2 groupId, @NonNull Recipient recipient)
public static void ejectAndBanFromGroup(@NonNull Context context, @NonNull GroupId.V2 groupId, @NonNull Recipient recipient)
throws GroupChangeBusyException, GroupChangeFailedException, GroupInsufficientRightsException, GroupNotAMemberException, IOException
{
try (GroupManagerV2.GroupEditor edit = new GroupManagerV2(context).edit(groupId.requireV2())) {
edit.ejectMember(recipient.requireServiceId(), false);
edit.ejectMember(recipient.requireServiceId(), false, true);
Log.i(TAG, "Member removed from group " + groupId);
}
}
@@ -273,6 +277,28 @@ public final class GroupManager {
}
}
@WorkerThread
public static void ban(@NonNull Context context,
@NonNull GroupId.V2 groupId,
@NonNull RecipientId recipientId)
throws GroupChangeBusyException, IOException, GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException
{
try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) {
editor.ban(Collections.singleton(Recipient.resolved(recipientId).requireServiceId().uuid()));
}
}
@WorkerThread
public static void unban(@NonNull Context context,
@NonNull GroupId.V2 groupId,
@NonNull RecipientId recipientId)
throws GroupChangeBusyException, IOException, GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException
{
try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) {
editor.unban(Collections.singleton(Recipient.resolved(recipientId).requireServiceId().uuid()));
}
}
@WorkerThread
public static void applyMembershipAdditionRightsChange(@NonNull Context context,
@NonNull GroupId.V2 groupId,

View File

@@ -428,7 +428,7 @@ final class GroupManagerV2 {
.map(r -> Recipient.resolved(r).requireServiceId().uuid())
.collect(Collectors.toSet());
return commitChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids));
return commitChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids, true));
}
@WorkerThread
@@ -455,15 +455,15 @@ final class GroupManagerV2 {
throw new AssertionError(e);
}
} else {
return ejectMember(ServiceId.from(selfAci.uuid()), true);
return ejectMember(ServiceId.from(selfAci.uuid()), true, false);
}
}
@WorkerThread
@NonNull GroupManager.GroupActionResult ejectMember(@NonNull ServiceId serviceId, boolean allowWhenBlocked)
@NonNull GroupManager.GroupActionResult ejectMember(@NonNull ServiceId serviceId, boolean allowWhenBlocked, boolean ban)
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
{
return commitChangeWithConflictResolution(groupOperations.createRemoveMembersChange(Collections.singleton(serviceId.uuid())), allowWhenBlocked);
return commitChangeWithConflictResolution(groupOperations.createRemoveMembersChange(Collections.singleton(serviceId.uuid()), ban), allowWhenBlocked);
}
@WorkerThread
@@ -530,6 +530,14 @@ final class GroupManagerV2 {
return commitChangeWithConflictResolution(groupOperations.createAcceptInviteChange(groupCandidate.getProfileKeyCredential().get()));
}
public GroupManager.GroupActionResult ban(Set<UUID> uuids) throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException {
return commitChangeWithConflictResolution(groupOperations.createBanUuidsChange(uuids));
}
public GroupManager.GroupActionResult unban(Set<UUID> uuids) throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException {
return commitChangeWithConflictResolution(groupOperations.createUnbanUuidsChange(uuids));
}
@WorkerThread
public GroupManager.GroupActionResult cycleGroupLinkPassword()
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
@@ -1094,7 +1102,7 @@ final class GroupManagerV2 {
GroupChange signedGroupChange;
try {
signedGroupChange = commitCancelChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids));
signedGroupChange = commitCancelChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids, false));
} catch (GroupLinkNotActiveException e) {
Log.d(TAG, "Unexpected unable to leave group due to group link off");
throw new GroupChangeFailedException(e);

View File

@@ -5,6 +5,8 @@ import android.content.Context;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -13,50 +15,37 @@ final class RequestConfirmationDialog {
private RequestConfirmationDialog() {
}
/**
* Confirms that you want to approve or deny a request to join the group depending on
* {@param approve}.
*/
static AlertDialog show(@NonNull Context context,
@NonNull Recipient requester,
boolean approve,
@NonNull Runnable onApproveOrDeny)
{
if (approve) {
return showRequestApproveConfirmationDialog(context, requester, onApproveOrDeny);
} else {
return showRequestDenyConfirmationDialog(context, requester, onApproveOrDeny);
}
}
/**
* Confirms that you want to approve a request to join the group.
*/
private static AlertDialog showRequestApproveConfirmationDialog(@NonNull Context context,
@NonNull Recipient requester,
@NonNull Runnable onApprove)
public static AlertDialog showApprove(@NonNull Context context,
@NonNull Recipient requester,
@NonNull Runnable onApprove)
{
return new AlertDialog.Builder(context)
.setMessage(context.getString(R.string.RequestConfirmationDialog_add_s_to_the_group,
requester.getDisplayName(context)))
.setPositiveButton(R.string.RequestConfirmationDialog_add, (dialog, which) -> onApprove.run())
.setNegativeButton(android.R.string.cancel, null)
.show();
return new MaterialAlertDialogBuilder(context)
.setMessage(context.getString(R.string.RequestConfirmationDialog_add_s_to_the_group,
requester.getDisplayName(context)))
.setPositiveButton(R.string.RequestConfirmationDialog_add, (dialog, which) -> onApprove.run())
.setNegativeButton(android.R.string.cancel, null)
.show();
}
/**
* Confirms that you want to deny a request to join the group.
*/
private static AlertDialog showRequestDenyConfirmationDialog(@NonNull Context context,
@NonNull Recipient requester,
@NonNull Runnable onDeny)
public static AlertDialog showDeny(@NonNull Context context,
@NonNull Recipient requester,
boolean linkEnabled,
@NonNull Runnable onDeny)
{
return new AlertDialog.Builder(context)
.setMessage(context.getString(R.string.RequestConfirmationDialog_deny_request_from_s,
requester.getDisplayName(context)))
.setPositiveButton(R.string.RequestConfirmationDialog_deny, (dialog, which) -> onDeny.run())
.setNegativeButton(android.R.string.cancel, null)
.show();
String message = linkEnabled ? context.getString(R.string.RequestConfirmationDialog_deny_request_from_s_they_will_not_be_able_to_request, requester.getDisplayName(context))
: context.getString(R.string.RequestConfirmationDialog_deny_request_from_s, requester.getDisplayName(context));
return new MaterialAlertDialogBuilder(context)
.setMessage(message)
.setPositiveButton(R.string.RequestConfirmationDialog_deny, (dialog, which) -> onDeny.run())
.setNegativeButton(android.R.string.cancel, null)
.show();
}
}

View File

@@ -15,13 +15,11 @@ 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.recipients.RecipientId;
import org.thoughtcrime.securesms.groups.v2.GroupLinkUrlAndStatus;
import org.thoughtcrime.securesms.util.AsynchronousCallback;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class RequestingMemberInvitesViewModel extends ViewModel {
@@ -29,6 +27,7 @@ public class RequestingMemberInvitesViewModel extends ViewModel {
private final RequestingMemberRepository requestingMemberRepository;
private final MutableLiveData<String> toasts;
private final LiveData<List<GroupMemberEntry.RequestingMember>> requesting;
private final LiveData<GroupLinkUrlAndStatus> inviteLink;
private RequestingMemberInvitesViewModel(@NonNull Context context,
@NonNull GroupId.V2 groupId,
@@ -36,62 +35,58 @@ public class RequestingMemberInvitesViewModel extends ViewModel {
{
this.context = context;
this.requestingMemberRepository = requestingMemberRepository;
this.requesting = new LiveGroup(groupId).getRequestingMembers();
this.toasts = new SingleLiveEvent<>();
LiveGroup liveGroup = new LiveGroup(groupId);
this.requesting = liveGroup.getRequestingMembers();
this.inviteLink = liveGroup.getGroupLink();
}
LiveData<List<GroupMemberEntry.RequestingMember>> getRequesting() {
return requesting;
}
LiveData<GroupLinkUrlAndStatus> getInviteLink() {
return inviteLink;
}
LiveData<String> getToasts() {
return toasts;
}
void approveRequestFor(@NonNull GroupMemberEntry.RequestingMember requestingMember) {
approveOrDeny(requestingMember, true);
requestingMember.setBusy(true);
requestingMemberRepository.approveRequest(requestingMember.getRequester(), new AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason>() {
@Override
public void onComplete(@Nullable Void result) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(R.string.RequestingMembersFragment_added_s, requestingMember.getRequester().getDisplayName(context)));
}
@Override
public void onError(@Nullable GroupChangeFailureReason error) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(GroupErrors.getUserDisplayMessage(error)));
}
});
}
void denyRequestFor(@NonNull GroupMemberEntry.RequestingMember requestingMember) {
approveOrDeny(requestingMember, false);
}
requestingMember.setBusy(true);
requestingMemberRepository.denyRequest(requestingMember.getRequester(), new AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason>() {
@Override
public void onComplete(@Nullable Void result) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(R.string.RequestingMembersFragment_denied_s, requestingMember.getRequester().getDisplayName(context)));
}
private void approveOrDeny(@NonNull GroupMemberEntry.RequestingMember requestingMember, boolean approve) {
RequestConfirmationDialog.show(context, requestingMember.getRequester(), approve, () -> {
Set<RecipientId> memberAsSet = Collections.singleton(requestingMember.getRequester().getId());
if (approve) {
requestingMember.setBusy(true);
requestingMemberRepository.approveRequests(memberAsSet, new AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason>() {
@Override
public void onComplete(@Nullable Void result) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(R.string.RequestingMembersFragment_added_s, requestingMember.getRequester().getDisplayName(context)));
}
@Override
public void onError(@Nullable GroupChangeFailureReason error) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(GroupErrors.getUserDisplayMessage(error)));
}
});
} else {
requestingMember.setBusy(true);
requestingMemberRepository.denyRequests(memberAsSet, new AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason>() {
@Override
public void onComplete(@Nullable Void result) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(R.string.RequestingMembersFragment_denied_s, requestingMember.getRequester().getDisplayName(context)));
}
@Override
public void onError(@Nullable GroupChangeFailureReason error) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(GroupErrors.getUserDisplayMessage(error)));
}
});
}
});
@Override
public void onError(@Nullable GroupChangeFailureReason error) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(GroupErrors.getUserDisplayMessage(error)));
}
});
}
public static class Factory implements ViewModelProvider.Factory {

View File

@@ -10,11 +10,13 @@ import org.thoughtcrime.securesms.groups.GroupChangeException;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.AsynchronousCallback;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
/**
* Repository for modifying the requesting members on a single group.
@@ -31,12 +33,12 @@ final class RequestingMemberRepository {
this.groupId = groupId;
}
void approveRequests(@NonNull Collection<RecipientId> recipientIds,
@NonNull AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason> callback)
void approveRequest(@NonNull Recipient recipient,
@NonNull AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason> callback)
{
SignalExecutors.UNBOUNDED.execute(() -> {
try {
GroupManager.approveRequests(context, groupId, recipientIds);
GroupManager.approveRequests(context, groupId, Collections.singleton(recipient.getId()));
callback.onComplete(null);
} catch (GroupChangeException | IOException e) {
Log.w(TAG, e);
@@ -45,12 +47,12 @@ final class RequestingMemberRepository {
});
}
void denyRequests(@NonNull Collection<RecipientId> recipientIds,
@NonNull AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason> callback)
void denyRequest(@NonNull Recipient recipient,
@NonNull AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason> callback)
{
SignalExecutors.UNBOUNDED.execute(() -> {
try {
GroupManager.denyRequests(context, groupId, recipientIds);
GroupManager.denyRequests(context, groupId, Collections.singleton(recipient.getId()));
callback.onComplete(null);
} catch (GroupChangeException | IOException e) {
Log.w(TAG, e);

View File

@@ -9,6 +9,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import org.thoughtcrime.securesms.R;
@@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.ui.AdminActionsListener;
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
import org.thoughtcrime.securesms.groups.v2.GroupLinkUrlAndStatus;
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
import org.thoughtcrime.securesms.util.BottomSheetUtil;
@@ -29,9 +31,9 @@ public class RequestingMembersFragment extends Fragment {
private static final String GROUP_ID = "GROUP_ID";
private RequestingMemberInvitesViewModel viewModel;
private GroupMemberListView requestingMembers;
private View noRequestingMessage;
private View requestingExplanation;
private GroupMemberListView requestingMembers;
private View noRequestingMessage;
private View requestingExplanation;
public static RequestingMembersFragment newInstance(@NonNull GroupId.V2 groupId) {
RequestingMembersFragment fragment = new RequestingMembersFragment();
@@ -53,9 +55,10 @@ public class RequestingMembersFragment extends Fragment {
requestingMembers.initializeAdapter(getViewLifecycleOwner());
requestingMembers.setRecipientClickListener(recipient ->
requestingMembers.setRecipientClickListener(recipient -> {
RecipientBottomSheetDialogFragment.create(recipient.getId(), null)
.show(requireActivity().getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG));
.show(requireActivity().getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
});
requestingMembers.setAdminActionsListener(new AdminActionsListener() {
@@ -71,31 +74,37 @@ public class RequestingMembersFragment extends Fragment {
@Override
public void onApproveRequest(@NonNull GroupMemberEntry.RequestingMember requestingMember) {
viewModel.approveRequestFor(requestingMember);
//noinspection CodeBlock2Expr
RequestConfirmationDialog.showApprove(requireContext(), requestingMember.getRequester(), () -> {
viewModel.approveRequestFor(requestingMember);
});
}
@Override
public void onDenyRequest(@NonNull GroupMemberEntry.RequestingMember requestingMember) {
viewModel.denyRequestFor(requestingMember);
GroupLinkUrlAndStatus linkStatus = viewModel.getInviteLink().getValue();
boolean linkEnabled = linkStatus == null || linkStatus.isEnabled();
//noinspection CodeBlock2Expr
RequestConfirmationDialog.showDeny(requireContext(), requestingMember.getRequester(), linkEnabled, () -> {
viewModel.denyRequestFor(requestingMember);
});
}
});
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
GroupId.V2 groupId = GroupId.parseOrThrow(Objects.requireNonNull(requireArguments().getString(GROUP_ID))).requireV2();
RequestingMemberInvitesViewModel.Factory factory = new RequestingMemberInvitesViewModel.Factory(requireContext(), groupId);
viewModel = ViewModelProviders.of(requireActivity(), factory).get(RequestingMemberInvitesViewModel.class);
viewModel = new ViewModelProvider(this, factory).get(RequestingMemberInvitesViewModel.class);
viewModel.getRequesting().observe(getViewLifecycleOwner(), requesting -> {
requestingMembers.setMembers(requesting);
noRequestingMessage.setVisibility(requesting.isEmpty() ? View.VISIBLE: View.GONE);
noRequestingMessage.setVisibility(requesting.isEmpty() ? View.VISIBLE : View.GONE);
requestingExplanation.setVisibility(requesting.isEmpty() ? View.GONE : View.VISIBLE);
});

View File

@@ -101,7 +101,7 @@ class ReviewCardRepository {
SignalExecutors.BOUNDED.execute(() -> {
try {
GroupManager.ejectFromGroup(context, groupId, reviewCard.getReviewRecipient());
GroupManager.ejectAndBanFromGroup(context, groupId, reviewCard.getReviewRecipient());
onRemoveFromGroupListener.onActionCompleted();
} catch (GroupChangeException | IOException e) {
onRemoveFromGroupListener.onActionFailed();

View File

@@ -290,6 +290,10 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
makeGroupAdminButton.setVisibility(adminStatus.isCanMakeAdmin() ? View.VISIBLE : View.GONE);
removeAdminButton.setVisibility(adminStatus.isCanMakeNonAdmin() ? View.VISIBLE : View.GONE);
removeFromGroupButton.setVisibility(adminStatus.isCanRemove() ? View.VISIBLE : View.GONE);
if (adminStatus.isCanRemove()) {
removeFromGroupButton.setOnClickListener(view -> viewModel.onRemoveFromGroupClicked(requireActivity(), adminStatus.isLinkActive(), this::dismiss));
}
});
viewModel.getIdentity().observe(getViewLifecycleOwner(), identityRecord -> {
@@ -319,8 +323,6 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
makeGroupAdminButton.setOnClickListener(view -> viewModel.onMakeGroupAdminClicked(requireActivity()));
removeAdminButton.setOnClickListener(view -> viewModel.onRemoveGroupAdminClicked(requireActivity()));
removeFromGroupButton.setOnClickListener(view -> viewModel.onRemoveFromGroupClicked(requireActivity(), this::dismiss));
addToGroupButton.setOnClickListener(view -> {
dismiss();
viewModel.onAddToGroupButton(requireActivity());

View File

@@ -77,7 +77,7 @@ final class RecipientDialogRepository {
SimpleTask.run(SignalExecutors.UNBOUNDED,
() -> {
try {
GroupManager.ejectFromGroup(context, Objects.requireNonNull(groupId).requireV2(), Recipient.resolved(recipientId));
GroupManager.ejectAndBanFromGroup(context, Objects.requireNonNull(groupId).requireV2(), Recipient.resolved(recipientId));
return true;
} catch (GroupChangeException | IOException e) {
Log.w(TAG, e);

View File

@@ -15,6 +15,8 @@ import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.ThreadUtil;
import org.thoughtcrime.securesms.BlockUnblockDialog;
import org.thoughtcrime.securesms.R;
@@ -33,6 +35,7 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
import org.whispersystems.libsignal.util.Pair;
import java.util.Objects;
@@ -68,20 +71,22 @@ final class RecipientDialogViewModel extends ViewModel {
if (recipientDialogRepository.getGroupId() != null && recipientDialogRepository.getGroupId().isV2() && !recipientIsSelf) {
LiveGroup source = new LiveGroup(recipientDialogRepository.getGroupId());
LiveData<Boolean> localIsAdmin = source.isSelfAdmin();
LiveData<Pair<Boolean, Boolean>> localStatus = LiveDataUtil.combineLatest(source.isSelfAdmin(), Transformations.map(source.getGroupLink(), s -> s == null || s.isEnabled()), Pair::new);
LiveData<GroupDatabase.MemberLevel> recipientMemberLevel = Transformations.switchMap(recipient, source::getMemberLevel);
adminActionStatus = LiveDataUtil.combineLatest(localIsAdmin, recipientMemberLevel,
(localAdmin, memberLevel) -> {
boolean inGroup = memberLevel.isInGroup();
boolean recipientAdmin = memberLevel == GroupDatabase.MemberLevel.ADMINISTRATOR;
adminActionStatus = LiveDataUtil.combineLatest(localStatus, recipientMemberLevel, (statuses, memberLevel) -> {
boolean localAdmin = statuses.first();
boolean isLinkActive = statuses.second();
boolean inGroup = memberLevel.isInGroup();
boolean recipientAdmin = memberLevel == GroupDatabase.MemberLevel.ADMINISTRATOR;
return new AdminActionStatus(inGroup && localAdmin,
inGroup && localAdmin && !recipientAdmin,
inGroup && localAdmin && recipientAdmin);
});
return new AdminActionStatus(inGroup && localAdmin,
inGroup && localAdmin && !recipientAdmin,
inGroup && localAdmin && recipientAdmin,
isLinkActive);
});
} else {
adminActionStatus = new MutableLiveData<>(new AdminActionStatus(false, false, false));
adminActionStatus = new MutableLiveData<>(new AdminActionStatus(false, false, false, false));
}
boolean isSelf = recipientDialogRepository.getRecipientId().equals(Recipient.self().getId());
@@ -164,7 +169,7 @@ final class RecipientDialogViewModel extends ViewModel {
}
void onMakeGroupAdminClicked(@NonNull Activity activity) {
new AlertDialog.Builder(activity)
new MaterialAlertDialogBuilder(activity)
.setMessage(context.getString(R.string.RecipientBottomSheet_s_will_be_able_to_edit_group, Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
.setPositiveButton(R.string.RecipientBottomSheet_make_admin,
(dialog, which) -> {
@@ -182,7 +187,7 @@ final class RecipientDialogViewModel extends ViewModel {
}
void onRemoveGroupAdminClicked(@NonNull Activity activity) {
new AlertDialog.Builder(activity)
new MaterialAlertDialogBuilder(activity)
.setMessage(context.getString(R.string.RecipientBottomSheet_remove_s_as_group_admin, Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
.setPositiveButton(R.string.RecipientBottomSheet_remove_as_admin,
(dialog, which) -> {
@@ -199,9 +204,11 @@ final class RecipientDialogViewModel extends ViewModel {
.show();
}
void onRemoveFromGroupClicked(@NonNull Activity activity, @NonNull Runnable onSuccess) {
new AlertDialog.Builder(activity)
.setMessage(context.getString(R.string.RecipientBottomSheet_remove_s_from_the_group, Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
void onRemoveFromGroupClicked(@NonNull Activity activity, boolean isLinkActive, @NonNull Runnable onSuccess) {
new MaterialAlertDialogBuilder(activity)
.setMessage(context.getString(isLinkActive ? R.string.RecipientBottomSheet_remove_s_from_the_group_they_will_not_be_able_to_rejoin
: R.string.RecipientBottomSheet_remove_s_from_the_group,
Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
.setPositiveButton(R.string.RecipientBottomSheet_remove,
(dialog, which) -> {
adminActionBusy.setValue(true);
@@ -234,11 +241,13 @@ final class RecipientDialogViewModel extends ViewModel {
private final boolean canRemove;
private final boolean canMakeAdmin;
private final boolean canMakeNonAdmin;
private final boolean isLinkActive;
AdminActionStatus(boolean canRemove, boolean canMakeAdmin, boolean canMakeNonAdmin) {
AdminActionStatus(boolean canRemove, boolean canMakeAdmin, boolean canMakeNonAdmin, boolean isLinkActive) {
this.canRemove = canRemove;
this.canMakeAdmin = canMakeAdmin;
this.canMakeNonAdmin = canMakeNonAdmin;
this.isLinkActive = isLinkActive;
}
boolean isCanRemove() {
@@ -252,6 +261,10 @@ final class RecipientDialogViewModel extends ViewModel {
boolean isCanMakeNonAdmin() {
return canMakeNonAdmin;
}
boolean isLinkActive() {
return isLinkActive;
}
}
public static class Factory implements ViewModelProvider.Factory {