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