Add the ability to add suggested members after a GV1 migration.

This commit is contained in:
Greyson Parrelli
2020-11-09 08:30:58 -05:00
committed by Cody Henthorne
parent c4c32d80b2
commit d307db8a95
8 changed files with 244 additions and 16 deletions

View File

@@ -69,7 +69,6 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.DrawableCompat;
@@ -117,6 +116,7 @@ import org.thoughtcrime.securesms.components.identity.UnverifiedBannerView;
import org.thoughtcrime.securesms.components.location.SignalPlace;
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder;
import org.thoughtcrime.securesms.components.reminder.GroupsV1MigrationSuggestionsReminder;
import org.thoughtcrime.securesms.components.reminder.PendingGroupJoinRequestsReminder;
import org.thoughtcrime.securesms.components.reminder.Reminder;
import org.thoughtcrime.securesms.components.reminder.ReminderView;
@@ -167,6 +167,7 @@ import org.thoughtcrime.securesms.groups.ui.GroupErrors;
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.migration.GroupsV1MigrationSuggestionsDialog;
import org.thoughtcrime.securesms.insights.InsightsLauncher;
import org.thoughtcrime.securesms.invites.InviteReminderModel;
import org.thoughtcrime.securesms.invites.InviteReminderRepository;
@@ -212,7 +213,6 @@ import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.GroupShareProfileView;
import org.thoughtcrime.securesms.profiles.spoofing.ReviewBannerView;
import org.thoughtcrime.securesms.profiles.spoofing.ReviewCardDialogFragment;
import org.thoughtcrime.securesms.profiles.spoofing.ReviewUtil;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment;
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment;
@@ -457,6 +457,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
initializeMentionsViewModel();
initializeEnabledCheck();
initializePendingRequestsBanner();
initializeGroupV1MigrationSuggestionsBanner();
initializeSecurity(recipient.get().isRegistered(), isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
@@ -1547,6 +1548,11 @@ public class ConversationActivity extends PassphraseRequiredActivity
.observe(this, actionablePendingGroupRequests -> updateReminders());
}
private void initializeGroupV1MigrationSuggestionsBanner() {
groupViewModel.getGroupV1MigrationSuggestions()
.observe(this, s -> updateReminders());
}
private ListenableFuture<Boolean> initializeDraftFromDatabase() {
SettableFuture<Boolean> future = new SettableFuture<>();
@@ -1702,6 +1708,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
protected void updateReminders() {
Optional<Reminder> inviteReminder = inviteReminderModel.getReminder();
Integer actionableRequestingMembers = groupViewModel.getActionableRequestingMembers().getValue();
List<RecipientId> gv1MigrationSuggestions = groupViewModel.getGroupV1MigrationSuggestions().getValue();
if (UnauthorizedReminder.isEligible(this)) {
reminderView.get().showReminder(new UnauthorizedReminder(this));
@@ -1726,25 +1733,32 @@ public class ConversationActivity extends PassphraseRequiredActivity
startActivity(ManagePendingAndRequestingMembersActivity.newIntent(this, getRecipient().getGroupId().get().requireV2()));
}
});
} else if (gv1MigrationSuggestions != null && gv1MigrationSuggestions.size() > 0 && recipient.get().isPushV2Group()) {
reminderView.get().showReminder(new GroupsV1MigrationSuggestionsReminder(this, gv1MigrationSuggestions));
reminderView.get().setOnActionClickListener(actionId -> {
if (actionId == R.id.reminder_action_gv1_suggestion_add_members) {
GroupsV1MigrationSuggestionsDialog.show(this, recipient.get().requireGroupId().requireV2(), gv1MigrationSuggestions);
} else if (actionId == R.id.reminder_action_gv1_suggestion_not_now) {
groupViewModel.onSuggestedMembersBannerDismissed(recipient.get().requireGroupId());
}
});
reminderView.get().setOnDismissListener(() -> {
});
} else if (reminderView.resolved()) {
reminderView.get().hide();
}
}
private void handleReminderAction(@IdRes int reminderActionId) {
switch (reminderActionId) {
case R.id.reminder_action_invite:
handleInviteLink();
reminderView.get().requestDismiss();
break;
case R.id.reminder_action_view_insights:
InsightsLauncher.showInsightsDashboard(getSupportFragmentManager());
break;
case R.id.reminder_action_update_now:
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(this);
break;
default:
throw new IllegalArgumentException("Unknown ID: " + reminderActionId);
if (reminderActionId == R.id.reminder_action_invite) {
handleInviteLink();
reminderView.get().requestDismiss();
} else if (reminderActionId == R.id.reminder_action_view_insights) {
InsightsLauncher.showInsightsDashboard(getSupportFragmentManager());
} else if (reminderActionId == R.id.reminder_action_update_now) {
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(this);
} else {
throw new IllegalArgumentException("Unknown ID: " + reminderActionId);
}
}

View File

@@ -4,6 +4,7 @@ import android.app.Application;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
@@ -15,6 +16,7 @@ import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
import org.thoughtcrime.securesms.groups.GroupChangeFailedException;
@@ -24,14 +26,17 @@ import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.profiles.spoofing.ReviewRecipient;
import org.thoughtcrime.securesms.profiles.spoofing.ReviewUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.AsynchronousCallback;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.SetUtil;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
final class ConversationGroupViewModel extends ViewModel {
@@ -40,6 +45,7 @@ final class ConversationGroupViewModel extends ViewModel {
private final LiveData<GroupDatabase.MemberLevel> selfMembershipLevel;
private final LiveData<Integer> actionableRequestingMembers;
private final LiveData<ReviewState> reviewState;
private final LiveData<List<RecipientId>> gv1MigrationSuggestions;
private ConversationGroupViewModel() {
this.liveRecipient = new MutableLiveData<>();
@@ -58,6 +64,7 @@ final class ConversationGroupViewModel extends ViewModel {
this.groupActiveState = Transformations.distinctUntilChanged(Transformations.map(groupRecord, ConversationGroupViewModel::mapToGroupActiveState));
this.selfMembershipLevel = Transformations.distinctUntilChanged(Transformations.map(groupRecord, ConversationGroupViewModel::mapToSelfMembershipLevel));
this.actionableRequestingMembers = Transformations.distinctUntilChanged(Transformations.map(groupRecord, ConversationGroupViewModel::mapToActionableRequestingMemberCount));
this.gv1MigrationSuggestions = Transformations.distinctUntilChanged(LiveDataUtil.mapAsync(groupRecord, ConversationGroupViewModel::mapToGroupV1MigrationSuggestions));
this.reviewState = LiveDataUtil.combineLatest(groupRecord,
duplicates,
(record, dups) -> dups.isEmpty()
@@ -70,6 +77,15 @@ final class ConversationGroupViewModel extends ViewModel {
liveRecipient.setValue(recipient);
}
void onSuggestedMembersBannerDismissed(@NonNull GroupId groupId) {
SignalExecutors.BOUNDED.execute(() -> {
if (groupId.isV2()) {
DatabaseFactory.getGroupDatabase(ApplicationDependencies.getApplication()).clearFormerV1Members(groupId.requireV2());
liveRecipient.postValue(liveRecipient.getValue());
}
});
}
/**
* The number of pending group join requests that can be actioned by this client.
*/
@@ -89,6 +105,10 @@ final class ConversationGroupViewModel extends ViewModel {
return reviewState;
}
@NonNull LiveData<List<RecipientId>> getGroupV1MigrationSuggestions() {
return gv1MigrationSuggestions;
}
private static @Nullable GroupRecord getGroupRecordForRecipient(@Nullable Recipient recipient) {
if (recipient != null && recipient.isGroup()) {
Application context = ApplicationDependencies.getApplication();
@@ -127,6 +147,24 @@ final class ConversationGroupViewModel extends ViewModel {
return record.memberLevel(Recipient.self());
}
@WorkerThread
private static List<RecipientId> mapToGroupV1MigrationSuggestions(@Nullable GroupRecord record) {
if (record == null) {
return Collections.emptyList();
}
Set<RecipientId> difference = SetUtil.difference(record.getFormerV1Members(), record.getMembers());
return Stream.of(Recipient.resolvedList(difference))
.filter(r -> r.hasUuid() &&
r.getGroupsV1MigrationCapability() == Recipient.Capability.SUPPORTED &&
r.getGroupsV2Capability() == Recipient.Capability.SUPPORTED &&
r.getProfileKey() != null &&
r.getRegistered() == RecipientDatabase.RegisteredState.REGISTERED)
.map(Recipient::getId)
.toList();
}
public static void onCancelJoinRequest(@NonNull Recipient recipient,
@NonNull AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason> callback)
{