mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-08 23:54:39 +00:00
feat: add push notifications for reactions
Add a new notification channel for reactions (kind 7 events) so users get notified when someone reacts to their posts. Follows the same pattern as zap notifications with deduplication, time-window filtering, and grouped summaries. https://claude.ai/code/session_01J9rAA4oeFEfNcYD6GoJqqt
This commit is contained in:
+75
@@ -30,6 +30,7 @@ import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
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
|
||||
@@ -43,6 +44,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
|
||||
@@ -106,6 +108,7 @@ class EventNotificationConsumer(
|
||||
when (innerEvent) {
|
||||
is PrivateDmEvent -> notify(innerEvent, account)
|
||||
is LnZapEvent -> notify(innerEvent, account)
|
||||
is ReactionEvent -> notify(innerEvent, account)
|
||||
is ChatMessageEvent -> notify(innerEvent, account)
|
||||
is ChatMessageEncryptedFileHeaderEvent -> notify(innerEvent, account)
|
||||
}
|
||||
@@ -481,6 +484,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,
|
||||
)
|
||||
}
|
||||
|
||||
fun notificationManager(): NotificationManager =
|
||||
ContextCompat.getSystemService(applicationContext, NotificationManager::class.java)
|
||||
as NotificationManager
|
||||
|
||||
+52
@@ -45,8 +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"
|
||||
const val REPLY_ACTION = "com.vitorpamplona.amethyst.REPLY_ACTION"
|
||||
const val MARK_READ_ACTION = "com.vitorpamplona.amethyst.MARK_READ_ACTION"
|
||||
const val KEY_REPLY_TEXT = "key_reply_text"
|
||||
@@ -56,6 +58,7 @@ object NotificationUtils {
|
||||
|
||||
private const val DM_SUMMARY_ID = 0x10000
|
||||
private const val ZAP_SUMMARY_ID = 0x20000
|
||||
private const val REACTION_SUMMARY_ID = 0x30000
|
||||
|
||||
fun getOrCreateDMChannel(applicationContext: Context): NotificationChannel {
|
||||
if (dmChannel != null) return dmChannel!!
|
||||
@@ -99,6 +102,55 @@ 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!!
|
||||
}
|
||||
|
||||
suspend fun NotificationManager.sendReactionNotification(
|
||||
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.sendZapNotification(
|
||||
id: String,
|
||||
messageBody: String,
|
||||
|
||||
@@ -751,6 +751,13 @@
|
||||
<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="reply_notify">Notify: </string>
|
||||
|
||||
<string name="channel_list_join_conversation">Join Conversation</string>
|
||||
|
||||
Reference in New Issue
Block a user