Release polls behind feature flag.

This commit is contained in:
Michelle Tang
2025-10-01 12:46:37 -04:00
parent 67a693107e
commit b8e4ffb5ae
84 changed files with 4164 additions and 102 deletions

View File

@@ -10,6 +10,7 @@ import androidx.annotation.StringRes
import androidx.core.graphics.drawable.IconCompat
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.emoji.EmojiStrings
import org.thoughtcrime.securesms.contactshare.Contact
import org.thoughtcrime.securesms.contactshare.ContactUtil
import org.thoughtcrime.securesms.database.MentionUtil
@@ -24,6 +25,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.Slide
import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.polls.PollVote
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientUtil
import org.thoughtcrime.securesms.service.KeyCachingService
@@ -32,6 +34,8 @@ import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.hasGiftBadge
import org.thoughtcrime.securesms.util.hasPoll
import org.thoughtcrime.securesms.util.hasPollTerminate
import org.thoughtcrime.securesms.util.hasSharedContact
import org.thoughtcrime.securesms.util.hasSticker
import org.thoughtcrime.securesms.util.isMediaMessage
@@ -240,6 +244,10 @@ class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : N
ThreadBodyUtil.getFormattedBodyForNotification(context, record, null)
} else if (record.isPaymentNotification || record.isPaymentTombstone) {
ThreadBodyUtil.getFormattedBodyForNotification(context, record, null)
} else if (record.hasPoll()) {
ThreadBodyUtil.getFormattedBodyForPollNotification(context, record as MmsMessageRecord)
} else if (record.hasPollTerminate()) {
ThreadBodyUtil.getFormattedBodyForPollEndNotification(context, record as MmsMessageRecord)
} else {
getBodyWithMentionsAndStyles(context, record)
}
@@ -380,3 +388,33 @@ class ReactionNotification(threadRecipient: Recipient, record: MessageRecord, va
return "ReactionNotification(timestamp=$timestamp, isNewNotification=$isNewNotification)"
}
}
/**
* Represents a notification associated with a new vote.
*/
class VoteNotification(threadRecipient: Recipient, record: MessageRecord, val vote: PollVote) : NotificationItem(threadRecipient, record) {
override val timestamp: Long = vote.dateReceived
override val authorRecipient: Recipient = Recipient.resolved(vote.voterId)
override val isNewNotification: Boolean = timestamp > notifiedTimestamp
override fun getPrimaryTextActual(context: Context): CharSequence {
return if (KeyCachingService.isLocked(context)) {
SpanUtil.italic(context.getString(R.string.MessageNotifier_locked_message))
} else {
context.getString(R.string.MessageNotifier_s_voted_in_poll, EmojiStrings.POLL, authorRecipient.getDisplayName(context), vote.question)
}
}
override fun getStartingPosition(context: Context): Int {
return SignalDatabase.messages.getMessagePositionInConversation(threadId = thread.threadId, groupStoryId = 0L, receivedTimestamp = record.dateReceived)
}
override fun getLargeIconUri(): Uri? = null
override fun getBigPictureUri(): Uri? = null
override fun getThumbnailInfo(context: Context): ThumbnailInfo = ThumbnailInfo()
override fun canReply(context: Context): Boolean = false
override fun toString(): String {
return "VoteNotification(timestamp=$timestamp, isNewNotification=$isNewNotification)"
}
}

View File

@@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.polls.PollVote
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.isStoryReaction
@@ -37,6 +38,7 @@ object NotificationStateProvider {
val threadRecipient: Recipient? = SignalDatabase.threads.getRecipientForThreadId(record.threadId)
if (threadRecipient != null) {
val hasUnreadReactions = CursorUtil.requireInt(unreadMessages, MessageTable.REACTIONS_UNREAD) == 1
val hasUnreadVotes = CursorUtil.requireInt(unreadMessages, MessageTable.VOTES_UNREAD) == 1
val conversationId = ConversationId.fromMessageRecord(record)
val parentRecord = conversationId.groupStoryId?.let {
@@ -56,17 +58,24 @@ object NotificationStateProvider {
if (attachments.isNotEmpty()) {
record = record.withAttachments(attachments)
}
val poll = SignalDatabase.polls.getPoll(record.id)
if (poll != null) {
record = record.withPoll(poll)
}
}
messages += NotificationMessage(
messageRecord = record,
reactions = if (hasUnreadReactions) SignalDatabase.reactions.getReactions(MessageId(record.id)) else emptyList(),
pollVotes = if (hasUnreadVotes) SignalDatabase.polls.getAllVotes(record.id) else emptyList(),
threadRecipient = threadRecipient,
thread = conversationId,
stickyThread = stickyThreads.containsKey(conversationId),
isUnreadMessage = CursorUtil.requireInt(unreadMessages, MessageTable.READ) == 0,
hasUnreadReactions = hasUnreadReactions,
hasUnreadVotes = hasUnreadVotes,
lastReactionRead = CursorUtil.requireLong(unreadMessages, MessageTable.REACTIONS_LAST_SEEN),
lastVoteRead = CursorUtil.requireLong(unreadMessages, MessageTable.VOTES_LAST_SEEN),
isParentStorySentBySelf = parentRecord?.isOutgoing ?: false,
hasSelfRepliedToStory = hasSelfRepliedToGroupStory ?: false
)
@@ -108,6 +117,17 @@ object NotificationStateProvider {
}
}
}
if (notification.hasUnreadVotes) {
notification.pollVotes.forEach {
when (notification.shouldIncludeVote(it, notificationProfile)) {
MessageInclusion.INCLUDE -> notificationItems.add(VoteNotification(notification.threadRecipient, notification.messageRecord, it))
MessageInclusion.EXCLUDE -> Unit
MessageInclusion.MUTE_FILTERED -> muteFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
MessageInclusion.PROFILE_FILTERED -> profileFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
}
}
}
}
notificationItems.sort()
@@ -127,12 +147,15 @@ object NotificationStateProvider {
private data class NotificationMessage(
val messageRecord: MessageRecord,
val reactions: List<ReactionRecord>,
val pollVotes: List<PollVote>,
val threadRecipient: Recipient,
val thread: ConversationId,
val stickyThread: Boolean,
val isUnreadMessage: Boolean,
val hasUnreadReactions: Boolean,
val hasUnreadVotes: Boolean,
val lastReactionRead: Long,
val lastVoteRead: Long,
val isParentStorySentBySelf: Boolean,
val hasSelfRepliedToStory: Boolean
) {
@@ -172,6 +195,18 @@ object NotificationStateProvider {
}
}
fun shouldIncludeVote(vote: PollVote, notificationProfile: NotificationProfile?): MessageInclusion {
return if (threadRecipient.isMuted) {
MessageInclusion.MUTE_FILTERED
} else if (notificationProfile != null && !notificationProfile.isRecipientAllowed(threadRecipient.id)) {
MessageInclusion.PROFILE_FILTERED
} else if (vote.voterId != Recipient.self().id && messageRecord.isOutgoing && vote.dateReceived > lastVoteRead) {
MessageInclusion.INCLUDE
} else {
MessageInclusion.EXCLUDE
}
}
private val Recipient.isDoNotNotifyMentions: Boolean
get() = mentionSetting == RecipientTable.MentionSetting.DO_NOT_NOTIFY
}