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 e40adf983..fa8b4e31a 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
@@ -41,6 +41,7 @@ import com.vitorpamplona.amethyst.service.call.notification.CallNotifier
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.sendReplyNotification
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendZapNotification
import com.vitorpamplona.amethyst.service.relayClient.authCommand.model.ScreenAuthAccount
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
@@ -56,10 +57,12 @@ import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUserIds
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
+import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.nip21UriScheme.toNostrUri
+import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
@@ -176,6 +179,8 @@ class EventNotificationConsumer(
is ChatMessageEvent -> notify(event, account)
is ChatMessageEncryptedFileHeaderEvent -> notify(event, account)
is ReactionEvent -> notify(event, account)
+ is TextNoteEvent -> notify(event, account)
+ is CommentEvent -> notify(event, account)
is LiveChessGameAcceptEvent -> notifyChessEvent(event, account, R.string.app_notification_chess_challenge_accepted)
is LiveChessMoveEvent -> notifyChessEvent(event, account, R.string.app_notification_chess_your_turn)
// WelcomeEvent is dispatched directly from processMarmotWelcomeFlow
@@ -648,6 +653,110 @@ class EventNotificationConsumer(
)
}
+ private suspend fun notify(
+ event: TextNoteEvent,
+ account: Account,
+ ) {
+ Log.d(TAG, "New TextNote to Notify")
+
+ // old event being re-broadcast
+ if (event.createdAt < TimeUtils.fifteenMinutesAgo()) return
+
+ // don't notify for own notes
+ if (event.pubKey == account.signer.pubKey) return
+
+ // must be a reply (NIP-10) — plain mentions are not handled on this channel.
+ val replyTargetId = event.replyingTo() ?: return
+
+ // only notify when the reply targets a note authored by the current account.
+ val repliedNote = LocalCache.getNoteIfExists(replyTargetId) ?: return
+ if (repliedNote.author?.pubkeyHex != account.signer.pubKey) return
+
+ notifyReply(event, account, repliedNote.event?.content)
+ }
+
+ private suspend fun notify(
+ event: CommentEvent,
+ account: Account,
+ ) {
+ Log.d(TAG, "New NIP-22 Comment to Notify")
+
+ // old event being re-broadcast
+ if (event.createdAt < TimeUtils.fifteenMinutesAgo()) return
+
+ // don't notify for own comments
+ if (event.pubKey == account.signer.pubKey) return
+
+ // NIP-22 marks direct-reply and root authors. Notify when the current
+ // account is either (someone commenting on our post, or replying to our comment).
+ val pubKey = account.signer.pubKey
+ val isTarget = event.replyAuthorKeys().contains(pubKey) || event.rootAuthorKeys().contains(pubKey)
+ if (!isTarget) return
+
+ val parentContent =
+ event
+ .replyingTo()
+ ?.let { LocalCache.getNoteIfExists(it)?.event?.content }
+
+ notifyReply(event, account, parentContent)
+ }
+
+ private suspend fun notifyReply(
+ event: Event,
+ account: Account,
+ parentContent: String?,
+ ) {
+ val replyNote = LocalCache.getNoteIfExists(event.id) ?: return
+
+ val author = LocalCache.getOrCreateUser(event.pubKey)
+ val user = author.toBestDisplayName()
+ val userPicture = author.profilePicture()
+
+ val title = stringRes(applicationContext, R.string.app_notification_replies_channel_message, user)
+
+ val replyExcerpt =
+ event.content
+ .split("\n")
+ .firstOrNull { it.isNotBlank() }
+ ?.take(280)
+ ?: ""
+
+ val parentExcerpt =
+ parentContent
+ ?.split("\n")
+ ?.firstOrNull { it.isNotBlank() }
+ ?.take(140)
+
+ val content =
+ if (!parentExcerpt.isNullOrBlank()) {
+ replyExcerpt + "\n\n" +
+ stringRes(
+ applicationContext,
+ R.string.app_notification_replies_channel_message_for,
+ parentExcerpt,
+ )
+ } else {
+ replyExcerpt
+ }
+
+ val accountNpub =
+ account.signer.pubKey
+ .hexToByteArray()
+ .toNpub()
+ val noteUri = replyNote.toNEvent() + ACCOUNT_QUERY_PARAM + accountNpub
+
+ notificationManager()
+ .sendReplyNotification(
+ 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/NotificationDispatcher.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationDispatcher.kt
index f4ff5f82d..32900cc9f 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationDispatcher.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationDispatcher.kt
@@ -28,8 +28,10 @@ import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
+import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
+import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip64Chess.challenge.accept.LiveChessGameAcceptEvent
@@ -75,6 +77,8 @@ class NotificationDispatcher(
PrivateDmEvent.KIND,
LnZapEvent.KIND,
ReactionEvent.KIND,
+ TextNoteEvent.KIND,
+ CommentEvent.KIND,
LiveChessGameAcceptEvent.KIND,
LiveChessMoveEvent.KIND,
WakeUpEvent.KIND,
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 a3aeefddb..b1815d605 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
@@ -48,11 +48,13 @@ object NotificationUtils {
private var zapChannel: NotificationChannel? = null
private var reactionChannel: NotificationChannel? = null
private var chessChannel: NotificationChannel? = null
+ private var replyChannel: 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 const val CHESS_GROUP_KEY = "com.vitorpamplona.amethyst.CHESS_NOTIFICATION"
+ private const val REPLY_GROUP_KEY = "com.vitorpamplona.amethyst.REPLY_NOTIFICATION"
const val REPLY_ACTION = "com.vitorpamplona.amethyst.REPLY_ACTION"
const val MARK_READ_ACTION = "com.vitorpamplona.amethyst.MARK_READ_ACTION"
@@ -65,6 +67,7 @@ object NotificationUtils {
private const val ZAP_SUMMARY_ID = 0x20000
private const val REACTION_SUMMARY_ID = 0x40000
private const val CHESS_SUMMARY_ID = 0x30000
+ private const val REPLY_SUMMARY_ID = 0x50000
fun getOrCreateDMChannel(applicationContext: Context): NotificationChannel {
if (dmChannel != null) return dmChannel!!
@@ -150,6 +153,27 @@ object NotificationUtils {
return chessChannel!!
}
+ fun getOrCreateReplyChannel(applicationContext: Context): NotificationChannel {
+ if (replyChannel != null) return replyChannel!!
+
+ replyChannel =
+ NotificationChannel(
+ stringRes(applicationContext, R.string.app_notification_replies_channel_id),
+ stringRes(applicationContext, R.string.app_notification_replies_channel_name),
+ NotificationManager.IMPORTANCE_DEFAULT,
+ ).apply {
+ description =
+ stringRes(applicationContext, R.string.app_notification_replies_channel_description)
+ }
+
+ val notificationManager: NotificationManager =
+ applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+
+ notificationManager.createNotificationChannel(replyChannel!!)
+
+ return replyChannel!!
+ }
+
suspend fun NotificationManager.sendReactionNotification(
id: String,
messageBody: String,
@@ -206,6 +230,34 @@ object NotificationUtils {
)
}
+ suspend fun NotificationManager.sendReplyNotification(
+ id: String,
+ messageBody: String,
+ messageTitle: String,
+ time: Long,
+ pictureUrl: String?,
+ uri: String,
+ applicationContext: Context,
+ ) {
+ getOrCreateReplyChannel(applicationContext)
+ val channelId = stringRes(applicationContext, R.string.app_notification_replies_channel_id)
+
+ sendNotification(
+ id = id,
+ messageBody = messageBody,
+ messageTitle = messageTitle,
+ time = time,
+ pictureUrl = pictureUrl,
+ uri = uri,
+ channelId = channelId,
+ notificationGroupKey = REPLY_GROUP_KEY,
+ category = NotificationCompat.CATEGORY_SOCIAL,
+ summaryId = REPLY_SUMMARY_ID,
+ summaryText = stringRes(applicationContext, R.string.app_notification_replies_summary),
+ applicationContext = applicationContext,
+ )
+ }
+
suspend fun NotificationManager.sendZapNotification(
id: String,
messageBody: String,
diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml
index 4bbb8f3e0..43745ce20 100644
--- a/amethyst/src/main/res/values/strings.xml
+++ b/amethyst/src/main/res/values/strings.xml
@@ -876,6 +876,12 @@
%1$s accepted your chess challenge
%1$s moved — your turn
Chess updates
+ RepliesID
+ Replies
+ Notifies you when somebody replies to your post
+ %1$s replied
+ on: %1$s
+ New replies
Incoming calls