diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt index 33237a36f..dc71af1ac 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt @@ -31,6 +31,7 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendChessNotification import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendDMNotification +import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendReactionNotification import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendZapNotification import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.stringRes @@ -44,6 +45,7 @@ import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEven import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent import com.vitorpamplona.quartz.nip19Bech32.toNpub import com.vitorpamplona.quartz.nip21UriScheme.toNostrUri +import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent @@ -123,6 +125,10 @@ class EventNotificationConsumer( is ChatMessageEncryptedFileHeaderEvent -> { notify(innerEvent, account) } + + is ReactionEvent -> { + notify(innerEvent, account) + } is LiveChessGameAcceptEvent -> { notifyChessEvent(innerEvent, account, R.string.app_notification_chess_challenge_accepted) @@ -504,6 +510,78 @@ class EventNotificationConsumer( } } + private suspend fun notify( + event: ReactionEvent, + account: Account, + ) { + Log.d(TAG, "New Reaction to Notify") + + // old event being re-broadcast + if (event.createdAt < TimeUtils.fifteenMinutesAgo()) return + + // don't notify for own reactions + if (event.pubKey == account.signer.pubKey) return + + // only notify if the reaction is for the current user + if (!event.isTaggedUser(account.signer.pubKey)) return + + val reactedPostId = event.originalPost().firstOrNull() ?: return + val reactedNote = LocalCache.checkGetOrCreateNote(reactedPostId) + + val author = LocalCache.getOrCreateUser(event.pubKey) + val user = author.toBestDisplayName() + val userPicture = author.profilePicture() + + val reactionContent = event.content + val reactionSymbol = + when { + reactionContent == ReactionEvent.LIKE || reactionContent.isBlank() -> "\uD83E\uDD19" + reactionContent == ReactionEvent.DISLIKE -> "\uD83D\uDC4E" + else -> reactionContent + } + + val title = "$reactionSymbol $user" + + val reactedContent = + reactedNote + ?.event + ?.content + ?.split("\n") + ?.firstOrNull() ?: "" + + val content = + if (reactedContent.isNotBlank()) { + stringRes( + applicationContext, + R.string.app_notification_reactions_channel_message_for, + reactedContent, + ) + } else { + stringRes( + applicationContext, + R.string.app_notification_reactions_channel_message, + user, + ) + } + + val noteUri = + "notifications$ACCOUNT_QUERY_PARAM" + + account.signer.pubKey + .hexToByteArray() + .toNpub() + + notificationManager() + .sendReactionNotification( + event.id, + content, + title, + event.createdAt, + userPicture, + noteUri, + applicationContext, + ) + } + private suspend fun notifyChessEvent( event: BaseChessEvent, account: Account, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationUtils.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationUtils.kt index a418f223e..80ddfffaf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationUtils.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationUtils.kt @@ -45,6 +45,10 @@ import kotlinx.coroutines.withContext object NotificationUtils { private var dmChannel: NotificationChannel? = null private var zapChannel: NotificationChannel? = null + private var reactionChannel: NotificationChannel? = null + private const val DM_GROUP_KEY = "com.vitorpamplona.amethyst.DM_NOTIFICATION" + private const val ZAP_GROUP_KEY = "com.vitorpamplona.amethyst.ZAP_NOTIFICATION" + private const val REACTION_GROUP_KEY = "com.vitorpamplona.amethyst.REACTION_NOTIFICATION" private var chessChannel: NotificationChannel? = null private const val DM_GROUP_KEY = "com.vitorpamplona.amethyst.DM_NOTIFICATION" private const val ZAP_GROUP_KEY = "com.vitorpamplona.amethyst.ZAP_NOTIFICATION" @@ -58,6 +62,7 @@ object NotificationUtils { private const val DM_SUMMARY_ID = 0x10000 private const val ZAP_SUMMARY_ID = 0x20000 + private const val REACTION_SUMMARY_ID = 0x40000 private const val CHESS_SUMMARY_ID = 0x30000 fun getOrCreateDMChannel(applicationContext: Context): NotificationChannel { @@ -102,6 +107,27 @@ object NotificationUtils { return zapChannel!! } + fun getOrCreateReactionChannel(applicationContext: Context): NotificationChannel { + if (reactionChannel != null) return reactionChannel!! + + reactionChannel = + NotificationChannel( + stringRes(applicationContext, R.string.app_notification_reactions_channel_id), + stringRes(applicationContext, R.string.app_notification_reactions_channel_name), + NotificationManager.IMPORTANCE_DEFAULT, + ).apply { + description = + stringRes(applicationContext, R.string.app_notification_reactions_channel_description) + } + + val notificationManager: NotificationManager = + applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + notificationManager.createNotificationChannel(reactionChannel!!) + + return reactionChannel!! + } + fun getOrCreateChessChannel(applicationContext: Context): NotificationChannel { if (chessChannel != null) return chessChannel!! @@ -122,7 +148,35 @@ object NotificationUtils { return chessChannel!! } + + suspend fun NotificationManager.sendChessNotification( + id: String, + messageBody: String, + messageTitle: String, + time: Long, + pictureUrl: String?, + uri: String, + applicationContext: Context, + ) { + getOrCreateReactionChannel(applicationContext) + val channelId = stringRes(applicationContext, R.string.app_notification_reactions_channel_id) + sendNotification( + id = id, + messageBody = messageBody, + messageTitle = messageTitle, + time = time, + pictureUrl = pictureUrl, + uri = uri, + channelId = channelId, + notificationGroupKey = REACTION_GROUP_KEY, + category = NotificationCompat.CATEGORY_SOCIAL, + summaryId = REACTION_SUMMARY_ID, + summaryText = stringRes(applicationContext, R.string.app_notification_reactions_summary), + applicationContext = applicationContext, + ) + } + suspend fun NotificationManager.sendChessNotification( id: String, messageBody: String, diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index baa043fc9..26c6c2eb7 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -751,6 +751,12 @@ New messages New zaps + ReactionsID + Reactions + Notifies you when somebody reacts to your post + %1$s reacted to your post + for %1$s + New reactions ChessID Chess Notifies you about chess game events