diff --git a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java index 1d9c2ac17e..b6d7a6daa6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java @@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.conversation.ConversationMessage; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.groups.GroupId; +import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.recipients.Recipient; @@ -59,7 +60,7 @@ public interface BindableConversationItem extends Unbindable { void onVoiceNotePause(@NonNull Uri uri); void onVoiceNotePlay(@NonNull Uri uri, long messageId, double position); void onVoiceNoteSeekTo(@NonNull Uri uri, double position); - void onGroupMigrationLearnMoreClicked(@NonNull List pendingRecipients); + void onGroupMigrationLearnMoreClicked(@NonNull GroupMigrationMembershipChange membershipChange); void onJoinGroupCallClicked(); /** @return true if handled, false if you want to let the normal url handling continue */ diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 03648c6154..3edd4b272f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -89,6 +89,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupId; +import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInfoBottomSheetDialogFragment; import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob; @@ -1416,8 +1417,8 @@ public class ConversationFragment extends LoggingFragment { } @Override - public void onGroupMigrationLearnMoreClicked(@NonNull List pendingRecipients) { - GroupsV1MigrationInfoBottomSheetDialogFragment.showForLearnMore(requireFragmentManager(), pendingRecipients); + public void onGroupMigrationLearnMoreClicked(@NonNull GroupMigrationMembershipChange membershipChange) { + GroupsV1MigrationInfoBottomSheetDialogFragment.show(requireFragmentManager(), membershipChange); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java index b1494babdb..fb4e597235 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java @@ -178,7 +178,7 @@ public final class ConversationUpdateItem extends LinearLayout actionButton.setVisibility(VISIBLE); actionButton.setOnClickListener(v -> { if (batchSelected.isEmpty() && eventListener != null) { - eventListener.onGroupMigrationLearnMoreClicked(conversationMessage.getMessageRecord().getGroupV1MigrationEventInvites()); + eventListener.onGroupMigrationLearnMoreClicked(conversationMessage.getMessageRecord().getGroupV1MigrationMembershipChanges()); } }); } else if (conversationMessage.getMessageRecord().isGroupCall()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index 80f63538f6..7434b66507 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -24,11 +24,13 @@ import org.signal.zkgroup.groups.GroupMasterKey; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.groups.GroupAccessControl; import org.thoughtcrime.securesms.groups.GroupId; +import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.tracing.Trace; import org.thoughtcrime.securesms.util.CursorUtil; +import org.thoughtcrime.securesms.util.SetUtil; import org.thoughtcrime.securesms.util.SqlUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; @@ -486,11 +488,12 @@ public final class GroupDatabase extends Database { * Migrates a V1 group to a V2 group. * * @param decryptedGroup The state that represents the group on the server. This will be used to - * determine if we need to save our old membership list and stuff. It will - * *not* be stored as the definitive group state as-is. In order to ensure - * proper diffing, we modify this model to have our V1 membership. + * determine if we need to save our old membership list and stuff. */ - public @NonNull GroupId.V2 migrateToV2(@NonNull GroupId.V1 groupIdV1, @NonNull DecryptedGroup decryptedGroup) { + public @NonNull GroupId.V2 migrateToV2(long threadId, + @NonNull GroupId.V1 groupIdV1, + @NonNull DecryptedGroup decryptedGroup) + { SQLiteDatabase db = databaseHelper.getWritableDatabase(); GroupId.V2 groupIdV2 = groupIdV1.deriveV2MigrationGroupId(); GroupMasterKey groupMasterKey = groupIdV1.deriveV2MigrationMasterKey(); @@ -504,10 +507,13 @@ public final class GroupDatabase extends Database { contentValues.put(V2_MASTER_KEY, groupMasterKey.serialize()); contentValues.putNull(EXPECTED_V2_ID); - List newMembers = Stream.of(DecryptedGroupUtil.membersToUuidList(decryptedGroup.getMembersList())).map(u -> RecipientId.from(u, null)).toList(); - newMembers.addAll(Stream.of(DecryptedGroupUtil.pendingToUuidList(decryptedGroup.getPendingMembersList())).map(u -> RecipientId.from(u, null)).toList()); + List newMembers = Stream.of(DecryptedGroupUtil.membersToUuidList(decryptedGroup.getMembersList())).map(u -> RecipientId.from(u, null)).toList(); + List pendingMembers = Stream.of(DecryptedGroupUtil.pendingToUuidList(decryptedGroup.getPendingMembersList())).map(u -> RecipientId.from(u, null)).toList(); + List droppedMembers = new ArrayList<>(SetUtil.difference(record.getMembers(), newMembers)); - if (record.getMembers().size() > newMembers.size() || !newMembers.containsAll(record.getMembers())) { + newMembers.addAll(pendingMembers); + + if (droppedMembers.size() > 0) { contentValues.put(FORMER_V1_MEMBERS, RecipientId.toSerializedList(record.getMembers())); } @@ -519,7 +525,11 @@ public final class GroupDatabase extends Database { DatabaseFactory.getRecipientDatabase(context).updateGroupId(groupIdV1, groupIdV2); - update(groupMasterKey, updateToHaveV1Membership(decryptedGroup, record.getMembers())); + update(groupMasterKey, decryptedGroup); + + DatabaseFactory.getSmsDatabase(context).insertGroupV1MigrationEvents(record.getRecipientId(), + threadId, + new GroupMigrationMembershipChange(pendingMembers, droppedMembers)); db.setTransactionSuccessful(); } finally { @@ -529,23 +539,6 @@ public final class GroupDatabase extends Database { return groupIdV2; } - private static DecryptedGroup updateToHaveV1Membership(@NonNull DecryptedGroup serverGroup, @NonNull List v1Members) { - DecryptedGroup.Builder builder = serverGroup.toBuilder(); - builder.clearMembers(); - - for (RecipientId v1MemberId : v1Members) { - Recipient v1Member = Recipient.resolved(v1MemberId); - if (v1Member.hasUuid()) { - builder.addMembers(DecryptedMember.newBuilder() - .setUuid(UuidUtil.toByteString(v1Member.getUuid().get())) - .setRole(Member.Role.ADMINISTRATOR) - .build()); - } - } - - return builder.build(); - } - public void update(@NonNull GroupMasterKey groupMasterKey, @NonNull DecryptedGroup decryptedGroup) { update(GroupId.v2(groupMasterKey), decryptedGroup); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java index caaa2d9960..10432d62ce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java @@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.ReactionRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.databaseprotos.ReactionList; +import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.insights.InsightsConstants; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; @@ -148,7 +149,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns public abstract long insertMessageOutbox(@NonNull OutgoingMediaMessage message, long threadId, boolean forceSms, @Nullable SmsDatabase.InsertListener insertListener) throws MmsException; public abstract long insertMessageOutbox(@NonNull OutgoingMediaMessage message, long threadId, boolean forceSms, int defaultReceiptStatus, @Nullable SmsDatabase.InsertListener insertListener) throws MmsException; public abstract void insertProfileNameChangeMessages(@NonNull Recipient recipient, @NonNull String newProfileName, @NonNull String previousProfileName); - public abstract void insertGroupV1MigrationEvents(@NonNull RecipientId recipientId, long threadId, List pendingRecipients); + public abstract void insertGroupV1MigrationEvents(@NonNull RecipientId recipientId, long threadId, @NonNull GroupMigrationMembershipChange membershipChange); public abstract boolean deleteMessage(long messageId); abstract void deleteThread(long threadId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index c64fc6e1b6..3fbb0d71a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -54,6 +54,7 @@ import org.thoughtcrime.securesms.database.model.ReactionRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.jobs.TrimThreadJob; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.logging.Log; @@ -494,7 +495,10 @@ public class MmsDatabase extends MessageDatabase { } @Override - public void insertGroupV1MigrationEvents(@NonNull RecipientId recipientId, long threadId, List pendingRecipients) { + public void insertGroupV1MigrationEvents(@NonNull RecipientId recipientId, + long threadId, + @NonNull GroupMigrationMembershipChange membershipChange) + { throw new UnsupportedOperationException(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index f0254b0e83..f577b62929 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails; import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.jobs.TrimThreadJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; @@ -72,7 +73,6 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -862,11 +862,14 @@ public class SmsDatabase extends MessageDatabase { } @Override - public void insertGroupV1MigrationEvents(@NonNull RecipientId recipientId, long threadId, @NonNull List pendingRecipients) { + public void insertGroupV1MigrationEvents(@NonNull RecipientId recipientId, + long threadId, + @NonNull GroupMigrationMembershipChange membershipChange) + { insertGroupV1MigrationNotification(recipientId, threadId); - if (pendingRecipients.size() > 0) { - insertGroupV1MigrationEvent(recipientId, threadId, pendingRecipients); + if (!membershipChange.isEmpty()) { + insertGroupV1MigrationMembershipChanges(recipientId, threadId, membershipChange); } notifyConversationListeners(threadId); @@ -874,10 +877,13 @@ public class SmsDatabase extends MessageDatabase { } private void insertGroupV1MigrationNotification(@NonNull RecipientId recipientId, long threadId) { - insertGroupV1MigrationEvent(recipientId, threadId, Collections.emptyList()); + insertGroupV1MigrationMembershipChanges(recipientId, threadId, GroupMigrationMembershipChange.empty()); } - private void insertGroupV1MigrationEvent(@NonNull RecipientId recipientId, long threadId, @NonNull List pendingRecipients) { + private void insertGroupV1MigrationMembershipChanges(@NonNull RecipientId recipientId, + long threadId, + @NonNull GroupMigrationMembershipChange membershipChange) + { ContentValues values = new ContentValues(); values.put(RECIPIENT_ID, recipientId.serialize()); values.put(ADDRESS_DEVICE_ID, 1); @@ -887,8 +893,8 @@ public class SmsDatabase extends MessageDatabase { values.put(TYPE, Types.GV1_MIGRATION_TYPE); values.put(THREAD_ID, threadId); - if (pendingRecipients.size() > 0) { - values.put(BODY, RecipientId.toSerializedList(pendingRecipients)); + if (!membershipChange.isEmpty()) { + values.put(BODY, membershipChange.serialize()); } databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, values); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index 2e7021f3df..9c53b84a2d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -39,6 +39,7 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context; import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails; import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails; +import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.profiles.ProfileName; import org.thoughtcrime.securesms.recipients.Recipient; @@ -54,6 +55,7 @@ import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; import org.whispersystems.signalservice.api.util.UuidUtil; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -186,12 +188,7 @@ public abstract class MessageRecord extends DisplayRecord { if (isOutgoing()) return staticUpdateDescription(context.getString(R.string.SmsMessageRecord_secure_session_reset), R.drawable.ic_update_info_16); else return fromRecipient(getIndividualRecipient(), r-> context.getString(R.string.SmsMessageRecord_secure_session_reset_s, r.getDisplayName(context)), R.drawable.ic_update_info_16); } else if (isGroupV1MigrationEvent()) { - if (Util.isEmpty(getBody())) { - return staticUpdateDescription(context.getString(R.string.MessageRecord_this_group_was_updated_to_a_new_group), R.drawable.ic_update_group_role_16); - } else { - int count = getGroupV1MigrationEventInvites().size(); - return staticUpdateDescription(context.getResources().getQuantityString(R.plurals.MessageRecord_members_couldnt_be_added_to_the_new_group_and_have_been_invited, count, count), R.drawable.ic_update_group_add_16); - } + return getGroupMigrationEventDescription(context); } return null; @@ -288,6 +285,30 @@ public abstract class MessageRecord extends DisplayRecord { return context.getString(R.string.MessageRecord_changed_their_profile, getIndividualRecipient().getDisplayName(context)); } + private UpdateDescription getGroupMigrationEventDescription(@NonNull Context context) { + if (Util.isEmpty(getBody())) { + return staticUpdateDescription(context.getString(R.string.MessageRecord_this_group_was_updated_to_a_new_group), R.drawable.ic_update_group_role_16); + } else { + GroupMigrationMembershipChange change = getGroupV1MigrationMembershipChanges(); + List updates = new ArrayList<>(2); + + if (change.getPending().size() == 1 && change.getPending().get(0).equals(Recipient.self().getId())) { + updates.add(staticUpdateDescription(context.getString(R.string.MessageRecord_you_couldnt_be_added_to_the_new_group_and_have_been_invited_to_join), R.drawable.ic_update_group_add_16)); + } else if (change.getPending().size() > 0) { + int count = change.getPending().size(); + updates.add(staticUpdateDescription(context.getResources().getQuantityString(R.plurals.MessageRecord_members_couldnt_be_added_to_the_new_group_and_have_been_invited, count, count), R.drawable.ic_update_group_add_16)); + } + + if (change.getDropped().size() > 0) { + int count = change.getDropped().size(); + updates.add(staticUpdateDescription(context.getResources().getQuantityString(R.plurals.MessageRecord_members_couldnt_be_added_to_the_new_group_and_have_been_removed, count, count), R.drawable.ic_update_group_remove_16)); + } + + return UpdateDescription.concatWithNewLines(updates); + } + } + + public static @NonNull UpdateDescription getGroupCallUpdateDescription(@NonNull Context context, @NonNull String body) { GroupCallUpdateDetails groupCallUpdateDetails = GroupCallUpdateDetailsUtil.parse(body); @@ -380,11 +401,11 @@ public abstract class MessageRecord extends DisplayRecord { return SmsDatabase.Types.isGroupV1MigrationEvent(type); } - public @NonNull List getGroupV1MigrationEventInvites() { + public @NonNull GroupMigrationMembershipChange getGroupV1MigrationMembershipChanges() { if (isGroupV1MigrationEvent()) { - return RecipientId.fromSerializedList(getBody()); + return GroupMigrationMembershipChange.deserialize(getBody()); } else { - return Collections.emptyList(); + return GroupMigrationMembershipChange.empty(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMigrationMembershipChange.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMigrationMembershipChange.java new file mode 100644 index 0000000000..3a7498798f --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMigrationMembershipChange.java @@ -0,0 +1,60 @@ +package org.thoughtcrime.securesms.groups; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.thoughtcrime.securesms.database.model.MessageRecord; +import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.util.StringUtil; +import org.thoughtcrime.securesms.util.Util; + +import java.util.Collections; +import java.util.List; + +/** + * Describes a change in membership that results from a GV1->GV2 migration. + */ +public final class GroupMigrationMembershipChange { + private final List pending; + private final List dropped; + + public GroupMigrationMembershipChange(@NonNull List pending, @NonNull List dropped) { + this.pending = pending; + this.dropped = dropped; + } + + public static GroupMigrationMembershipChange empty() { + return new GroupMigrationMembershipChange(Collections.emptyList(), Collections.emptyList()); + } + + public static @NonNull GroupMigrationMembershipChange deserialize(@Nullable String serialized) { + if (Util.isEmpty(serialized)) { + return empty(); + } else { + String[] parts = serialized.split("\\|"); + if (parts.length == 1) { + return new GroupMigrationMembershipChange(RecipientId.fromSerializedList(parts[0]), Collections.emptyList()); + } else if (parts.length == 2) { + return new GroupMigrationMembershipChange(RecipientId.fromSerializedList(parts[0]), RecipientId.fromSerializedList(parts[1])); + } else { + return GroupMigrationMembershipChange.empty(); + } + } + } + + public @NonNull List getPending() { + return pending; + } + + public @NonNull List getDropped() { + return dropped; + } + + public @NonNull String serialize() { + return RecipientId.toSerializedList(pending) + "|" + RecipientId.toSerializedList(dropped); + } + + public boolean isEmpty() { + return pending.isEmpty() && dropped.isEmpty(); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV1MigrationUtil.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV1MigrationUtil.java index 00174a484f..ad661b0ae8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV1MigrationUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV1MigrationUtil.java @@ -26,6 +26,7 @@ import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; import java.io.Closeable; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import static org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor.LATEST; @@ -182,15 +183,8 @@ public final class GroupsV1MigrationUtil { return null; } - List pendingRecipients = Stream.of(DecryptedGroupUtil.pendingToUuidList(decryptedGroup.getPendingMembersList())) - .map(uuid -> Recipient.externalPush(context, uuid, null, false)) - .filterNot(Recipient::isSelf) - .map(Recipient::getId) - .toList(); - Log.i(TAG, "[Local] Migrating group over to the version we were added to: V" + decryptedGroup.getRevision()); - DatabaseFactory.getGroupDatabase(context).migrateToV2(gv1Id, decryptedGroup); - DatabaseFactory.getSmsDatabase(context).insertGroupV1MigrationEvents(groupRecipient.getId(), threadId, pendingRecipients); + DatabaseFactory.getGroupDatabase(context).migrateToV2(threadId, gv1Id, decryptedGroup); Log.i(TAG, "[Local] Applying all changes since V" + decryptedGroup.getRevision()); try { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/migration/GroupsV1MigrationInfoBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/migration/GroupsV1MigrationInfoBottomSheetDialogFragment.java index e82baed10f..08097b937f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/migration/GroupsV1MigrationInfoBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/migration/GroupsV1MigrationInfoBottomSheetDialogFragment.java @@ -15,13 +15,12 @@ import androidx.lifecycle.ViewModelProviders; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.groups.ui.GroupMemberListView; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.BottomSheetUtil; import org.thoughtcrime.securesms.util.ThemeUtil; -import java.util.ArrayList; import java.util.List; /** @@ -30,16 +29,19 @@ import java.util.List; */ public final class GroupsV1MigrationInfoBottomSheetDialogFragment extends BottomSheetDialogFragment { - private static final String KEY_PENDING = "pending"; + private static final String KEY_MEMBERSHIP_CHANGE = "membership_change"; private GroupsV1MigrationInfoViewModel viewModel; private GroupMemberListView pendingList; private TextView pendingTitle; private View pendingContainer; + private GroupMemberListView droppedList; + private TextView droppedTitle; + private View droppedContainer; - public static void showForLearnMore(@NonNull FragmentManager manager, @NonNull List pendingRecipients) { + public static void show(@NonNull FragmentManager manager, @NonNull GroupMigrationMembershipChange membershipChange) { Bundle args = new Bundle(); - args.putParcelableArrayList(KEY_PENDING, new ArrayList<>(pendingRecipients)); + args.putString(KEY_MEMBERSHIP_CHANGE, membershipChange.serialize()); GroupsV1MigrationInfoBottomSheetDialogFragment fragment = new GroupsV1MigrationInfoBottomSheetDialogFragment(); fragment.setArguments(args); @@ -66,12 +68,16 @@ public final class GroupsV1MigrationInfoBottomSheetDialogFragment extends Bottom this.pendingContainer = view.findViewById(R.id.gv1_learn_more_pending_container); this.pendingTitle = view.findViewById(R.id.gv1_learn_more_pending_title); this.pendingList = view.findViewById(R.id.gv1_learn_more_pending_list); + this.droppedContainer = view.findViewById(R.id.gv1_learn_more_dropped_container); + this.droppedTitle = view.findViewById(R.id.gv1_learn_more_dropped_title); + this.droppedList = view.findViewById(R.id.gv1_learn_more_dropped_list); //noinspection ConstantConditions - List pending = getArguments().getParcelableArrayList(KEY_PENDING); + GroupMigrationMembershipChange membershipChange = GroupMigrationMembershipChange.deserialize(getArguments().getString(KEY_MEMBERSHIP_CHANGE)); - this.viewModel = ViewModelProviders.of(this, new GroupsV1MigrationInfoViewModel.Factory(pending)).get(GroupsV1MigrationInfoViewModel.class); + this.viewModel = ViewModelProviders.of(this, new GroupsV1MigrationInfoViewModel.Factory(membershipChange)).get(GroupsV1MigrationInfoViewModel.class); viewModel.getPendingMembers().observe(getViewLifecycleOwner(), this::onPendingMembersChanged); + viewModel.getDroppedMembers().observe(getViewLifecycleOwner(), this::onDroppedMembersChanged); view.findViewById(R.id.gv1_learn_more_ok_button).setOnClickListener(v -> dismiss()); } @@ -82,7 +88,10 @@ public final class GroupsV1MigrationInfoBottomSheetDialogFragment extends Bottom } private void onPendingMembersChanged(@NonNull List pendingMembers) { - if (pendingMembers.size() > 0) { + if (pendingMembers.size() == 1 && pendingMembers.get(0).isSelf()) { + pendingContainer.setVisibility(View.VISIBLE); + pendingTitle.setText(R.string.GroupsV1MigrationLearnMore_you_will_need_to_accept_an_invite_to_join_this_group_again); + } else if (pendingMembers.size() > 0) { pendingContainer.setVisibility(View.VISIBLE); pendingTitle.setText(getResources().getQuantityText(R.plurals.GroupsV1MigrationLearnMore_these_members_will_need_to_accept_an_invite, pendingMembers.size())); pendingList.setDisplayOnlyMembers(pendingMembers); @@ -90,4 +99,14 @@ public final class GroupsV1MigrationInfoBottomSheetDialogFragment extends Bottom pendingContainer.setVisibility(View.GONE); } } + + private void onDroppedMembersChanged(@NonNull List droppedMembers) { + if (droppedMembers.size() > 0) { + droppedContainer.setVisibility(View.VISIBLE); + droppedTitle.setText(getResources().getQuantityText(R.plurals.GroupsV1MigrationLearnMore_these_members_were_removed_from_the_group, droppedMembers.size())); + droppedList.setDisplayOnlyMembers(droppedMembers); + } else { + droppedContainer.setVisibility(View.GONE); + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/migration/GroupsV1MigrationInfoViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/migration/GroupsV1MigrationInfoViewModel.java index 38e9686b18..d758a1eba2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/migration/GroupsV1MigrationInfoViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/migration/GroupsV1MigrationInfoViewModel.java @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; +import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; @@ -15,12 +16,18 @@ import java.util.List; class GroupsV1MigrationInfoViewModel extends ViewModel { private final MutableLiveData> pendingMembers; + private final MutableLiveData> droppedMembers; - private GroupsV1MigrationInfoViewModel(@NonNull List pendingMembers) { + private GroupsV1MigrationInfoViewModel(@NonNull GroupMigrationMembershipChange membershipChange) { this.pendingMembers = new MutableLiveData<>(); + this.droppedMembers = new MutableLiveData<>(); SignalExecutors.BOUNDED.execute(() -> { - this.pendingMembers.postValue(Recipient.resolvedList(pendingMembers)); + this.pendingMembers.postValue(Recipient.resolvedList(membershipChange.getPending())); + }); + + SignalExecutors.BOUNDED.execute(() -> { + this.droppedMembers.postValue(Recipient.resolvedList(membershipChange.getDropped())); }); } @@ -28,17 +35,21 @@ class GroupsV1MigrationInfoViewModel extends ViewModel { return pendingMembers; } + @NonNull LiveData> getDroppedMembers() { + return droppedMembers; + } + static class Factory extends ViewModelProvider.NewInstanceFactory { - private final List pendingMembers; + private final GroupMigrationMembershipChange membershipChange; - Factory(List pendingMembers) { - this.pendingMembers = pendingMembers; + Factory(@NonNull GroupMigrationMembershipChange membershipChange) { + this.membershipChange = membershipChange; } @Override public @NonNull T create(@NonNull Class modelClass) { - return modelClass.cast(new GroupsV1MigrationInfoViewModel(pendingMembers)); + return modelClass.cast(new GroupsV1MigrationInfoViewModel(membershipChange)); } } } diff --git a/app/src/main/res/layout/groupsv1_migration_bottom_sheet.xml b/app/src/main/res/layout/groupsv1_migration_bottom_sheet.xml index 1e48fa8b70..c964e1cff6 100644 --- a/app/src/main/res/layout/groupsv1_migration_bottom_sheet.xml +++ b/app/src/main/res/layout/groupsv1_migration_bottom_sheet.xml @@ -164,6 +164,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" + android:padding="20dp" style="@style/Button.Primary" android:text="@string/GroupsV1MigrationInitiation_upgrade_this_group" /> diff --git a/app/src/main/res/layout/groupsv1_migration_learn_more_bottom_sheet.xml b/app/src/main/res/layout/groupsv1_migration_learn_more_bottom_sheet.xml index 17cae578e8..c48a1f93a0 100644 --- a/app/src/main/res/layout/groupsv1_migration_learn_more_bottom_sheet.xml +++ b/app/src/main/res/layout/groupsv1_migration_learn_more_bottom_sheet.xml @@ -118,11 +118,53 @@ + + + + + + + + + + + + + +