Add support for admin delete.

This commit is contained in:
Michelle Tang
2026-02-20 14:44:34 -05:00
committed by Cody Henthorne
parent 1968438ebb
commit 071fbfd916
45 changed files with 648 additions and 132 deletions

View File

@@ -116,6 +116,7 @@ import org.thoughtcrime.securesms.database.model.Quote;
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.events.PartProgressEvent;
import org.thoughtcrime.securesms.fonts.SignalSymbols;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicy;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicyEnforcer;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
@@ -1093,16 +1094,8 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
bodyText.setOverflowText(null);
bodyText.setMaxLength(-1);
if (messageRecord.isRemoteDelete()) {
String deletedMessage = context.getString(messageRecord.isOutgoing() ? R.string.ConversationItem_you_deleted_this_message : R.string.ConversationItem_this_message_was_deleted);
SpannableString italics = new SpannableString(deletedMessage);
italics.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, deletedMessage.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
italics.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, R.color.signal_text_primary)),
0,
deletedMessage.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
bodyText.setText(italics);
if (conversationMessage.getDeletedByRecipient() != null) {
bodyText.setText(getDeletedMessageText(conversationMessage));
bodyText.setVisibility(View.VISIBLE);
bodyText.setOverflowText(null);
} else if (isCaptionlessMms(messageRecord) || isStoryReaction(messageRecord) || isGiftMessage(messageRecord) || messageRecord.isPaymentNotification() || messageRecord.isPaymentTombstone()) {
@@ -1156,6 +1149,44 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
}
}
private SpannableStringBuilder getDeletedMessageText(@NonNull ConversationMessage message) {
boolean isAdminDelete = !message.getDeletedByRecipient().equals(message.getMessageRecord().getFromRecipient());
CharSequence body;
if (!isAdminDelete && messageRecord.isOutgoing()) {
body = formatDeletedText(context.getString(R.string.ConversationItem_you_deleted_this_message));
} else if (!isAdminDelete) {
body = formatDeletedText(context.getString(R.string.ConversationItem_this_message_was_deleted));
} else {
SpannableString prefix = formatDeletedText(context.getString(R.string.ConversationItem_admin));
SpannableString suffix = formatDeletedText(context.getString(R.string.ConversationItem_deleted_this_message));
int nameColor = colorizer.getIncomingGroupSenderColor(getContext(), message.getDeletedByRecipient());
SpannableString name = new SpannableString(message.getDeletedByRecipient().getDisplayName(context));
name.setSpan(new ForegroundColorSpan(nameColor), 0, name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
name.setSpan(new RecipientClickableSpan(conversationMessage.getDeletedByRecipient().getId()), 0, name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
name.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
body = new SpannableStringBuilder()
.append(prefix)
.append(" ")
.append(name)
.append(" ")
.append(suffix);
}
return new SpannableStringBuilder()
.append(SignalSymbols.getSpannedString(getContext(), SignalSymbols.Weight.REGULAR, SignalSymbols.Glyph.X_CIRCLE, org.signal.core.ui.R.color.signal_colorOnSurfaceVariant))
.append(" ")
.append(body);
}
private SpannableString formatDeletedText(String text) {
SpannableString spannableString = new SpannableString(text);
spannableString.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, org.signal.core.ui.R.color.signal_colorOnSurfaceVariant)), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannableString;
}
private void setMediaAttributes(@NonNull MessageRecord messageRecord,
@NonNull Optional<MessageRecord> previousRecord,
@NonNull Optional<MessageRecord> nextRecord,
@@ -1668,7 +1699,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
List<Annotation> mentionAnnotations = MentionAnnotation.getMentionAnnotations(messageBody);
for (Annotation annotation : mentionAnnotations) {
messageBody.setSpan(new MentionClickableSpan(RecipientId.from(annotation.getValue())), messageBody.getSpanStart(annotation), messageBody.getSpanEnd(annotation), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
messageBody.setSpan(new RecipientClickableSpan(RecipientId.from(annotation.getValue())), messageBody.getSpanStart(annotation), messageBody.getSpanEnd(annotation), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
@@ -2895,18 +2926,18 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
}
}
private class MentionClickableSpan extends ClickableSpan {
private final RecipientId mentionedRecipientId;
private class RecipientClickableSpan extends ClickableSpan {
private final RecipientId recipientId;
MentionClickableSpan(RecipientId mentionedRecipientId) {
this.mentionedRecipientId = mentionedRecipientId;
RecipientClickableSpan(RecipientId recipientId) {
this.recipientId = recipientId;
}
@Override
public void onClick(@NonNull View widget) {
if (eventListener != null && batchSelected.isEmpty()) {
VibrateUtil.vibrateTick(context);
eventListener.onGroupMemberClicked(mentionedRecipientId, conversationRecipient.get().requireGroupId());
eventListener.onGroupMemberClicked(recipientId, conversationRecipient.get().requireGroupId());
}
}

View File

@@ -51,6 +51,7 @@ public class ConversationMessage {
@NonNull private final ComputedProperties computedProperties;
@Nullable private final MemberLabel memberLabel;
@Nullable private final MemberLabel quoteMemberLabel;
@Nullable private final Recipient deletedByRecipient;
private ConversationMessage(@NonNull MessageRecord messageRecord,
@Nullable CharSequence body,
@@ -61,7 +62,8 @@ public class ConversationMessage {
@Nullable MessageRecord originalMessage,
@NonNull ComputedProperties computedProperties,
@Nullable MemberLabel memberLabel,
@Nullable MemberLabel quoteMemberLabel)
@Nullable MemberLabel quoteMemberLabel,
@Nullable Recipient deletedByRecipient)
{
this.messageRecord = messageRecord;
this.hasBeenQuoted = hasBeenQuoted;
@@ -72,6 +74,7 @@ public class ConversationMessage {
this.computedProperties = computedProperties;
this.memberLabel = memberLabel;
this.quoteMemberLabel = quoteMemberLabel;
this.deletedByRecipient = deletedByRecipient;
if (body != null) {
this.body = SpannableString.valueOf(body);
@@ -116,6 +119,10 @@ public class ConversationMessage {
return quoteMemberLabel;
}
public @Nullable Recipient getDeletedByRecipient() {
return deletedByRecipient;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -252,6 +259,7 @@ public class ConversationMessage {
FormattedDate formattedDate = getFormattedDate(context, messageRecord);
MemberLabel memberLabel = getMemberLabel(messageRecord, threadRecipient);
MemberLabel quoteMemberLabel = getQuoteMemberLabel(messageRecord, threadRecipient);
Recipient deletedBy = messageRecord.getDeletedBy() != null ? Recipient.resolved(messageRecord.getDeletedBy()) : null;
return new ConversationMessage(messageRecord,
styledAndMentionBody != null ? styledAndMentionBody : mentionsUpdate != null ? mentionsUpdate.getBody() : body,
@@ -262,7 +270,8 @@ public class ConversationMessage {
originalMessage,
new ComputedProperties(formattedDate),
memberLabel,
quoteMemberLabel);
quoteMemberLabel,
deletedBy);
}
/**

View File

@@ -2828,7 +2828,10 @@ class ConversationFragment :
disposables += DeleteDialog.show(
context = requireContext(),
messageRecords = records
messageRecords = records,
title = requireContext().resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_title, records.size, records.size),
message = requireContext().resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_body, records.size, records.size),
isAdmin = conversationGroupViewModel.isAdmin()
).observeOn(AndroidSchedulers.mainThread())
.subscribe { (deleted: Boolean, _: Boolean) ->
if (!deleted) return@subscribe

View File

@@ -62,6 +62,10 @@ class ConversationGroupViewModel(
disposables.clear()
}
fun isAdmin(): Boolean {
return _memberLevel.value?.groupTableMemberLevel == GroupTable.MemberLevel.ADMINISTRATOR
}
fun isNonAdminInAnnouncementGroup(): Boolean {
val memberLevel = _memberLevel.value ?: return false
return memberLevel.groupTableMemberLevel != GroupTable.MemberLevel.ADMINISTRATOR && memberLevel.isAnnouncementGroup