From b3f74d37e1ac09b9275da4d3147b59b64424441f Mon Sep 17 00:00:00 2001 From: Michelle Tang Date: Thu, 16 Oct 2025 13:32:33 -0400 Subject: [PATCH] Add poll icon when quoting a poll. --- .../v2/exporters/ChatItemArchiveExporter.kt | 1 + .../v2/importer/ChatItemArchiveImporter.kt | 1 + .../securesms/components/QuoteView.java | 20 ++++++++++++++++++- .../conversation/v2/ConversationRepository.kt | 7 ------- .../thoughtcrime/securesms/mms/QuoteModel.kt | 12 ++++++----- .../securesms/util/MessageRecordUtil.kt | 8 +++++++- app/src/main/protowire/Backup.proto | 1 + .../api/messages/SignalServiceDataMessage.kt | 3 ++- .../src/main/protowire/SignalService.proto | 1 + 9 files changed, 39 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt index 2dd4940710..a1ded56c92 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt @@ -1117,6 +1117,7 @@ private fun BackupMessageRecord.toRemoteQuote(exportState: ExportState, attachme } } QuoteModel.Type.GIFT_BADGE -> Quote.Type.GIFT_BADGE + QuoteModel.Type.POLL -> Quote.Type.POLL } val bodyRanges = this.quoteBodyRanges?.toRemoteBodyRanges(dateSent) ?: emptyList() diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt index 9cd2c097d0..455d67b9c2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt @@ -1053,6 +1053,7 @@ class ChatItemArchiveImporter( Quote.Type.NORMAL -> QuoteModel.Type.NORMAL.code Quote.Type.GIFT_BADGE -> QuoteModel.Type.GIFT_BADGE.code Quote.Type.VIEW_ONCE -> QuoteModel.Type.NORMAL.code + Quote.Type.POLL -> QuoteModel.Type.POLL.code } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java b/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java index 79fd50eb4a..3f5dc2c926 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java @@ -4,6 +4,7 @@ package org.thoughtcrime.securesms.components; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; @@ -32,6 +33,7 @@ import org.thoughtcrime.securesms.components.quotes.QuoteViewColorTheme; import org.thoughtcrime.securesms.conversation.MessageStyler; import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList; +import org.thoughtcrime.securesms.fonts.SignalSymbols; import org.thoughtcrime.securesms.mms.DecryptableUri; import org.thoughtcrime.securesms.mms.QuoteModel; import org.thoughtcrime.securesms.mms.Slide; @@ -43,6 +45,7 @@ import org.thoughtcrime.securesms.stories.StoryTextPostModel; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.Projection; import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.views.Stub; import java.io.IOException; @@ -231,7 +234,14 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser } private @Nullable CharSequence resolveBody(@Nullable CharSequence body, @NonNull QuoteModel.Type quoteType) { - return quoteType == QuoteModel.Type.GIFT_BADGE ? getContext().getString(R.string.QuoteView__donation_for_a_friend) : body; + switch (quoteType) { + case GIFT_BADGE: + return getContext().getString(R.string.QuoteView__donation_for_a_friend); + case POLL: + return getContext().getString(R.string.Poll__poll_question, body); + default: + return body; + } } public void setTopCornerSizes(boolean topLeftLarge, boolean topRightLarge) { @@ -317,6 +327,14 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser Log.w(TAG, "Could not parse body of text post.", e); bodyView.setText(""); } + } else if (quoteType == QuoteModel.Type.POLL) { + CharSequence glyph = SignalSymbols.getSpannedString(getContext(), SignalSymbols.Weight.REGULAR, SignalSymbols.Glyph.POLL, -1); + // TODO(michelle): Update with RTL poll icon + SpannableStringBuilder builder = new SpannableStringBuilder() + .append(glyph) + .append(" ") + .append(body); + bodyView.setText(body == null ? "" : builder); } else { bodyView.setText(body == null ? "" : body); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt index b5b356b0ff..4844c0ec9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt @@ -85,11 +85,9 @@ import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.MessageUtil import org.thoughtcrime.securesms.util.SignalLocalMetrics import org.thoughtcrime.securesms.util.Util -import org.thoughtcrime.securesms.util.getPoll import org.thoughtcrime.securesms.util.hasLinkPreview import org.thoughtcrime.securesms.util.hasSharedContact import org.thoughtcrime.securesms.util.hasTextSlide -import org.thoughtcrime.securesms.util.isPoll import org.thoughtcrime.securesms.util.isViewOnceMessage import org.thoughtcrime.securesms.util.requireTextSlide import java.io.IOException @@ -562,11 +560,6 @@ class ConversationRepository( } slideDeck to conversationMessage.getDisplayBody(context) - } else if (messageRecord.isPoll()) { - val poll = messageRecord.getPoll()!! - val slideDeck = SlideDeck() - - slideDeck to SpannableStringBuilder().append(context.getString(R.string.Poll__poll_question, poll.question)) } else { var slideDeck = if (messageRecord.isMms) { (messageRecord as MmsMessageRecord).slideDeck diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/QuoteModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mms/QuoteModel.kt index 684d3aba94..fb195b793d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/QuoteModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/QuoteModel.kt @@ -26,7 +26,8 @@ class QuoteModel( enum class Type(val code: Int, val dataMessageType: SignalServiceDataMessage.Quote.Type) { NORMAL(0, SignalServiceDataMessage.Quote.Type.NORMAL), - GIFT_BADGE(1, SignalServiceDataMessage.Quote.Type.GIFT_BADGE); + GIFT_BADGE(1, SignalServiceDataMessage.Quote.Type.GIFT_BADGE), + POLL(2, SignalServiceDataMessage.Quote.Type.POLL); companion object { @JvmStatic @@ -50,10 +51,11 @@ class QuoteModel( } fun fromProto(type: DataMessage.Quote.Type?): Type { - return if (type == DataMessage.Quote.Type.GIFT_BADGE) { - GIFT_BADGE - } else { - NORMAL + return when (type) { + DataMessage.Quote.Type.NORMAL -> NORMAL + DataMessage.Quote.Type.GIFT_BADGE -> GIFT_BADGE + DataMessage.Quote.Type.POLL -> POLL + null -> NORMAL } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MessageRecordUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/MessageRecordUtil.kt index 412147de61..d794b22df7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MessageRecordUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MessageRecordUtil.kt @@ -164,7 +164,13 @@ fun MessageRecord.isScheduled(): Boolean { * Returns the QuoteType for this record, as if it was being quoted. */ fun MessageRecord.getRecordQuoteType(): QuoteModel.Type { - return if (hasGiftBadge()) QuoteModel.Type.GIFT_BADGE else QuoteModel.Type.NORMAL + return if (hasGiftBadge()) { + QuoteModel.Type.GIFT_BADGE + } else if (hasPoll()) { + QuoteModel.Type.POLL + } else { + QuoteModel.Type.NORMAL + } } fun MessageRecord.isEditMessage(): Boolean { diff --git a/app/src/main/protowire/Backup.proto b/app/src/main/protowire/Backup.proto index 1c88c6a5f5..306898922f 100644 --- a/app/src/main/protowire/Backup.proto +++ b/app/src/main/protowire/Backup.proto @@ -760,6 +760,7 @@ message Quote { NORMAL = 1; GIFT_BADGE = 2; VIEW_ONCE = 3; + POLL = 4; } message QuotedAttachment { diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.kt index 37fcc75383..7ce505f807 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.kt +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.kt @@ -291,7 +291,8 @@ class SignalServiceDataMessage private constructor( ) { enum class Type(val protoType: QuoteProto.Type) { NORMAL(QuoteProto.Type.NORMAL), - GIFT_BADGE(QuoteProto.Type.GIFT_BADGE); + GIFT_BADGE(QuoteProto.Type.GIFT_BADGE), + POLL(QuoteProto.Type.POLL); companion object { @JvmStatic diff --git a/libsignal-service/src/main/protowire/SignalService.proto b/libsignal-service/src/main/protowire/SignalService.proto index 1d278a205f..249c4ebb65 100644 --- a/libsignal-service/src/main/protowire/SignalService.proto +++ b/libsignal-service/src/main/protowire/SignalService.proto @@ -177,6 +177,7 @@ message DataMessage { enum Type { NORMAL = 0; GIFT_BADGE = 1; + POLL = 2; } message QuotedAttachment {