From bd8beb43e1e3156a6a6be3ff3bcfbf9c8a9106dc Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 20:27:32 +0000 Subject: [PATCH] feat: add Notification setting to show or hide Messages on the Notification tab Adds a "Show Messages" toggle to the Display section of Notification Settings. When disabled, direct/group messages (NIP-17 chats, NIP-04 DMs, and Marmot group messages) are filtered out of the Notification tab, keeping them only in the Messages tab. Defaults to on, preserving existing behavior. The setting is persisted per-account in AccountSettings/LocalPreferences and is included in the notification feed key so the feed refreshes immediately when toggled. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_018FwwcaBRyvnJFWaDLoQ16d --- .../vitorpamplona/amethyst/LocalPreferences.kt | 4 ++++ .../amethyst/model/AccountSettings.kt | 8 ++++++++ .../notifications/NotificationScreen.kt | 5 ++++- .../notifications/dal/NotificationFeedFilter.kt | 17 ++++++++++++++++- .../settings/NotificationSettingsScreen.kt | 10 ++++++++++ amethyst/src/main/res/values/strings.xml | 2 ++ 6 files changed, 44 insertions(+), 2 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt index 4f5854a76c..449c7d1ef2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt @@ -152,6 +152,7 @@ private object PrefKeys { const val HIDE_NIP_17_WARNING_DIALOG = "hide_nip24_warning_dialog" // delete later const val ALWAYS_ON_NOTIFICATION_SERVICE = "always_on_notification_service" const val SPLIT_NOTIFICATIONS_ENABLED = "split_notifications_enabled" + const val SHOW_MESSAGES_IN_NOTIFICATIONS = "show_messages_in_notifications" // One-shot stamp: set once an account has gone through the notifications // Global -> Selected (Curated) migration (or was created after it shipped). @@ -467,6 +468,7 @@ object LocalPreferences { putBoolean(PrefKeys.CALLS_ENABLED, settings.callsEnabled.value) putBoolean(PrefKeys.ALWAYS_ON_NOTIFICATION_SERVICE, settings.alwaysOnNotificationService.value) putBoolean(PrefKeys.SPLIT_NOTIFICATIONS_ENABLED, settings.splitNotificationsEnabled.value) + putBoolean(PrefKeys.SHOW_MESSAGES_IN_NOTIFICATIONS, settings.showMessagesInNotifications.value) // Any account that reaches a save has its notification filter in its // post-split meaning, so stamp it as migrated. This keeps the one-shot // Global -> Selected rewrite from ever touching it again and preserves a @@ -582,6 +584,7 @@ object LocalPreferences { val callsEnabled = getBoolean(PrefKeys.CALLS_ENABLED, true) val alwaysOnNotificationService = getBoolean(PrefKeys.ALWAYS_ON_NOTIFICATION_SERVICE, false) val splitNotificationsEnabled = getBoolean(PrefKeys.SPLIT_NOTIFICATIONS_ENABLED, false) + val showMessagesInNotifications = getBoolean(PrefKeys.SHOW_MESSAGES_IN_NOTIFICATIONS, true) val hasDonatedInVersion = getStringSet(PrefKeys.HAS_DONATED_IN_VERSION, null) ?: setOf() val dismissedPollNoteIds = getStringSet(PrefKeys.DISMISSED_POLL_NOTE_IDS, null) ?: setOf() val viewedPollResultNoteIdsStr = getString(PrefKeys.VIEWED_POLL_RESULT_NOTE_IDS, null) @@ -742,6 +745,7 @@ object LocalPreferences { hideNIP17WarningDialog = hideNIP17WarningDialog, alwaysOnNotificationService = MutableStateFlow(alwaysOnNotificationService), splitNotificationsEnabled = MutableStateFlow(splitNotificationsEnabled), + showMessagesInNotifications = MutableStateFlow(showMessagesInNotifications), backupUserMetadata = latestUserMetadata.await(), backupContactList = latestContactList.await(), backupNIP65RelayList = latestNip65RelayList.await(), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt index 93e89ca49e..a20b82ea24 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt @@ -217,6 +217,7 @@ class AccountSettings( var hideNIP17WarningDialog: Boolean = false, val alwaysOnNotificationService: MutableStateFlow = MutableStateFlow(false), val splitNotificationsEnabled: MutableStateFlow = MutableStateFlow(false), + val showMessagesInNotifications: MutableStateFlow = MutableStateFlow(true), var backupUserMetadata: MetadataEvent? = null, var backupContactList: ContactListEvent? = null, var backupDMRelayList: ChatMessageRelayListEvent? = null, @@ -295,6 +296,13 @@ class AccountSettings( return newValue } + fun toggleShowMessagesInNotifications(): Boolean { + val newValue = !showMessagesInNotifications.value + showMessagesInNotifications.tryEmit(newValue) + saveAccountSettings() + return newValue + } + // --- // Zaps and Reactions // --- diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationScreen.kt index 1f179678fe..b3e6823b45 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationScreen.kt @@ -313,8 +313,11 @@ fun WatchAccountForNotifications( ) { val listState by accountViewModel.account.liveNotificationFollowLists.collectAsStateWithLifecycle() + val showMessages by + accountViewModel.account.settings.showMessagesInNotifications + .collectAsStateWithLifecycle() - LaunchedEffect(accountViewModel, listState) { + LaunchedEffect(accountViewModel, listState, showMessages) { notifFeedContentState.checkKeysInvalidateDataAndSendToTop() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt index a0fc6989ec..4bdfe16166 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt @@ -246,7 +246,7 @@ class NotificationFeedFilter( } } - override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList().code + override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList().code + "-" + account.settings.showMessagesInNotifications.value fun followList(): TopFilter = modeOverride ?: account.settings.defaultNotificationFollowList.value @@ -299,6 +299,10 @@ class NotificationFeedFilter( ): Boolean { val loggedInUserHex = account.userProfile().pubkeyHex + // When the user opts out of seeing Messages on the Notification tab, drop + // direct/group message events (DMs and Marmot group chats) entirely. + val showMessages = account.settings.showMessagesInNotifications.value + // Marmot group messages are only acceptable if the gathering chatroom is // actually in the current account's group list. Notes are stored in the // global LocalCache and accumulate a gatherer reference from every @@ -308,6 +312,7 @@ class NotificationFeedFilter( // instance held by this account's list. val marmotGatherers = it.inGatherers?.filterIsInstance() if (!marmotGatherers.isNullOrEmpty()) { + if (!showMessages) return false val inCurrentAccount = marmotGatherers.any { room -> account.marmotGroupList.rooms.get(room.nostrGroupId) === room @@ -317,6 +322,16 @@ class NotificationFeedFilter( } val noteEvent = it.event + + if (!showMessages && + ( + noteEvent is ChatMessageEvent || + noteEvent is ChatMessageEncryptedFileHeaderEvent || + noteEvent is PrivateDmEvent + ) + ) { + return false + } val notifAuthor = if (noteEvent is LnZapEvent) { val zapRequest = noteEvent.zapRequest diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NotificationSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NotificationSettingsScreen.kt index b2914644d3..d0b82a09e6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NotificationSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NotificationSettingsScreen.kt @@ -115,6 +115,8 @@ private fun DeliverySection(accountViewModel: AccountViewModel) { private fun DisplaySection(accountViewModel: AccountViewModel) { val splitByFollows by accountViewModel.account.settings.splitNotificationsEnabled .collectAsStateWithLifecycle() + val showMessages by accountViewModel.account.settings.showMessagesInNotifications + .collectAsStateWithLifecycle() SettingsSection(R.string.notification_settings_section_display) { SettingsSwitchTile( @@ -124,6 +126,14 @@ private fun DisplaySection(accountViewModel: AccountViewModel) { checked = splitByFollows, onCheckedChange = { accountViewModel.account.settings.toggleSplitNotificationsEnabled() }, ) + SettingsDivider() + SettingsSwitchTile( + icon = MaterialSymbols.Mail, + title = R.string.show_messages_in_notifications_setting_title, + description = R.string.show_messages_in_notifications_setting_description, + checked = showMessages, + onCheckedChange = { accountViewModel.account.settings.toggleShowMessagesInNotifications() }, + ) } } diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 91a60cadfc..e5ce712d1c 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -1281,6 +1281,8 @@ Split notifications by Follows Show two notification tabs — Following (people you follow) and Everyone. The unread indicator glows only for activity from people you follow. + Show Messages + Include direct and group messages on the Notification tab. Turn off to keep messages only in the Messages tab. Following Everyone