Add the ability to add suggested members after a GV1 migration.
This commit is contained in:
parent
c4c32d80b2
commit
d307db8a95
8 changed files with 244 additions and 16 deletions
|
@ -0,0 +1,26 @@
|
||||||
|
package org.thoughtcrime.securesms.components.reminder;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a reminder to add anyone that might have been missed in GV1->GV2 migration.
|
||||||
|
*/
|
||||||
|
public class GroupsV1MigrationSuggestionsReminder extends Reminder {
|
||||||
|
public GroupsV1MigrationSuggestionsReminder(@NonNull Context context, @NonNull List<RecipientId> suggestions) {
|
||||||
|
super(null, context.getResources().getQuantityString(R.plurals.GroupsV1MigrationSuggestionsReminder_members_couldnt_be_added_to_the_new_group, suggestions.size(), suggestions.size()));
|
||||||
|
addAction(new Action(context.getResources().getQuantityString(R.plurals.GroupsV1MigrationSuggestionsReminder_add_members, suggestions.size()), R.id.reminder_action_gv1_suggestion_add_members));
|
||||||
|
addAction(new Action(context.getResources().getString(R.string.GroupsV1MigrationSuggestionsReminder_not_now), R.id.reminder_action_gv1_suggestion_not_now));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDismissable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,7 +69,6 @@ import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.core.content.pm.ShortcutInfoCompat;
|
import androidx.core.content.pm.ShortcutInfoCompat;
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||||
import androidx.core.graphics.drawable.DrawableCompat;
|
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.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.GroupsV1MigrationSuggestionsReminder;
|
||||||
import org.thoughtcrime.securesms.components.reminder.PendingGroupJoinRequestsReminder;
|
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;
|
||||||
|
@ -167,6 +167,7 @@ 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.invitesandrequests.ManagePendingAndRequestingMembersActivity;
|
||||||
import org.thoughtcrime.securesms.groups.ui.managegroup.ManageGroupActivity;
|
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.insights.InsightsLauncher;
|
||||||
import org.thoughtcrime.securesms.invites.InviteReminderModel;
|
import org.thoughtcrime.securesms.invites.InviteReminderModel;
|
||||||
import org.thoughtcrime.securesms.invites.InviteReminderRepository;
|
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.GroupShareProfileView;
|
||||||
import org.thoughtcrime.securesms.profiles.spoofing.ReviewBannerView;
|
import org.thoughtcrime.securesms.profiles.spoofing.ReviewBannerView;
|
||||||
import org.thoughtcrime.securesms.profiles.spoofing.ReviewCardDialogFragment;
|
import org.thoughtcrime.securesms.profiles.spoofing.ReviewCardDialogFragment;
|
||||||
import org.thoughtcrime.securesms.profiles.spoofing.ReviewUtil;
|
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment;
|
import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment;
|
||||||
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment;
|
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment;
|
||||||
|
@ -457,6 +457,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||||
initializeMentionsViewModel();
|
initializeMentionsViewModel();
|
||||||
initializeEnabledCheck();
|
initializeEnabledCheck();
|
||||||
initializePendingRequestsBanner();
|
initializePendingRequestsBanner();
|
||||||
|
initializeGroupV1MigrationSuggestionsBanner();
|
||||||
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) {
|
||||||
|
@ -1547,6 +1548,11 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||||
.observe(this, actionablePendingGroupRequests -> updateReminders());
|
.observe(this, actionablePendingGroupRequests -> updateReminders());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initializeGroupV1MigrationSuggestionsBanner() {
|
||||||
|
groupViewModel.getGroupV1MigrationSuggestions()
|
||||||
|
.observe(this, s -> updateReminders());
|
||||||
|
}
|
||||||
|
|
||||||
private ListenableFuture<Boolean> initializeDraftFromDatabase() {
|
private ListenableFuture<Boolean> initializeDraftFromDatabase() {
|
||||||
SettableFuture<Boolean> future = new SettableFuture<>();
|
SettableFuture<Boolean> future = new SettableFuture<>();
|
||||||
|
|
||||||
|
@ -1702,6 +1708,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();
|
Integer actionableRequestingMembers = groupViewModel.getActionableRequestingMembers().getValue();
|
||||||
|
List<RecipientId> gv1MigrationSuggestions = groupViewModel.getGroupV1MigrationSuggestions().getValue();
|
||||||
|
|
||||||
if (UnauthorizedReminder.isEligible(this)) {
|
if (UnauthorizedReminder.isEligible(this)) {
|
||||||
reminderView.get().showReminder(new UnauthorizedReminder(this));
|
reminderView.get().showReminder(new UnauthorizedReminder(this));
|
||||||
|
@ -1726,25 +1733,32 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||||
startActivity(ManagePendingAndRequestingMembersActivity.newIntent(this, getRecipient().getGroupId().get().requireV2()));
|
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()) {
|
} else if (reminderView.resolved()) {
|
||||||
reminderView.get().hide();
|
reminderView.get().hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleReminderAction(@IdRes int reminderActionId) {
|
private void handleReminderAction(@IdRes int reminderActionId) {
|
||||||
switch (reminderActionId) {
|
if (reminderActionId == R.id.reminder_action_invite) {
|
||||||
case R.id.reminder_action_invite:
|
handleInviteLink();
|
||||||
handleInviteLink();
|
reminderView.get().requestDismiss();
|
||||||
reminderView.get().requestDismiss();
|
} else if (reminderActionId == R.id.reminder_action_view_insights) {
|
||||||
break;
|
InsightsLauncher.showInsightsDashboard(getSupportFragmentManager());
|
||||||
case R.id.reminder_action_view_insights:
|
} else if (reminderActionId == R.id.reminder_action_update_now) {
|
||||||
InsightsLauncher.showInsightsDashboard(getSupportFragmentManager());
|
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(this);
|
||||||
break;
|
} else {
|
||||||
case R.id.reminder_action_update_now:
|
throw new IllegalArgumentException("Unknown ID: " + reminderActionId);
|
||||||
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(this);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unknown ID: " + reminderActionId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.app.Application;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.Transformations;
|
import androidx.lifecycle.Transformations;
|
||||||
|
@ -15,6 +16,7 @@ import com.annimon.stream.Stream;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
||||||
|
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
||||||
import org.thoughtcrime.securesms.groups.GroupChangeFailedException;
|
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.ReviewRecipient;
|
||||||
import org.thoughtcrime.securesms.profiles.spoofing.ReviewUtil;
|
import org.thoughtcrime.securesms.profiles.spoofing.ReviewUtil;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.AsynchronousCallback;
|
import org.thoughtcrime.securesms.util.AsynchronousCallback;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
|
import org.thoughtcrime.securesms.util.SetUtil;
|
||||||
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;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
final class ConversationGroupViewModel extends ViewModel {
|
final class ConversationGroupViewModel extends ViewModel {
|
||||||
|
|
||||||
|
@ -40,6 +45,7 @@ final class ConversationGroupViewModel extends ViewModel {
|
||||||
private final LiveData<GroupDatabase.MemberLevel> selfMembershipLevel;
|
private final LiveData<GroupDatabase.MemberLevel> selfMembershipLevel;
|
||||||
private final LiveData<Integer> actionableRequestingMembers;
|
private final LiveData<Integer> actionableRequestingMembers;
|
||||||
private final LiveData<ReviewState> reviewState;
|
private final LiveData<ReviewState> reviewState;
|
||||||
|
private final LiveData<List<RecipientId>> gv1MigrationSuggestions;
|
||||||
|
|
||||||
private ConversationGroupViewModel() {
|
private ConversationGroupViewModel() {
|
||||||
this.liveRecipient = new MutableLiveData<>();
|
this.liveRecipient = new MutableLiveData<>();
|
||||||
|
@ -58,6 +64,7 @@ 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));
|
this.actionableRequestingMembers = Transformations.distinctUntilChanged(Transformations.map(groupRecord, ConversationGroupViewModel::mapToActionableRequestingMemberCount));
|
||||||
|
this.gv1MigrationSuggestions = Transformations.distinctUntilChanged(LiveDataUtil.mapAsync(groupRecord, ConversationGroupViewModel::mapToGroupV1MigrationSuggestions));
|
||||||
this.reviewState = LiveDataUtil.combineLatest(groupRecord,
|
this.reviewState = LiveDataUtil.combineLatest(groupRecord,
|
||||||
duplicates,
|
duplicates,
|
||||||
(record, dups) -> dups.isEmpty()
|
(record, dups) -> dups.isEmpty()
|
||||||
|
@ -70,6 +77,15 @@ final class ConversationGroupViewModel extends ViewModel {
|
||||||
liveRecipient.setValue(recipient);
|
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.
|
* 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;
|
return reviewState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull LiveData<List<RecipientId>> getGroupV1MigrationSuggestions() {
|
||||||
|
return gv1MigrationSuggestions;
|
||||||
|
}
|
||||||
|
|
||||||
private static @Nullable GroupRecord getGroupRecordForRecipient(@Nullable Recipient recipient) {
|
private static @Nullable GroupRecord getGroupRecordForRecipient(@Nullable Recipient recipient) {
|
||||||
if (recipient != null && recipient.isGroup()) {
|
if (recipient != null && recipient.isGroup()) {
|
||||||
Application context = ApplicationDependencies.getApplication();
|
Application context = ApplicationDependencies.getApplication();
|
||||||
|
@ -127,6 +147,24 @@ final class ConversationGroupViewModel extends ViewModel {
|
||||||
return record.memberLevel(Recipient.self());
|
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,
|
public static void onCancelJoinRequest(@NonNull Recipient recipient,
|
||||||
@NonNull AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason> callback)
|
@NonNull AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason> callback)
|
||||||
{
|
{
|
||||||
|
|
|
@ -37,6 +37,7 @@ import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -162,6 +163,8 @@ public final class GroupDatabase extends Database {
|
||||||
values.putNull(FORMER_V1_MEMBERS);
|
values.putNull(FORMER_V1_MEMBERS);
|
||||||
|
|
||||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, GROUP_ID + " = ?", SqlUtil.buildArgs(id));
|
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, GROUP_ID + " = ?", SqlUtil.buildArgs(id));
|
||||||
|
|
||||||
|
Recipient.live(Recipient.externalGroupExact(context, id).getId()).refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<GroupRecord> getGroup(Cursor cursor) {
|
Optional<GroupRecord> getGroup(Cursor cursor) {
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
package org.thoughtcrime.securesms.groups.ui.migration;
|
||||||
|
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.DialogInterface.OnDismissListener;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupChangeFailedException;
|
||||||
|
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.MembershipNotSuitableForV2Exception;
|
||||||
|
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
|
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a list of members that got lost when migrating from a V1->V2 group, giving you the chance
|
||||||
|
* to add them back.
|
||||||
|
*/
|
||||||
|
public final class GroupsV1MigrationSuggestionsDialog {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(GroupsV1MigrationSuggestionsDialog.class);
|
||||||
|
|
||||||
|
private final FragmentActivity fragmentActivity;
|
||||||
|
private final GroupId.V2 groupId;
|
||||||
|
private final List<RecipientId> suggestions;
|
||||||
|
|
||||||
|
public static void show(@NonNull FragmentActivity activity,
|
||||||
|
@NonNull GroupId.V2 groupId,
|
||||||
|
@NonNull List<RecipientId> suggestions)
|
||||||
|
{
|
||||||
|
new GroupsV1MigrationSuggestionsDialog(activity, groupId, suggestions).display();
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupsV1MigrationSuggestionsDialog(@NonNull FragmentActivity activity,
|
||||||
|
@NonNull GroupId.V2 groupId,
|
||||||
|
@NonNull List<RecipientId> suggestions)
|
||||||
|
{
|
||||||
|
this.fragmentActivity = activity;
|
||||||
|
this.groupId = groupId;
|
||||||
|
this.suggestions = suggestions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void display() {
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(fragmentActivity)
|
||||||
|
.setTitle(fragmentActivity.getResources().getQuantityString(R.plurals.GroupsV1MigrationSuggestionsDialog_add_members_question, suggestions.size()))
|
||||||
|
.setMessage(fragmentActivity.getResources().getQuantityString(R.plurals.GroupsV1MigrationSuggestionsDialog_these_members_couldnt_be_automatically_added, suggestions.size()))
|
||||||
|
.setView(R.layout.dialog_group_members)
|
||||||
|
.setPositiveButton(fragmentActivity.getResources().getQuantityString(R.plurals.GroupsV1MigrationSuggestionsDialog_add_members, suggestions.size()), (d, i) -> onAddClicked(d))
|
||||||
|
.setNegativeButton(android.R.string.cancel, (d, i) -> d.dismiss())
|
||||||
|
.show();
|
||||||
|
|
||||||
|
GroupMemberListView memberListView = dialog.findViewById(R.id.list_members);
|
||||||
|
|
||||||
|
SimpleTask.run(() -> Recipient.resolvedList(suggestions),
|
||||||
|
memberListView::setDisplayOnlyMembers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAddClicked(@NonNull DialogInterface rootDialog) {
|
||||||
|
SimpleProgressDialog.DismissibleDialog progressDialog = SimpleProgressDialog.showDelayed(fragmentActivity, 300, 0);
|
||||||
|
SimpleTask.run(SignalExecutors.UNBOUNDED, () -> {
|
||||||
|
try {
|
||||||
|
GroupManager.addMembers(fragmentActivity, groupId.requirePush(), suggestions);
|
||||||
|
Log.i(TAG, "Successfully added members! Clearing former members.");
|
||||||
|
DatabaseFactory.getGroupDatabase(fragmentActivity).clearFormerV1Members(groupId);
|
||||||
|
return Result.SUCCESS;
|
||||||
|
} catch (IOException | GroupChangeBusyException e) {
|
||||||
|
Log.w(TAG, "Temporary failure.", e);
|
||||||
|
return Result.NETWORK_ERROR;
|
||||||
|
} catch (GroupNotAMemberException | GroupInsufficientRightsException | MembershipNotSuitableForV2Exception | GroupChangeFailedException e) {
|
||||||
|
Log.w(TAG, "Permanent failure! Clearing former members.", e);
|
||||||
|
DatabaseFactory.getGroupDatabase(fragmentActivity).clearFormerV1Members(groupId);
|
||||||
|
return Result.IMPOSSIBLE;
|
||||||
|
}
|
||||||
|
}, result -> {
|
||||||
|
progressDialog.dismiss();
|
||||||
|
rootDialog.dismiss();
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case NETWORK_ERROR:
|
||||||
|
Toast.makeText(fragmentActivity, fragmentActivity.getResources().getQuantityText(R.plurals.GroupsV1MigrationSuggestionsDialog_failed_to_add_members_try_again_later, suggestions.size()), Toast.LENGTH_SHORT).show();
|
||||||
|
break;
|
||||||
|
case IMPOSSIBLE:
|
||||||
|
Toast.makeText(fragmentActivity, fragmentActivity.getResources().getQuantityText(R.plurals.GroupsV1MigrationSuggestionsDialog_cannot_add_members, suggestions.size()), Toast.LENGTH_SHORT).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Result {
|
||||||
|
SUCCESS, NETWORK_ERROR, IMPOSSIBLE
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,12 +18,14 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
public class SignalExecutors {
|
public final class SignalExecutors {
|
||||||
|
|
||||||
public static final ExecutorService UNBOUNDED = Executors.newCachedThreadPool(new NumberedThreadFactory("signal-unbounded"));
|
public static final ExecutorService UNBOUNDED = Executors.newCachedThreadPool(new NumberedThreadFactory("signal-unbounded"));
|
||||||
public static final ExecutorService BOUNDED = Executors.newFixedThreadPool(getIdealThreadCount(), new NumberedThreadFactory("signal-bounded"));
|
public static final ExecutorService BOUNDED = Executors.newFixedThreadPool(getIdealThreadCount(), new NumberedThreadFactory("signal-bounded"));
|
||||||
public static final ExecutorService SERIAL = Executors.newSingleThreadExecutor(new NumberedThreadFactory("signal-serial"));
|
public static final ExecutorService SERIAL = Executors.newSingleThreadExecutor(new NumberedThreadFactory("signal-serial"));
|
||||||
|
|
||||||
|
private SignalExecutors() {}
|
||||||
|
|
||||||
public static ExecutorService newCachedSingleThreadExecutor(final String name) {
|
public static ExecutorService newCachedSingleThreadExecutor(final String name) {
|
||||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 15, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), r -> new Thread(r, name));
|
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 15, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), r -> new Thread(r, name));
|
||||||
executor.allowCoreThreadTimeOut(true);
|
executor.allowCoreThreadTimeOut(true);
|
||||||
|
|
|
@ -8,4 +8,7 @@
|
||||||
<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" />
|
<item name="reminder_action_review_join_requests" type="id" />
|
||||||
|
|
||||||
|
<item name="reminder_action_gv1_suggestion_not_now" type="id" />
|
||||||
|
<item name="reminder_action_gv1_suggestion_add_members" type="id" />
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -556,6 +556,39 @@
|
||||||
<item quantity="other">These members are not capable of joining New Groups, and have been removed from the group:</item>
|
<item quantity="other">These members are not capable of joining New Groups, and have been removed from the group:</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
|
<!-- GroupsV1MigrationSuggestionsReminder -->
|
||||||
|
<plurals name="GroupsV1MigrationSuggestionsReminder_members_couldnt_be_added_to_the_new_group">
|
||||||
|
<item quantity="one">%1$d member couldn\'t be re-added to the New Group. Do you want to add them now?</item>
|
||||||
|
<item quantity="other">%1$d members couldn\'t be re-added to the New Group. Do you want to add them now?</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="GroupsV1MigrationSuggestionsReminder_add_members">
|
||||||
|
<item quantity="one">Add member</item>
|
||||||
|
<item quantity="other">Add members</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="GroupsV1MigrationSuggestionsReminder_not_now">Not now</string>
|
||||||
|
|
||||||
|
<!-- GroupsV1MigrationSuggestionsDialog -->
|
||||||
|
<plurals name="GroupsV1MigrationSuggestionsDialog_add_members_question">
|
||||||
|
<item quantity="one">Add member?</item>
|
||||||
|
<item quantity="other">Add members?</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="GroupsV1MigrationSuggestionsDialog_these_members_couldnt_be_automatically_added">
|
||||||
|
<item quantity="one">This member couldn\'t be automatically added to the New Group when it was upgraded:</item>
|
||||||
|
<item quantity="other">These members couldn\'t be automatically added to the New Group when it was upgraded:</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="GroupsV1MigrationSuggestionsDialog_add_members">
|
||||||
|
<item quantity="one">Add member</item>
|
||||||
|
<item quantity="other">Add members</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="GroupsV1MigrationSuggestionsDialog_failed_to_add_members_try_again_later">
|
||||||
|
<item quantity="one">Failed to add member. Try again later.</item>
|
||||||
|
<item quantity="other">Failed to add members. Try again later.</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="GroupsV1MigrationSuggestionsDialog_cannot_add_members">
|
||||||
|
<item quantity="one">Cannot add member.</item>
|
||||||
|
<item quantity="other">Cannot add members.</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
<!-- LeaveGroupDialog -->
|
<!-- LeaveGroupDialog -->
|
||||||
<string name="LeaveGroupDialog_leave_group">Leave group?</string>
|
<string name="LeaveGroupDialog_leave_group">Leave group?</string>
|
||||||
<string name="LeaveGroupDialog_you_will_no_longer_be_able_to_send_or_receive_messages_in_this_group">You will no longer be able to send or receive messages in this group.</string>
|
<string name="LeaveGroupDialog_you_will_no_longer_be_able_to_send_or_receive_messages_in_this_group">You will no longer be able to send or receive messages in this group.</string>
|
||||||
|
|
Loading…
Add table
Reference in a new issue