Add notifications for scheduled notes to self.

This commit is contained in:
Greyson Parrelli
2026-07-22 12:04:09 -04:00
committed by Michelle Tang
parent 836645ed2b
commit 2d04bb32fd
8 changed files with 60 additions and 22 deletions
@@ -2494,15 +2494,22 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
AppDependencies.databaseObserver.notifyMessageUpdateObservers(MessageId(messageId))
}
fun clearScheduledStatus(threadId: Long, messageId: Long, expiresIn: Long): Boolean {
fun clearScheduledStatus(threadId: Long, messageId: Long, expiresIn: Long, markUnread: Boolean = false): Boolean {
val values = contentValuesOf(
SCHEDULED_DATE to -1,
DATE_SENT to System.currentTimeMillis(),
DATE_RECEIVED to System.currentTimeMillis(),
EXPIRES_IN to expiresIn
)
if (markUnread) {
values.put(READ, 0)
values.put(NOTIFIED, 0)
}
val rowsUpdated = writableDatabase
.update(TABLE_NAME)
.values(
SCHEDULED_DATE to -1,
DATE_SENT to System.currentTimeMillis(),
DATE_RECEIVED to System.currentTimeMillis(),
EXPIRES_IN to expiresIn
)
.values(values)
.where("$ID = ? AND $SCHEDULED_DATE != ?", messageId, -1)
.run()
@@ -301,15 +301,16 @@ sealed class NotificationBuilder(protected val context: Context) {
conversation.notificationItems.forEach { notificationItem ->
var person: PersonCompat? = null
val isNoteToSelf = notificationItem.isPersonSelf && conversation.recipient.isSelf
if (!notificationItem.isPersonSelf) {
if (!notificationItem.isPersonSelf || isNoteToSelf) {
val personBuilder: PersonCompat.Builder = PersonCompat.Builder()
.setBot(false)
.setName(notificationItem.getPersonName(context))
.setUri(notificationItem.getPersonUri(context))
.setIcon(notificationItem.getPersonIcon(context))
if (includeShortcut) {
if (includeShortcut && !isNoteToSelf) {
personBuilder.setKey(ConversationUtil.getShortcutId(notificationItem.authorRecipient))
}
@@ -55,7 +55,11 @@ data class NotificationConversation(
fun getContactLargeIcon(context: Context): Drawable? {
return if (SignalStore.settings.messageNotificationsPrivacy.isDisplayContact) {
recipient.getContactDrawable(context)
if (recipient.isSelf) {
FallbackAvatarDrawable(context, FallbackAvatar.Resource.NoteToSelf(recipient.avatarColor)).circleCrop()
} else {
recipient.getContactDrawable(context)
}
} else {
FallbackAvatarDrawable(context, FallbackAvatar.forTextOrDefault("Unknown", AvatarColor.UNKNOWN)).circleCrop()
}
@@ -207,6 +211,8 @@ data class NotificationConversation(
private fun getDisplayName(context: Context): String {
return if (thread.groupStoryId != null) {
context.getString(R.string.SingleRecipientNotificationBuilder__s_dot_story, recipient.getDisplayName(context))
} else if (recipient.isSelf) {
context.getString(R.string.note_to_self)
} else {
recipient.getDisplayName(context)
}
@@ -213,8 +213,9 @@ object NotificationFactory {
else -> 0.seconds
}
val canAlertBasedOnTime: Boolean = lastNotificationTimestamp < System.currentTimeMillis() - throttle.inWholeMilliseconds || lastNotificationTimestamp > System.currentTimeMillis()
val isUnreadNoteToSelf: Boolean = conversation.recipient.isSelf && (conversation.mostRecentNotification as? MessageNotification)?.isUnread == true
return ((conversation.hasNewNotifications() && canAlertBasedOnTime) || alertOverride) && !conversation.mostRecentNotification.authorRecipient.isSelf
return ((conversation.hasNewNotifications() && canAlertBasedOnTime) || alertOverride) && (!conversation.mostRecentNotification.authorRecipient.isSelf || isUnreadNoteToSelf)
}
@WorkerThread
@@ -58,6 +58,9 @@ sealed class NotificationItem(val threadRecipient: Recipient, protected val reco
val isPersonSelf: Boolean
get() = authorRecipient.isSelf
protected val isNoteToSelf: Boolean
get() = authorRecipient.isSelf && threadRecipient.isSelf
protected val notifiedTimestamp: Long = record.notifiedTimestamp
abstract val timestamp: Long
@@ -99,7 +102,7 @@ sealed class NotificationItem(val threadRecipient: Recipient, protected val reco
context.getString(R.string.SingleRecipientNotificationBuilder_new_message)
} else {
SpannableStringBuilder().apply {
append(Util.getBoldedString(authorRecipient.getShortDisplayName(context)))
append(Util.getBoldedString(if (isNoteToSelf) context.getString(R.string.note_to_self) else authorRecipient.getShortDisplayName(context)))
if (threadRecipient != authorRecipient) {
append(Util.getBoldedString("@${threadRecipient.getDisplayName(context)}"))
}
@@ -111,7 +114,11 @@ sealed class NotificationItem(val threadRecipient: Recipient, protected val reco
fun getPersonName(context: Context): CharSequence {
return if (SignalStore.settings.messageNotificationsPrivacy.isDisplayContact) {
authorRecipient.getDisplayName(context)
if (isNoteToSelf) {
context.getString(R.string.note_to_self)
} else {
authorRecipient.getDisplayName(context)
}
} else {
context.getString(R.string.SingleRecipientNotificationBuilder_signal)
}
@@ -131,7 +138,11 @@ sealed class NotificationItem(val threadRecipient: Recipient, protected val reco
fun getPersonIcon(context: Context): IconCompat? {
return if (SignalStore.settings.messageNotificationsPrivacy.isDisplayContact) {
AvatarUtil.getIconCompat(context, authorRecipient)
if (isNoteToSelf) {
AvatarUtil.getIconCompatForNoteToSelf(context, authorRecipient)
} else {
AvatarUtil.getIconCompat(context, authorRecipient)
}
} else {
null
}
@@ -216,7 +227,7 @@ sealed class NotificationItem(val threadRecipient: Recipient, protected val reco
/**
* Represents a notification associated with a new message.
*/
class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : NotificationItem(threadRecipient, record) {
class MessageNotification(threadRecipient: Recipient, record: MessageRecord, val isUnread: Boolean = false) : NotificationItem(threadRecipient, record) {
override val timestamp: Long = record.timestamp
override val authorRecipient: Recipient = record.fromRecipient.resolve()
override val isNewNotification: Boolean = notifiedTimestamp == 0L && !record.isEditMessage
@@ -101,7 +101,7 @@ object NotificationStateProvider {
for (notification: NotificationMessage in threadMessages) {
when (notification.includeMessage(notificationProfile)) {
MessageInclusion.INCLUDE -> notificationItems.add(MessageNotification(notification.threadRecipient, notification.messageRecord))
MessageInclusion.INCLUDE -> notificationItems.add(MessageNotification(notification.threadRecipient, notification.messageRecord, notification.isUnreadMessage))
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)
@@ -161,6 +161,7 @@ object NotificationStateProvider {
) {
private val isGroupStoryReply: Boolean = thread.groupStoryId != null
private val isUnreadIncoming: Boolean = isUnreadMessage && !messageRecord.isOutgoing && !isGroupStoryReply
private val isUnreadNoteToSelf: Boolean = isUnreadMessage && messageRecord.isOutgoing && threadRecipient.isSelf && !isGroupStoryReply
private val isIncomingMissedCall: Boolean = !messageRecord.isOutgoing && (messageRecord.isMissedAudioCall || messageRecord.isMissedVideoCall)
private val isNotifiableGroupStoryMessage: Boolean =
@@ -170,7 +171,7 @@ object NotificationStateProvider {
(isParentStorySentBySelf || messageRecord.hasGroupQuoteOrSelfMention() || (hasSelfRepliedToStory && !messageRecord.isStoryReaction()))
fun includeMessage(notificationProfile: NotificationProfile?): MessageInclusion {
return if (isUnreadIncoming || stickyThread || isNotifiableGroupStoryMessage || isIncomingMissedCall) {
return if (isUnreadIncoming || isUnreadNoteToSelf || stickyThread || isNotifiableGroupStoryMessage || isIncomingMissedCall) {
if (threadRecipient.isMuted && !breaksThroughMute()) {
MessageInclusion.MUTE_FILTERED
} else if (notificationProfile != null && !notificationProfile.isRecipientAllowed(threadRecipient.id) && !(notificationProfile.allowAllMentions && messageRecord.hasGroupQuoteOrSelfMention())) {
@@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobs.IndividualSendJob
import org.thoughtcrime.securesms.jobs.PushGroupSendJob
import org.thoughtcrime.securesms.notifications.v2.ConversationId
import org.thoughtcrime.securesms.recipients.RecipientId
import kotlin.time.Duration.Companion.seconds
@@ -55,12 +56,17 @@ class ScheduledMessageManager(
val scheduledMessagesToSend = messagesTable.getScheduledMessagesBefore(System.currentTimeMillis())
for (record in scheduledMessagesToSend) {
val expiresIn = SignalDatabase.recipients.getExpiresInSeconds(record.toRecipient.id)
if (messagesTable.clearScheduledStatus(record.threadId, record.id, expiresIn.seconds.inWholeMilliseconds)) {
val isNoteToSelf = record.toRecipient.isSelf
if (messagesTable.clearScheduledStatus(record.threadId, record.id, expiresIn.seconds.inWholeMilliseconds, markUnread = isNoteToSelf)) {
if (record.toRecipient.isPushGroup) {
PushGroupSendJob.enqueue(application, AppDependencies.jobManager, record.id, record.toRecipient.id, emptySet(), true)
} else {
IndividualSendJob.enqueue(application, AppDependencies.jobManager, record.id, record.toRecipient, true)
}
if (isNoteToSelf) {
AppDependencies.messageNotifier.updateNotification(application, ConversationId.forConversation(record.threadId))
}
} else {
Log.i(TAG, "messageId=${record.id} was not a scheduled message, ignoring")
}
@@ -128,12 +128,17 @@ public final class AvatarUtil {
*/
@WorkerThread
public static @NonNull IconCompat getIconCompatForShortcut(@NonNull Context context, @NonNull Recipient recipient) {
int size = AdaptiveBitmapMetrics.getInnerWidth();
if (recipient.isSelf()) {
Drawable noteToSelfDrawable = getNoteToSelfDrawable(context, recipient.getAvatarColor(), size);
return IconCompat.createWithBitmap(DrawableUtil.toBitmap(noteToSelfDrawable, size, size));
return getIconCompatForNoteToSelf(context, recipient);
}
return IconCompat.createWithBitmap(getBitmapForNotification(context, recipient, size));
return IconCompat.createWithBitmap(getBitmapForNotification(context, recipient, AdaptiveBitmapMetrics.getInnerWidth()));
}
@WorkerThread
public static @NonNull IconCompat getIconCompatForNoteToSelf(@NonNull Context context, @NonNull Recipient recipient) {
int size = AdaptiveBitmapMetrics.getInnerWidth();
Drawable noteToSelfDrawable = getNoteToSelfDrawable(context, recipient.getAvatarColor(), size);
return IconCompat.createWithBitmap(DrawableUtil.toBitmap(noteToSelfDrawable, size, size));
}
@WorkerThread