Show reminder banner to administrators for pending group join requests.

This commit is contained in:
Alan Evans 2020-10-15 13:32:50 -03:00 committed by Greyson Parrelli
parent b46589cd14
commit 837ed76f85
6 changed files with 96 additions and 4 deletions

View File

@ -0,0 +1,44 @@
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.PlayStoreUtil;
import java.util.List;
/**
* Shown to admins when there are pending group join requests.
*/
public final class PendingGroupJoinRequestsReminder extends Reminder {
private PendingGroupJoinRequestsReminder(@Nullable CharSequence title,
@NonNull CharSequence text)
{
super(title, text);
}
public static Reminder create(@NonNull Context context, int count) {
String message = context.getResources().getQuantityString(R.plurals.PendingGroupJoinRequestsReminder_d_pending_member_requests, count, count);
Reminder reminder = new PendingGroupJoinRequestsReminder(null, message);
reminder.addAction(new Action(context.getString(R.string.PendingGroupJoinRequestsReminder_view), R.id.reminder_action_review_join_requests));
return reminder;
}
@Override
public boolean isDismissable() {
return true;
}
@Override
public @NonNull Importance getImportance() {
return Importance.NORMAL;
}
}

View File

@ -74,7 +74,7 @@ public abstract class Reminder {
NORMAL, ERROR, TERMINAL NORMAL, ERROR, TERMINAL
} }
public final class Action { public static final class Action {
private final CharSequence title; private final CharSequence title;
private final int actionId; private final int actionId;

View File

@ -115,6 +115,7 @@ import org.thoughtcrime.securesms.components.identity.UnverifiedBannerView;
import org.thoughtcrime.securesms.components.location.SignalPlace; import org.thoughtcrime.securesms.components.location.SignalPlace;
import org.thoughtcrime.securesms.components.mention.MentionAnnotation; import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder; import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder;
import org.thoughtcrime.securesms.components.reminder.PendingGroupJoinRequestsReminder;
import org.thoughtcrime.securesms.components.reminder.Reminder; import org.thoughtcrime.securesms.components.reminder.Reminder;
import org.thoughtcrime.securesms.components.reminder.ReminderView; import org.thoughtcrime.securesms.components.reminder.ReminderView;
import org.thoughtcrime.securesms.components.reminder.ServiceOutageReminder; import org.thoughtcrime.securesms.components.reminder.ServiceOutageReminder;
@ -162,6 +163,7 @@ import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.groups.ui.GroupChangeResult; import org.thoughtcrime.securesms.groups.ui.GroupChangeResult;
import org.thoughtcrime.securesms.groups.ui.GroupErrors; import org.thoughtcrime.securesms.groups.ui.GroupErrors;
import org.thoughtcrime.securesms.groups.ui.LeaveGroupDialog; import org.thoughtcrime.securesms.groups.ui.LeaveGroupDialog;
import org.thoughtcrime.securesms.groups.ui.invitesandrequests.ManagePendingAndRequestingMembersActivity;
import org.thoughtcrime.securesms.groups.ui.managegroup.ManageGroupActivity; import org.thoughtcrime.securesms.groups.ui.managegroup.ManageGroupActivity;
import org.thoughtcrime.securesms.insights.InsightsLauncher; import org.thoughtcrime.securesms.insights.InsightsLauncher;
import org.thoughtcrime.securesms.invites.InviteReminderModel; import org.thoughtcrime.securesms.invites.InviteReminderModel;
@ -444,6 +446,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
initializeGroupViewModel(); initializeGroupViewModel();
initializeMentionsViewModel(); initializeMentionsViewModel();
initializeEnabledCheck(); initializeEnabledCheck();
initializePendingRequestsBanner();
initializeSecurity(recipient.get().isRegistered(), isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() { initializeSecurity(recipient.get().isRegistered(), isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() {
@Override @Override
public void onSuccess(Boolean result) { public void onSuccess(Boolean result) {
@ -1525,6 +1528,11 @@ public class ConversationActivity extends PassphraseRequiredActivity
}); });
} }
private void initializePendingRequestsBanner() {
groupViewModel.getActionableRequestingMembers()
.observe(this, actionablePendingGroupRequests -> updateReminders());
}
private ListenableFuture<Boolean> initializeDraftFromDatabase() { private ListenableFuture<Boolean> initializeDraftFromDatabase() {
SettableFuture<Boolean> future = new SettableFuture<>(); SettableFuture<Boolean> future = new SettableFuture<>();
@ -1679,6 +1687,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
protected void updateReminders() { protected void updateReminders() {
Optional<Reminder> inviteReminder = inviteReminderModel.getReminder(); Optional<Reminder> inviteReminder = inviteReminderModel.getReminder();
Integer actionableRequestingMembers = groupViewModel.getActionableRequestingMembers().getValue();
if (UnauthorizedReminder.isEligible(this)) { if (UnauthorizedReminder.isEligible(this)) {
reminderView.get().showReminder(new UnauthorizedReminder(this)); reminderView.get().showReminder(new UnauthorizedReminder(this));
@ -1696,6 +1705,13 @@ public class ConversationActivity extends PassphraseRequiredActivity
reminderView.get().setOnActionClickListener(this::handleReminderAction); reminderView.get().setOnActionClickListener(this::handleReminderAction);
reminderView.get().setOnDismissListener(() -> inviteReminderModel.dismissReminder()); reminderView.get().setOnDismissListener(() -> inviteReminderModel.dismissReminder());
reminderView.get().showReminder(inviteReminder.get()); reminderView.get().showReminder(inviteReminder.get());
} else if (actionableRequestingMembers != null && actionableRequestingMembers > 0 && FeatureFlags.groupsV2manageGroupLinks()) {
reminderView.get().showReminder(PendingGroupJoinRequestsReminder.create(this, actionableRequestingMembers));
reminderView.get().setOnActionClickListener(id -> {
if (id == R.id.reminder_action_review_join_requests) {
startActivity(ManagePendingAndRequestingMembersActivity.newIntent(this, getRecipient().getGroupId().get().requireV2()));
}
});
} else if (reminderView.resolved()) { } else if (reminderView.resolved()) {
reminderView.get().hide(); reminderView.get().hide();
} }

View File

@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason; import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.AsynchronousCallback; import org.thoughtcrime.securesms.util.AsynchronousCallback;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
@ -30,6 +31,7 @@ final class ConversationGroupViewModel extends ViewModel {
private final MutableLiveData<Recipient> liveRecipient; private final MutableLiveData<Recipient> liveRecipient;
private final LiveData<GroupActiveState> groupActiveState; private final LiveData<GroupActiveState> groupActiveState;
private final LiveData<GroupDatabase.MemberLevel> selfMembershipLevel; private final LiveData<GroupDatabase.MemberLevel> selfMembershipLevel;
private final LiveData<Integer> actionableRequestingMembers;
private ConversationGroupViewModel() { private ConversationGroupViewModel() {
this.liveRecipient = new MutableLiveData<>(); this.liveRecipient = new MutableLiveData<>();
@ -38,12 +40,20 @@ final class ConversationGroupViewModel extends ViewModel {
this.groupActiveState = Transformations.distinctUntilChanged(Transformations.map(groupRecord, ConversationGroupViewModel::mapToGroupActiveState)); this.groupActiveState = Transformations.distinctUntilChanged(Transformations.map(groupRecord, ConversationGroupViewModel::mapToGroupActiveState));
this.selfMembershipLevel = Transformations.distinctUntilChanged(Transformations.map(groupRecord, ConversationGroupViewModel::mapToSelfMembershipLevel)); this.selfMembershipLevel = Transformations.distinctUntilChanged(Transformations.map(groupRecord, ConversationGroupViewModel::mapToSelfMembershipLevel));
this.actionableRequestingMembers = Transformations.distinctUntilChanged(Transformations.map(groupRecord, ConversationGroupViewModel::mapToActionableRequestingMemberCount));
} }
void onRecipientChange(Recipient recipient) { void onRecipientChange(Recipient recipient) {
liveRecipient.setValue(recipient); liveRecipient.setValue(recipient);
} }
/**
* The number of pending group join requests that can be actioned by this client.
*/
LiveData<Integer> getActionableRequestingMembers() {
return actionableRequestingMembers;
}
LiveData<GroupActiveState> getGroupActiveState() { LiveData<GroupActiveState> getGroupActiveState() {
return groupActiveState; return groupActiveState;
} }
@ -62,6 +72,20 @@ final class ConversationGroupViewModel extends ViewModel {
} }
} }
private static int mapToActionableRequestingMemberCount(@Nullable GroupRecord record) {
if (record != null &&
FeatureFlags.groupsV2manageGroupLinks() &&
record.isV2Group() &&
record.memberLevel(Recipient.self()) == GroupDatabase.MemberLevel.ADMINISTRATOR)
{
return record.requireV2GroupProperties()
.getDecryptedGroup()
.getRequestingMembersCount();
} else {
return 0;
}
}
private static GroupActiveState mapToGroupActiveState(@Nullable GroupRecord record) { private static GroupActiveState mapToGroupActiveState(@Nullable GroupRecord record) {
if (record == null) { if (record == null) {
return null; return null;

View File

@ -7,4 +7,5 @@
<item name="reminder_action_view_insights" type="id" /> <item name="reminder_action_view_insights" type="id" />
<item name="reminder_action_invite" type="id" /> <item name="reminder_action_invite" type="id" />
<item name="reminder_action_update_now" type="id" /> <item name="reminder_action_update_now" type="id" />
<item name="reminder_action_review_join_requests" type="id" />
</resources> </resources>

View File

@ -435,6 +435,13 @@
<string name="ExpiredBuildReminder_this_version_of_signal_has_expired">This version of Signal has expired. Update now to send and receive messages.</string> <string name="ExpiredBuildReminder_this_version_of_signal_has_expired">This version of Signal has expired. Update now to send and receive messages.</string>
<string name="ExpiredBuildReminder_update_now">Update now</string> <string name="ExpiredBuildReminder_update_now">Update now</string>
<!-- PendingGroupJoinRequestsReminder -->
<plurals name="PendingGroupJoinRequestsReminder_d_pending_member_requests">
<item quantity="one">%d pending member request.</item>
<item quantity="many">%d pending member requests.</item>
</plurals>
<string name="PendingGroupJoinRequestsReminder_view">View</string>
<!-- ShareActivity --> <!-- ShareActivity -->
<string name="ShareActivity_share_with">Share with</string> <string name="ShareActivity_share_with">Share with</string>
<string name="ShareActivity_multiple_attachments_are_only_supported">Multiple attachments are only supported for images and videos</string> <string name="ShareActivity_multiple_attachments_are_only_supported">Multiple attachments are only supported for images and videos</string>