kopia lustrzana https://github.com/ryukoposting/Signal-Android
962 wiersze
48 KiB
Java
962 wiersze
48 KiB
Java
package org.thoughtcrime.securesms.database.model;
|
|
|
|
import android.content.Context;
|
|
import android.text.Spannable;
|
|
import android.text.SpannableStringBuilder;
|
|
|
|
import androidx.annotation.DrawableRes;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.PluralsRes;
|
|
import androidx.annotation.StringRes;
|
|
import androidx.annotation.VisibleForTesting;
|
|
import androidx.core.content.ContextCompat;
|
|
|
|
import com.google.protobuf.ByteString;
|
|
|
|
import org.signal.core.util.StringUtil;
|
|
import org.signal.storageservice.protos.groups.AccessControl;
|
|
import org.signal.storageservice.protos.groups.Member;
|
|
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
|
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
|
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
|
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
|
import org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole;
|
|
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
|
import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval;
|
|
import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember;
|
|
import org.signal.storageservice.protos.groups.local.EnabledState;
|
|
import org.thoughtcrime.securesms.R;
|
|
import org.thoughtcrime.securesms.groups.GV2AccessLevelUtil;
|
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
|
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
|
import org.thoughtcrime.securesms.util.SpanUtil;
|
|
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
|
import org.whispersystems.signalservice.api.push.ServiceId;
|
|
import org.whispersystems.signalservice.api.push.ServiceIds;
|
|
import org.whispersystems.signalservice.api.util.UuidUtil;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.function.Consumer;
|
|
import java.util.stream.Collectors;
|
|
|
|
final class GroupsV2UpdateMessageProducer {
|
|
|
|
@NonNull private final Context context;
|
|
@NonNull private final ServiceIds selfIds;
|
|
@Nullable private final Consumer<RecipientId> recipientClickHandler;
|
|
|
|
GroupsV2UpdateMessageProducer(@NonNull Context context, @NonNull ServiceIds selfIds, @Nullable Consumer<RecipientId> recipientClickHandler) {
|
|
this.context = context;
|
|
this.selfIds = selfIds;
|
|
this.recipientClickHandler = recipientClickHandler;
|
|
}
|
|
|
|
/**
|
|
* Describes a group that is new to you, use this when there is no available change record.
|
|
* <p>
|
|
* Invitation and revision 0 groups are the most common use cases for this.
|
|
* <p>
|
|
* When invited, it's possible there's no change available.
|
|
* <p>
|
|
* When the revision of the group is 0, the change is very noisy and only the editor is useful.
|
|
*/
|
|
UpdateDescription describeNewGroup(@NonNull DecryptedGroup group, @NonNull DecryptedGroupChange decryptedGroupChange) {
|
|
Optional<DecryptedPendingMember> selfPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfIds.getAci().uuid());
|
|
if (!selfPending.isPresent() && selfIds.getPni() != null) {
|
|
selfPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfIds.getPni().uuid());
|
|
}
|
|
|
|
if (selfPending.isPresent()) {
|
|
return updateDescription(R.string.MessageRecord_s_invited_you_to_the_group, selfPending.get().getAddedByUuid(), R.drawable.ic_update_group_add_16);
|
|
}
|
|
|
|
ByteString foundingMemberUuid = decryptedGroupChange.getEditor();
|
|
if (!foundingMemberUuid.isEmpty()) {
|
|
if (selfIds.matches(foundingMemberUuid)) {
|
|
return updateDescription(context.getString(R.string.MessageRecord_you_created_the_group), R.drawable.ic_update_group_16);
|
|
} else {
|
|
return updateDescription(R.string.MessageRecord_s_added_you, foundingMemberUuid, R.drawable.ic_update_group_add_16);
|
|
}
|
|
}
|
|
|
|
if (DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfIds.getAci().uuid()).isPresent() ||
|
|
(selfIds.getPni() != null && DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfIds.getPni().uuid()).isPresent()))
|
|
{
|
|
return updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group), R.drawable.ic_update_group_add_16);
|
|
} else {
|
|
return updateDescription(context.getString(R.string.MessageRecord_group_updated), R.drawable.ic_update_group_16);
|
|
}
|
|
}
|
|
|
|
List<UpdateDescription> describeChanges(@Nullable DecryptedGroup previousGroupState, @NonNull DecryptedGroupChange change) {
|
|
if (DecryptedGroup.getDefaultInstance().equals(previousGroupState)) {
|
|
previousGroupState = null;
|
|
}
|
|
|
|
List<UpdateDescription> updates = new LinkedList<>();
|
|
|
|
if (change.getEditor().isEmpty() || UuidUtil.UNKNOWN_UUID.equals(UuidUtil.fromByteString(change.getEditor()))) {
|
|
describeUnknownEditorMemberAdditions(change, updates);
|
|
|
|
describeUnknownEditorModifyMemberRoles(change, updates);
|
|
describeUnknownEditorInvitations(change, updates);
|
|
describeUnknownEditorRevokedInvitations(change, updates);
|
|
describeUnknownEditorPromotePending(change, updates);
|
|
describeUnknownEditorNewTitle(change, updates);
|
|
describeUnknownEditorNewDescription(change, updates);
|
|
describeUnknownEditorNewAvatar(change, updates);
|
|
describeUnknownEditorNewTimer(change, updates);
|
|
describeUnknownEditorNewAttributeAccess(change, updates);
|
|
describeUnknownEditorNewMembershipAccess(change, updates);
|
|
describeUnknownEditorNewGroupInviteLinkAccess(previousGroupState, change, updates);
|
|
describeRequestingMembers(change, updates);
|
|
describeUnknownEditorRequestingMembersApprovals(change, updates);
|
|
describeUnknownEditorRequestingMembersDeletes(change, updates);
|
|
describeUnknownEditorAnnouncementGroupChange(change, updates);
|
|
describeUnknownEditorPromotePendingPniAci(change, updates);
|
|
|
|
describeUnknownEditorMemberRemovals(change, updates);
|
|
|
|
if (updates.isEmpty()) {
|
|
describeUnknownEditorUnknownChange(updates);
|
|
}
|
|
|
|
} else {
|
|
describeMemberAdditions(change, updates);
|
|
|
|
describeModifyMemberRoles(change, updates);
|
|
describeInvitations(change, updates);
|
|
describeRevokedInvitations(change, updates);
|
|
describePromotePending(change, updates);
|
|
describeNewTitle(change, updates);
|
|
describeNewDescription(change, updates);
|
|
describeNewAvatar(change, updates);
|
|
describeNewTimer(change, updates);
|
|
describeNewAttributeAccess(change, updates);
|
|
describeNewMembershipAccess(change, updates);
|
|
describeNewGroupInviteLinkAccess(previousGroupState, change, updates);
|
|
describeRequestingMembers(change, updates);
|
|
describeRequestingMembersApprovals(change, updates);
|
|
describeRequestingMembersDeletes(change, updates);
|
|
describeAnnouncementGroupChange(change, updates);
|
|
describePromotePendingPniAci(change, updates);
|
|
|
|
describeMemberRemovals(change, updates);
|
|
|
|
if (updates.isEmpty()) {
|
|
describeUnknownChange(change, updates);
|
|
}
|
|
}
|
|
|
|
return updates;
|
|
}
|
|
|
|
/**
|
|
* Handles case of future protocol versions where we don't know what has changed.
|
|
*/
|
|
private void describeUnknownChange(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_updated_group), R.drawable.ic_update_group_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_updated_group, change.getEditor(), R.drawable.ic_update_group_16));
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorUnknownChange(@NonNull List<UpdateDescription> updates) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_was_updated), R.drawable.ic_update_group_16));
|
|
}
|
|
|
|
private void describeMemberAdditions(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
for (DecryptedMember member : change.getNewMembersList()) {
|
|
boolean newMemberIsYou = selfIds.matches(member.getUuid());
|
|
|
|
if (editorIsYou) {
|
|
if (newMemberIsYou) {
|
|
updates.add(0, updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group_via_the_group_link), R.drawable.ic_update_group_accept_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_you_added_s, member.getUuid(), R.drawable.ic_update_group_add_16));
|
|
}
|
|
} else {
|
|
if (newMemberIsYou) {
|
|
updates.add(0, updateDescription(R.string.MessageRecord_s_added_you, change.getEditor(), R.drawable.ic_update_group_add_16));
|
|
} else {
|
|
if (member.getUuid().equals(change.getEditor())) {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_joined_the_group_via_the_group_link, member.getUuid(), R.drawable.ic_update_group_accept_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_added_s, change.getEditor(), member.getUuid(), R.drawable.ic_update_group_add_16));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorMemberAdditions(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
for (DecryptedMember member : change.getNewMembersList()) {
|
|
boolean newMemberIsYou = selfIds.matches(member.getUuid());
|
|
|
|
if (newMemberIsYou) {
|
|
updates.add(0, updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group), R.drawable.ic_update_group_add_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_joined_the_group, member.getUuid(), R.drawable.ic_update_group_add_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeMemberRemovals(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
for (ByteString member : change.getDeleteMembersList()) {
|
|
boolean removedMemberIsYou = selfIds.matches(member);
|
|
|
|
if (editorIsYou) {
|
|
if (removedMemberIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_left_the_group), R.drawable.ic_update_group_leave_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_you_removed_s, member, R.drawable.ic_update_group_remove_16));
|
|
}
|
|
} else {
|
|
if (removedMemberIsYou) {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_removed_you_from_the_group, change.getEditor(), R.drawable.ic_update_group_remove_16));
|
|
} else {
|
|
if (member.equals(change.getEditor())) {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_left_the_group, member, R.drawable.ic_update_group_leave_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_removed_s, change.getEditor(), member, R.drawable.ic_update_group_remove_16));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorMemberRemovals(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
for (ByteString member : change.getDeleteMembersList()) {
|
|
boolean removedMemberIsYou = selfIds.matches(member);
|
|
|
|
if (removedMemberIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_are_no_longer_in_the_group), R.drawable.ic_update_group_leave_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_is_no_longer_in_the_group, member, R.drawable.ic_update_group_leave_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeModifyMemberRoles(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
for (DecryptedModifyMemberRole roleChange : change.getModifyMemberRolesList()) {
|
|
boolean changedMemberIsYou = selfIds.matches(roleChange.getUuid());
|
|
if (roleChange.getRole() == Member.Role.ADMINISTRATOR) {
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(R.string.MessageRecord_you_made_s_an_admin, roleChange.getUuid(), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
if (changedMemberIsYou) {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_made_you_an_admin, change.getEditor(), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_made_s_an_admin, change.getEditor(), roleChange.getUuid(), R.drawable.ic_update_group_role_16));
|
|
|
|
}
|
|
}
|
|
} else {
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(R.string.MessageRecord_you_revoked_admin_privileges_from_s, roleChange.getUuid(), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
if (changedMemberIsYou) {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_revoked_your_admin_privileges, change.getEditor(), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_revoked_admin_privileges_from_s, change.getEditor(), roleChange.getUuid(), R.drawable.ic_update_group_role_16));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorModifyMemberRoles(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
for (DecryptedModifyMemberRole roleChange : change.getModifyMemberRolesList()) {
|
|
boolean changedMemberIsYou = selfIds.matches(roleChange.getUuid());
|
|
|
|
if (roleChange.getRole() == Member.Role.ADMINISTRATOR) {
|
|
if (changedMemberIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_are_now_an_admin), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_is_now_an_admin, roleChange.getUuid(), R.drawable.ic_update_group_role_16));
|
|
}
|
|
} else {
|
|
if (changedMemberIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_are_no_longer_an_admin), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_is_no_longer_an_admin, roleChange.getUuid(), R.drawable.ic_update_group_role_16));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeInvitations(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
int notYouInviteCount = 0;
|
|
|
|
for (DecryptedPendingMember invitee : change.getNewPendingMembersList()) {
|
|
boolean newMemberIsYou = selfIds.matches(invitee.getUuid());
|
|
|
|
if (newMemberIsYou) {
|
|
updates.add(0, updateDescription(R.string.MessageRecord_s_invited_you_to_the_group, change.getEditor(), R.drawable.ic_update_group_add_16));
|
|
} else {
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(R.string.MessageRecord_you_invited_s_to_the_group, invitee.getUuid(), R.drawable.ic_update_group_add_16));
|
|
} else {
|
|
notYouInviteCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (notYouInviteCount > 0) {
|
|
updates.add(updateDescription(R.plurals.MessageRecord_s_invited_members, notYouInviteCount, change.getEditor(), notYouInviteCount, R.drawable.ic_update_group_add_16));
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorInvitations(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
int notYouInviteCount = 0;
|
|
|
|
for (DecryptedPendingMember invitee : change.getNewPendingMembersList()) {
|
|
boolean newMemberIsYou = selfIds.matches(invitee.getUuid());
|
|
|
|
if (newMemberIsYou) {
|
|
UUID uuid = UuidUtil.fromByteStringOrUnknown(invitee.getAddedByUuid());
|
|
|
|
if (UuidUtil.UNKNOWN_UUID.equals(uuid)) {
|
|
updates.add(0, updateDescription(context.getString(R.string.MessageRecord_you_were_invited_to_the_group), R.drawable.ic_update_group_add_16));
|
|
} else {
|
|
updates.add(0, updateDescription(R.string.MessageRecord_s_invited_you_to_the_group, invitee.getAddedByUuid(), R.drawable.ic_update_group_add_16));
|
|
}
|
|
} else {
|
|
notYouInviteCount++;
|
|
}
|
|
}
|
|
|
|
if (notYouInviteCount > 0) {
|
|
updates.add(updateDescription(context.getResources().getQuantityString(R.plurals.MessageRecord_d_people_were_invited_to_the_group, notYouInviteCount, notYouInviteCount), R.drawable.ic_update_group_add_16));
|
|
}
|
|
}
|
|
|
|
private void describeRevokedInvitations(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
int notDeclineCount = 0;
|
|
|
|
for (DecryptedPendingMemberRemoval invitee : change.getDeletePendingMembersList()) {
|
|
boolean decline = invitee.getUuid().equals(change.getEditor());
|
|
if (decline) {
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_declined_the_invitation_to_the_group), R.drawable.ic_update_group_decline_16));
|
|
} else {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_someone_declined_an_invitation_to_the_group), R.drawable.ic_update_group_decline_16));
|
|
}
|
|
} else if (selfIds.matches(invitee.getUuid())) {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_revoked_your_invitation_to_the_group, change.getEditor(), R.drawable.ic_update_group_decline_16));
|
|
} else {
|
|
notDeclineCount++;
|
|
}
|
|
}
|
|
|
|
if (notDeclineCount > 0) {
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(context.getResources().getQuantityString(R.plurals.MessageRecord_you_revoked_invites, notDeclineCount, notDeclineCount), R.drawable.ic_update_group_decline_16));
|
|
} else {
|
|
updates.add(updateDescription(R.plurals.MessageRecord_s_revoked_invites, notDeclineCount, change.getEditor(), notDeclineCount, R.drawable.ic_update_group_decline_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorRevokedInvitations(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
int notDeclineCount = 0;
|
|
|
|
for (DecryptedPendingMemberRemoval invitee : change.getDeletePendingMembersList()) {
|
|
boolean inviteeWasYou = selfIds.matches(invitee.getUuid());
|
|
|
|
if (inviteeWasYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_an_admin_revoked_your_invitation_to_the_group), R.drawable.ic_update_group_decline_16));
|
|
} else {
|
|
notDeclineCount++;
|
|
}
|
|
}
|
|
|
|
if (notDeclineCount > 0) {
|
|
updates.add(updateDescription(context.getResources().getQuantityString(R.plurals.MessageRecord_d_invitations_were_revoked, notDeclineCount, notDeclineCount), R.drawable.ic_update_group_decline_16));
|
|
}
|
|
}
|
|
|
|
private void describePromotePending(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
for (DecryptedMember newMember : change.getPromotePendingMembersList()) {
|
|
ByteString uuid = newMember.getUuid();
|
|
boolean newMemberIsYou = selfIds.matches(uuid);
|
|
|
|
if (editorIsYou) {
|
|
if (newMemberIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_accepted_invite), R.drawable.ic_update_group_accept_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_you_added_invited_member_s, uuid, R.drawable.ic_update_group_add_16));
|
|
}
|
|
} else {
|
|
if (newMemberIsYou) {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_added_you, change.getEditor(), R.drawable.ic_update_group_add_16));
|
|
} else {
|
|
if (uuid.equals(change.getEditor())) {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_accepted_invite, uuid, R.drawable.ic_update_group_accept_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_added_invited_member_s, change.getEditor(), uuid, R.drawable.ic_update_group_add_16));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorPromotePending(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
for (DecryptedMember newMember : change.getPromotePendingMembersList()) {
|
|
ByteString uuid = newMember.getUuid();
|
|
boolean newMemberIsYou = selfIds.matches(uuid);
|
|
|
|
if (newMemberIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group), R.drawable.ic_update_group_add_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_joined_the_group, uuid, R.drawable.ic_update_group_add_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeNewTitle(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
if (change.hasNewTitle()) {
|
|
String newTitle = StringUtil.isolateBidi(change.getNewTitle().getValue());
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_changed_the_group_name_to_s, newTitle), R.drawable.ic_update_group_name_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_changed_the_group_name_to_s, change.getEditor(), newTitle, R.drawable.ic_update_group_name_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeNewDescription(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
if (change.hasNewDescription()) {
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_changed_the_group_description), R.drawable.ic_update_group_name_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_changed_the_group_description, change.getEditor(), R.drawable.ic_update_group_name_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorNewTitle(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
if (change.hasNewTitle()) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_name_has_changed_to_s, StringUtil.isolateBidi(change.getNewTitle().getValue())), R.drawable.ic_update_group_name_16));
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorNewDescription(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
if (change.hasNewDescription()) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_description_has_changed), R.drawable.ic_update_group_name_16));
|
|
}
|
|
}
|
|
|
|
private void describeNewAvatar(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
if (change.hasNewAvatar()) {
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_changed_the_group_avatar), R.drawable.ic_update_group_avatar_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_changed_the_group_avatar, change.getEditor(), R.drawable.ic_update_group_avatar_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorNewAvatar(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
if (change.hasNewAvatar()) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_group_avatar_has_been_changed), R.drawable.ic_update_group_avatar_16));
|
|
}
|
|
}
|
|
|
|
void describeNewTimer(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
if (change.hasNewTimer()) {
|
|
String time = ExpirationUtil.getExpirationDisplayValue(context, change.getNewTimer().getDuration());
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_set_disappearing_message_time_to_s, time), R.drawable.ic_update_timer_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_set_disappearing_message_time_to_s, change.getEditor(), time, R.drawable.ic_update_timer_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorNewTimer(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
if (change.hasNewTimer()) {
|
|
String time = ExpirationUtil.getExpirationDisplayValue(context, change.getNewTimer().getDuration());
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_disappearing_message_time_set_to_s, time), R.drawable.ic_update_timer_16));
|
|
}
|
|
}
|
|
|
|
private void describeNewAttributeAccess(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
if (change.getNewAttributeAccess() != AccessControl.AccessRequired.UNKNOWN) {
|
|
String accessLevel = GV2AccessLevelUtil.toString(context, change.getNewAttributeAccess());
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_changed_who_can_edit_group_info_to_s, accessLevel), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_changed_who_can_edit_group_info_to_s, change.getEditor(), accessLevel, R.drawable.ic_update_group_role_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorNewAttributeAccess(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
if (change.getNewAttributeAccess() != AccessControl.AccessRequired.UNKNOWN) {
|
|
String accessLevel = GV2AccessLevelUtil.toString(context, change.getNewAttributeAccess());
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_who_can_edit_group_info_has_been_changed_to_s, accessLevel), R.drawable.ic_update_group_role_16));
|
|
}
|
|
}
|
|
|
|
private void describeNewMembershipAccess(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
if (change.getNewMemberAccess() != AccessControl.AccessRequired.UNKNOWN) {
|
|
String accessLevel = GV2AccessLevelUtil.toString(context, change.getNewMemberAccess());
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_changed_who_can_edit_group_membership_to_s, accessLevel), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_changed_who_can_edit_group_membership_to_s, change.getEditor(), accessLevel, R.drawable.ic_update_group_role_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorNewMembershipAccess(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
if (change.getNewMemberAccess() != AccessControl.AccessRequired.UNKNOWN) {
|
|
String accessLevel = GV2AccessLevelUtil.toString(context, change.getNewMemberAccess());
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_who_can_edit_group_membership_has_been_changed_to_s, accessLevel), R.drawable.ic_update_group_role_16));
|
|
}
|
|
}
|
|
|
|
private void describeNewGroupInviteLinkAccess(@Nullable DecryptedGroup previousGroupState,
|
|
@NonNull DecryptedGroupChange change,
|
|
@NonNull List<UpdateDescription> updates)
|
|
{
|
|
AccessControl.AccessRequired previousAccessControl = null;
|
|
|
|
if (previousGroupState != null) {
|
|
previousAccessControl = previousGroupState.getAccessControl().getAddFromInviteLink();
|
|
}
|
|
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
boolean groupLinkEnabled = false;
|
|
|
|
switch (change.getNewInviteLinkAccess()) {
|
|
case ANY:
|
|
groupLinkEnabled = true;
|
|
if (editorIsYou) {
|
|
if (previousAccessControl == AccessControl.AccessRequired.ADMINISTRATOR) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_off_admin_approval_for_the_group_link), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_off), R.drawable.ic_update_group_role_16));
|
|
}
|
|
} else {
|
|
if (previousAccessControl == AccessControl.AccessRequired.ADMINISTRATOR) {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_turned_off_admin_approval_for_the_group_link, change.getEditor(), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_off, change.getEditor(), R.drawable.ic_update_group_role_16));
|
|
}
|
|
}
|
|
break;
|
|
case ADMINISTRATOR:
|
|
groupLinkEnabled = true;
|
|
if (editorIsYou) {
|
|
if (previousAccessControl == AccessControl.AccessRequired.ANY) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_admin_approval_for_the_group_link), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_on), R.drawable.ic_update_group_role_16));
|
|
}
|
|
} else {
|
|
if (previousAccessControl == AccessControl.AccessRequired.ANY) {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_turned_on_admin_approval_for_the_group_link, change.getEditor(), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_on, change.getEditor(), R.drawable.ic_update_group_role_16));
|
|
}
|
|
}
|
|
break;
|
|
case UNSATISFIABLE:
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_off_the_group_link), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_turned_off_the_group_link, change.getEditor(), R.drawable.ic_update_group_role_16));
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!groupLinkEnabled && change.getNewInviteLinkPassword().size() > 0) {
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_reset_the_group_link), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_reset_the_group_link, change.getEditor(), R.drawable.ic_update_group_role_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorNewGroupInviteLinkAccess(@Nullable DecryptedGroup previousGroupState,
|
|
@NonNull DecryptedGroupChange change,
|
|
@NonNull List<UpdateDescription> updates)
|
|
{
|
|
AccessControl.AccessRequired previousAccessControl = null;
|
|
|
|
if (previousGroupState != null) {
|
|
previousAccessControl = previousGroupState.getAccessControl().getAddFromInviteLink();
|
|
}
|
|
|
|
switch (change.getNewInviteLinkAccess()) {
|
|
case ANY:
|
|
if (previousAccessControl == AccessControl.AccessRequired.ADMINISTRATOR) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_admin_approval_for_the_group_link_has_been_turned_off), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_off), R.drawable.ic_update_group_role_16));
|
|
}
|
|
break;
|
|
case ADMINISTRATOR:
|
|
if (previousAccessControl == AccessControl.AccessRequired.ANY) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_admin_approval_for_the_group_link_has_been_turned_on), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_on), R.drawable.ic_update_group_role_16));
|
|
}
|
|
break;
|
|
case UNSATISFIABLE:
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_off), R.drawable.ic_update_group_role_16));
|
|
break;
|
|
}
|
|
|
|
if (change.getNewInviteLinkPassword().size() > 0) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_reset), R.drawable.ic_update_group_role_16));
|
|
}
|
|
}
|
|
|
|
private void describeRequestingMembers(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
Set<ByteString> deleteRequestingUuids = new HashSet<>(change.getDeleteRequestingMembersList());
|
|
|
|
for (DecryptedRequestingMember member : change.getNewRequestingMembersList()) {
|
|
boolean requestingMemberIsYou = selfIds.matches(member.getUuid());
|
|
|
|
if (requestingMemberIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_sent_a_request_to_join_the_group), R.drawable.ic_update_group_16));
|
|
} else {
|
|
if (deleteRequestingUuids.contains(member.getUuid())) {
|
|
updates.add(updateDescription(R.plurals.MessageRecord_s_requested_and_cancelled_their_request_to_join_via_the_group_link,
|
|
change.getDeleteRequestingMembersCount(),
|
|
member.getUuid(),
|
|
change.getDeleteRequestingMembersCount(),
|
|
R.drawable.ic_update_group_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_requested_to_join_via_the_group_link, member.getUuid(), R.drawable.ic_update_group_16));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeRequestingMembersApprovals(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
for (DecryptedApproveMember requestingMember : change.getPromoteRequestingMembersList()) {
|
|
boolean requestingMemberIsYou = selfIds.matches(requestingMember.getUuid());
|
|
|
|
if (requestingMemberIsYou) {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_approved_your_request_to_join_the_group, change.getEditor(), R.drawable.ic_update_group_accept_16));
|
|
} else {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(R.string.MessageRecord_you_approved_a_request_to_join_the_group_from_s, requestingMember.getUuid(), R.drawable.ic_update_group_accept_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_approved_a_request_to_join_the_group_from_s, change.getEditor(), requestingMember.getUuid(), R.drawable.ic_update_group_accept_16));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorRequestingMembersApprovals(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
for (DecryptedApproveMember requestingMember : change.getPromoteRequestingMembersList()) {
|
|
boolean requestingMemberIsYou = selfIds.matches(requestingMember.getUuid());
|
|
|
|
if (requestingMemberIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_your_request_to_join_the_group_has_been_approved), R.drawable.ic_update_group_accept_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_a_request_to_join_the_group_from_s_has_been_approved, requestingMember.getUuid(), R.drawable.ic_update_group_accept_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeRequestingMembersDeletes(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
Set<ByteString> newRequestingUuids = change.getNewRequestingMembersList().stream().map(DecryptedRequestingMember::getUuid).collect(Collectors.toSet());
|
|
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
for (ByteString requestingMember : change.getDeleteRequestingMembersList()) {
|
|
if (newRequestingUuids.contains(requestingMember)) {
|
|
continue;
|
|
}
|
|
|
|
boolean requestingMemberIsYou = selfIds.matches(requestingMember);
|
|
|
|
if (requestingMemberIsYou) {
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_canceled_your_request_to_join_the_group), R.drawable.ic_update_group_decline_16));
|
|
} else {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_your_request_to_join_the_group_has_been_denied_by_an_admin), R.drawable.ic_update_group_decline_16));
|
|
}
|
|
} else {
|
|
boolean editorIsCanceledMember = change.getEditor().equals(requestingMember);
|
|
|
|
if (editorIsCanceledMember) {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_canceled_their_request_to_join_the_group, requestingMember, R.drawable.ic_update_group_decline_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_denied_a_request_to_join_the_group_from_s, change.getEditor(), requestingMember, R.drawable.ic_update_group_decline_16));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorRequestingMembersDeletes(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
for (ByteString requestingMember : change.getDeleteRequestingMembersList()) {
|
|
boolean requestingMemberIsYou = selfIds.matches(requestingMember);
|
|
|
|
if (requestingMemberIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_your_request_to_join_the_group_has_been_denied_by_an_admin), R.drawable.ic_update_group_decline_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_a_request_to_join_the_group_from_s_has_been_denied, requestingMember, R.drawable.ic_update_group_decline_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeAnnouncementGroupChange(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
if (change.getNewIsAnnouncementGroup() == EnabledState.ENABLED) {
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_allow_only_admins_to_send), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_allow_only_admins_to_send, change.getEditor(), R.drawable.ic_update_group_role_16));
|
|
}
|
|
} else if (change.getNewIsAnnouncementGroup() == EnabledState.DISABLED) {
|
|
if (editorIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_allow_all_members_to_send), R.drawable.ic_update_group_role_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_allow_all_members_to_send, change.getEditor(), R.drawable.ic_update_group_role_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorAnnouncementGroupChange(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
if (change.getNewIsAnnouncementGroup() == EnabledState.ENABLED) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_allow_only_admins_to_send), R.drawable.ic_update_group_role_16));
|
|
} else if (change.getNewIsAnnouncementGroup() == EnabledState.DISABLED) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_allow_all_members_to_send), R.drawable.ic_update_group_role_16));
|
|
}
|
|
}
|
|
|
|
private void describePromotePendingPniAci(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
boolean editorIsYou = selfIds.matches(change.getEditor());
|
|
|
|
for (DecryptedMember newMember : change.getPromotePendingPniAciMembersList()) {
|
|
ByteString uuid = newMember.getUuid();
|
|
boolean newMemberIsYou = selfIds.matches(uuid);
|
|
|
|
if (editorIsYou) {
|
|
if (newMemberIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_accepted_invite), R.drawable.ic_update_group_accept_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_you_added_invited_member_s, uuid, R.drawable.ic_update_group_add_16));
|
|
}
|
|
} else {
|
|
if (newMemberIsYou) {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_added_you, change.getEditor(), R.drawable.ic_update_group_add_16));
|
|
} else {
|
|
if (uuid.equals(change.getEditor())) {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_accepted_invite, uuid, R.drawable.ic_update_group_accept_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_added_invited_member_s, change.getEditor(), uuid, R.drawable.ic_update_group_add_16));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void describeUnknownEditorPromotePendingPniAci(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
|
for (DecryptedMember newMember : change.getPromotePendingPniAciMembersList()) {
|
|
ByteString uuid = newMember.getUuid();
|
|
boolean newMemberIsYou = selfIds.matches(uuid);
|
|
|
|
if (newMemberIsYou) {
|
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group), R.drawable.ic_update_group_add_16));
|
|
} else {
|
|
updates.add(updateDescription(R.string.MessageRecord_s_joined_the_group, uuid, R.drawable.ic_update_group_add_16));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static UpdateDescription updateDescription(@NonNull String string, @DrawableRes int iconResource) {
|
|
return UpdateDescription.staticDescription(string, iconResource);
|
|
}
|
|
|
|
private UpdateDescription updateDescription(@StringRes int stringRes,
|
|
@NonNull ByteString uuid1Bytes,
|
|
@DrawableRes int iconResource)
|
|
{
|
|
ServiceId serviceId = ServiceId.fromByteStringOrUnknown(uuid1Bytes);
|
|
RecipientId recipientId = RecipientId.from(serviceId);
|
|
|
|
return UpdateDescription.mentioning(
|
|
Collections.singletonList(serviceId),
|
|
() -> {
|
|
List<RecipientId> recipientIdList = Collections.singletonList(recipientId);
|
|
String templateString = context.getString(stringRes, makePlaceholders(recipientIdList, null));
|
|
|
|
return makeRecipientsClickable(context, templateString, recipientIdList, recipientClickHandler);
|
|
},
|
|
iconResource);
|
|
}
|
|
|
|
private UpdateDescription updateDescription(@StringRes int stringRes,
|
|
@NonNull ByteString uuid1Bytes,
|
|
@NonNull ByteString uuid2Bytes,
|
|
@DrawableRes int iconResource)
|
|
{
|
|
ServiceId sid1 = ServiceId.fromByteStringOrUnknown(uuid1Bytes);
|
|
ServiceId sid2 = ServiceId.fromByteStringOrUnknown(uuid2Bytes);
|
|
|
|
RecipientId recipientId1 = RecipientId.from(sid1);
|
|
RecipientId recipientId2 = RecipientId.from(sid2);
|
|
|
|
return UpdateDescription.mentioning(
|
|
Arrays.asList(sid1, sid2),
|
|
() -> {
|
|
List<RecipientId> recipientIdList = Arrays.asList(recipientId1, recipientId2);
|
|
String templateString = context.getString(stringRes, makePlaceholders(recipientIdList, null));
|
|
|
|
return makeRecipientsClickable(context, templateString, recipientIdList, recipientClickHandler);
|
|
},
|
|
iconResource
|
|
);
|
|
}
|
|
|
|
private UpdateDescription updateDescription(@StringRes int stringRes,
|
|
@NonNull ByteString uuid1Bytes,
|
|
@NonNull Object formatArg,
|
|
@DrawableRes int iconResource)
|
|
{
|
|
ServiceId serviceId = ServiceId.fromByteStringOrUnknown(uuid1Bytes);
|
|
RecipientId recipientId = RecipientId.from(serviceId);
|
|
|
|
return UpdateDescription.mentioning(
|
|
Collections.singletonList(serviceId),
|
|
() -> {
|
|
List<RecipientId> recipientIdList = Collections.singletonList(recipientId);
|
|
String templateString = context.getString(stringRes, makePlaceholders(recipientIdList, Collections.singletonList(formatArg)));
|
|
|
|
return makeRecipientsClickable(context, templateString, recipientIdList, recipientClickHandler);
|
|
},
|
|
iconResource
|
|
);
|
|
}
|
|
|
|
private UpdateDescription updateDescription(@PluralsRes int stringRes,
|
|
int quantity,
|
|
@NonNull ByteString uuid1Bytes,
|
|
@NonNull Object formatArg,
|
|
@DrawableRes int iconResource)
|
|
{
|
|
ServiceId serviceId = ServiceId.fromByteStringOrUnknown(uuid1Bytes);
|
|
RecipientId recipientId = RecipientId.from(serviceId);
|
|
|
|
return UpdateDescription.mentioning(
|
|
Collections.singletonList(serviceId),
|
|
() -> {
|
|
List<RecipientId> recipientIdList = Collections.singletonList(recipientId);
|
|
String templateString = context.getResources().getQuantityString(stringRes, quantity, makePlaceholders(recipientIdList, Collections.singletonList(formatArg)));
|
|
|
|
return makeRecipientsClickable(context, templateString, recipientIdList, recipientClickHandler);
|
|
},
|
|
iconResource
|
|
);
|
|
}
|
|
|
|
private static @NonNull Object[] makePlaceholders(@NonNull List<RecipientId> recipientIds, @Nullable List<Object> formatArgs) {
|
|
List<Object> args = recipientIds.stream().map(GroupsV2UpdateMessageProducer::makePlaceholder).collect(Collectors.toList());
|
|
|
|
if (formatArgs != null) {
|
|
args.addAll(formatArgs);
|
|
}
|
|
|
|
return args.toArray();
|
|
}
|
|
|
|
@VisibleForTesting
|
|
static @NonNull Spannable makeRecipientsClickable(@NonNull Context context, @NonNull String template, @NonNull List<RecipientId> recipientIds, @Nullable Consumer<RecipientId> clickHandler) {
|
|
SpannableStringBuilder builder = new SpannableStringBuilder();
|
|
int startIndex = 0;
|
|
|
|
Map<String, RecipientId> idByPlaceholder = new HashMap<>();
|
|
for (RecipientId id : recipientIds) {
|
|
idByPlaceholder.put(makePlaceholder(id), id);
|
|
}
|
|
|
|
while (startIndex < template.length()) {
|
|
Map.Entry<String, RecipientId> nearestEntry = null;
|
|
int nearestPosition = Integer.MAX_VALUE;
|
|
|
|
for (Map.Entry<String, RecipientId> entry : idByPlaceholder.entrySet()) {
|
|
String placeholder = entry.getKey();
|
|
int placeholderStart = template.indexOf(placeholder, startIndex);
|
|
|
|
if (placeholderStart >= 0 && placeholderStart < nearestPosition) {
|
|
nearestPosition = placeholderStart;
|
|
nearestEntry = entry;
|
|
}
|
|
}
|
|
|
|
if (nearestEntry != null) {
|
|
String placeholder = nearestEntry.getKey();
|
|
RecipientId recipientId = nearestEntry.getValue();
|
|
|
|
String beforeChunk = template.substring(startIndex, nearestPosition);
|
|
|
|
builder.append(beforeChunk);
|
|
builder.append(SpanUtil.clickable(Recipient.resolved(recipientId).getDisplayName(context), ContextCompat.getColor(context, R.color.conversation_item_update_text_color), v -> {
|
|
if (!recipientId.isUnknown() && clickHandler != null) {
|
|
clickHandler.accept(recipientId);
|
|
}
|
|
}));
|
|
|
|
startIndex = nearestPosition + placeholder.length();
|
|
} else {
|
|
builder.append(template.substring(startIndex));
|
|
startIndex = template.length();
|
|
}
|
|
}
|
|
|
|
return builder;
|
|
}
|
|
|
|
@VisibleForTesting
|
|
static @NonNull String makePlaceholder(@NonNull RecipientId recipientId) {
|
|
return "{{SPAN_PLACEHOLDER_" + recipientId + "}}";
|
|
}
|
|
} |