diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/FullNotificationHandler.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/FullAndNameOnlyNotificationHandler.kt similarity index 95% rename from app/src/main/java/org/thoughtcrime/securesms/notifications/FullNotificationHandler.kt rename to app/src/main/java/org/thoughtcrime/securesms/notifications/FullAndNameOnlyNotificationHandler.kt index 1cb1324915..5b5c657a17 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/FullNotificationHandler.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/FullAndNameOnlyNotificationHandler.kt @@ -46,13 +46,13 @@ import javax.inject.Singleton import kotlin.math.max /** - * Handles notifications in [NotificationPrivacy.ShowNameAndContent] mode. + * Handles notifications in [NotificationPrivacy.ShowNameAndContent]/[NotificationPrivacy.ShowNameOnly] mode. * - * Shows one per-thread notification with the sender's name, avatar, and full message body, + * Shows one per-thread notification with the sender's name, avatar, and full message body if turned on, * using [NotificationCompat.MessagingStyle]. Reactions are also included. */ @Singleton -class FullNotificationHandler @Inject constructor( +class FullAndNameOnlyNotificationHandler @Inject constructor( @ApplicationContext context: Context, threadDb: ThreadDatabase, private val mmsSmsDatabase: MmsSmsDatabase, @@ -269,17 +269,25 @@ class FullNotificationHandler @Inject constructor( ArrayList(newMessages.size + newReactions.size) val personCache = arrayMapOf() + val nameOnlyMessageContent = if (prefs[NotificationPreferences.PRIVACY] == NotificationPrivacy.ShowNameOnly) { + context.resources.getQuantityText(R.plurals.messageNew, 1) + } else { + null + } + for (msg in newMessages) { + val text = nameOnlyMessageContent ?: MentionUtilities.parseAndSubstituteMentions( + recipientRepository = recipientRepository, + input = nameOnlyMessageContent ?: messageFormatter.formatMessageBodyForNotification( + context, + msg, + threadRecipient + ), + context = context + ).text + messages += NotificationCompat.MessagingStyle.Message( - MentionUtilities.parseAndSubstituteMentions( - recipientRepository = recipientRepository, - input = messageFormatter.formatMessageBodyForNotification( - context, - msg, - threadRecipient - ), - context = context - ).text, + text, msg.dateSent, msg.toPerson(personCache) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NameOnlyNotificationHandler.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/NameOnlyNotificationHandler.kt deleted file mode 100644 index f267becde2..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NameOnlyNotificationHandler.kt +++ /dev/null @@ -1,183 +0,0 @@ -package org.thoughtcrime.securesms.notifications - -import android.content.Context -import androidx.collection.MutableLongLongMap -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.flow.FlowCollector -import kotlinx.coroutines.flow.merge -import network.loki.messenger.R -import org.session.libsession.utilities.Address -import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.auth.LoginStateRepository -import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities -import org.thoughtcrime.securesms.database.MmsDatabase -import org.thoughtcrime.securesms.database.MmsSmsDatabase -import org.thoughtcrime.securesms.database.MmsSmsDatabaseExt.getMessages -import org.thoughtcrime.securesms.database.ReactionDatabase -import org.thoughtcrime.securesms.database.RecipientRepository -import org.thoughtcrime.securesms.database.SmsDatabase -import org.thoughtcrime.securesms.database.ThreadDatabase -import org.thoughtcrime.securesms.database.getLastSeen -import org.thoughtcrime.securesms.database.model.MessageChanges -import org.thoughtcrime.securesms.database.model.MessageId -import org.thoughtcrime.securesms.database.model.NotifyType -import org.thoughtcrime.securesms.database.model.ThreadChanges -import org.thoughtcrime.securesms.preferences.PreferenceStorage -import org.thoughtcrime.securesms.util.AppVisibilityManager -import org.thoughtcrime.securesms.util.AvatarUtils -import org.thoughtcrime.securesms.util.CurrentActivityObserver -import javax.inject.Inject -import javax.inject.Singleton -import kotlin.math.max - -/** - * Handles notifications in [NotificationPrivacy.ShowNameOnly] mode. - * - * Shows one per-thread notification with the thread name and avatar, but replaces the message - * body with a generic "You've got a new message" string. No reaction notifications are shown. - * The [NotifyType.MENTIONS] per-thread filter is still respected. - */ -@Singleton -class NameOnlyNotificationHandler @Inject constructor( - @ApplicationContext context: Context, - threadDb: ThreadDatabase, - private val mmsSmsDatabase: MmsSmsDatabase, - private val mmsDatabase: MmsDatabase, - private val smsDatabase: SmsDatabase, - private val reactionDb: ReactionDatabase, - private val loginStateRepository: LoginStateRepository, - recipientRepository: RecipientRepository, - currentActivityObserver: CurrentActivityObserver, - avatarUtils: AvatarUtils, - avatarBitmapCache: AvatarBitmapCache, - channels: NotificationChannelManager, - notificationManager: NotificationManagerCompat, - prefs: PreferenceStorage, - appVisibilityManager: AppVisibilityManager, -) : ThreadBasedNotificationHandler( - context = context, - currentActivityObserver = currentActivityObserver, - avatarUtils = avatarUtils, - channels = channels, - recipientRepository = recipientRepository, - avatarBitmapCache = avatarBitmapCache, - notificationManager = notificationManager, - prefs = prefs, - appVisibilityManager = appVisibilityManager, - threadDatabase = threadDb, -) { - suspend fun process() { - merge( - threadDb.changeNotification, - mmsDatabase.changeNotification, - smsDatabase.changeNotification, - reactionDb.changeNotification, - ).collect(object : FlowCollector { - private val lastNotifiedMessageTimestamp = MutableLongLongMap() - - override suspend fun emit(value: Any) { - when (value) { - is ThreadChanges -> { - val newLastSeen = threadDb.getLastSeen(value.address)?.toEpochMilliseconds() - ?: return - - // Cancel the thread notification if the whole thread is read - getActiveThreadNotification(value.id) - ?.takeIf { it.notification.`when` <= newLastSeen } - ?.let { notificationManager.cancel(it.tag, it.id) } - - return - } - - is MessageChanges if value.changeType == MessageChanges.ChangeType.Added -> { - val (threadAddress, lastSeen, threadNotifyType, threadRecipient) = - getThreadDataIfEligibleForNotification(value.threadId) ?: return - - val latestMessage = mmsSmsDatabase.getMessages(value.ids) - .filter { msg -> - !msg.isOutgoing && !msg.isDeleted && msg.dateSent > lastSeen && - (threadNotifyType != NotifyType.MENTIONS || - MentionUtilities.mentionsMe(msg.body, recipientRepository)) - } - .maxByOrNull { it.dateSent } - - if (latestMessage == null) return - - val lastNotified = - lastNotifiedMessageTimestamp.getOrDefault(value.threadId, 0L) - if (latestMessage.dateSent <= lastNotified) return - - lastNotifiedMessageTimestamp.put(value.threadId, latestMessage.dateSent) - - postOrUpdateNotification( - threadAddress = threadAddress, - threadRecipient = threadRecipient, - threadId = value.threadId, - messages = listOf(NotificationCompat.MessagingStyle.Message( - context.resources.getQuantityText(R.plurals.messageNew, 1), - latestMessage.dateSent, - latestMessage.toPerson(null), - )), - canReply = false, - silent = false, - ) - } - - is MessageId -> { - // Received reaction updates - val message = mmsSmsDatabase.getMessageById(value) ?: run { - Log.w(TAG, "Unable to get message for id=$value") - return - } - - val (threadAddress, lastSeen, threadNotifyType, threadRecipient) = - getThreadDataIfEligibleForNotification(message.threadId) ?: return - - // Community's reaction is not notified - if (threadAddress is Address.Community) return - - // Only notify reaction when notify type is ALL - if (threadNotifyType != NotifyType.ALL) return - - val reactions = reactionDb.getIncomingReactionsForMyMessages( - threadId = message.threadId, - minSendTimeMsExclusive = max( - lastSeen, - lastNotifiedMessageTimestamp.getOrDefault(message.threadId, 0L) - ), - myId = loginStateRepository.requireLocalAccountId(), - ) - - if (reactions.isEmpty()) { - // Nothing new - return - } - - val notified = reactions.maxBy { it.dateSent } - lastNotifiedMessageTimestamp.put(message.threadId, notified.dateSent) - - postOrUpdateNotification( - threadAddress = threadAddress, - threadRecipient = threadRecipient, - threadId = message.threadId, - messages = listOf(NotificationCompat.MessagingStyle.Message( - context.resources.getQuantityText(R.plurals.messageNew, 1), - notified.dateSent, - notified.toPerson(null), - )), - canReply = false, - silent = false, - ) - } - } - - } - }) - } - - companion object { - private const val TAG = "NameOnlyNotificationHandler" - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationProcessor.kt index d05b5ccdc7..6c52fe9789 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationProcessor.kt @@ -14,8 +14,8 @@ import javax.inject.Singleton * Reactive notification processor that replaces the poll-based DefaultMessageNotifier. * * Watches the user's notification privacy preference and delegates to the appropriate handler: - * - [NotificationPrivacy.ShowNameAndContent] → [FullNotificationHandler] - * - [NotificationPrivacy.ShowNameOnly] → [NameOnlyNotificationHandler] + * - [NotificationPrivacy.ShowNameAndContent] → [FullAndNameOnlyNotificationHandler] + * - [NotificationPrivacy.ShowNameOnly] → [FullAndNameOnlyNotificationHandler] * - [NotificationPrivacy.ShowNoNameOrContent] → [NoNameOrContentNotificationHandler] * * Key behaviours: @@ -27,8 +27,7 @@ import javax.inject.Singleton @Singleton class NotificationProcessor @Inject constructor( private val prefs: PreferenceStorage, - private val fullHandler: FullNotificationHandler, - private val nameOnlyHandler: NameOnlyNotificationHandler, + private val fullHandler: FullAndNameOnlyNotificationHandler, private val noNameOrContentHandler: NoNameOrContentNotificationHandler, ) : AuthAwareComponent { @@ -37,8 +36,7 @@ class NotificationProcessor @Inject constructor( .collectLatest { privacy -> Log.d(TAG, "Start processing notification for $privacy") when (privacy) { - NotificationPrivacy.ShowNameAndContent -> fullHandler.process() - NotificationPrivacy.ShowNameOnly -> nameOnlyHandler.process() + NotificationPrivacy.ShowNameOnly, NotificationPrivacy.ShowNameAndContent -> fullHandler.process() NotificationPrivacy.ShowNoNameOrContent -> noNameOrContentHandler.process() } }