Merge pull request #2046 from vitorpamplona/claude/add-reaction-notifications-NzyLO

Add push notifications for post reactions
This commit is contained in:
Vitor Pamplona
2026-03-31 09:01:43 -04:00
committed by GitHub
3 changed files with 138 additions and 0 deletions
@@ -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,
@@ -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,
+6
View File
@@ -751,6 +751,12 @@
<string name="app_notification_dms_summary">New messages</string>
<string name="app_notification_zaps_summary">New zaps</string>
<string name="app_notification_reactions_channel_id" translatable="false">ReactionsID</string>
<string name="app_notification_reactions_channel_name">Reactions</string>
<string name="app_notification_reactions_channel_description">Notifies you when somebody reacts to your post</string>
<string name="app_notification_reactions_channel_message">%1$s reacted to your post</string>
<string name="app_notification_reactions_channel_message_for">for %1$s</string>
<string name="app_notification_reactions_summary">New reactions</string>
<string name="app_notification_chess_channel_id" translatable="false">ChessID</string>
<string name="app_notification_chess_channel_name">Chess</string>
<string name="app_notification_chess_channel_description">Notifies you about chess game events</string>