From 8c7192435499541c8960af2ecdc31dfa4ca61f29 Mon Sep 17 00:00:00 2001 From: Isaac Serrano Date: Sun, 11 Oct 2026 22:20:17 -0700 Subject: [PATCH 1/2] feat: add "Follow Do Not Disturb" setting for island notifications --- .../data/repository/SettingsRepository.kt | 4 ++++ .../domain/model/ActiveNotificationAlert.kt | 1 + .../essentials/domain/registry/FeatureRegistry.kt | 5 +++++ .../plugins/notifications/NotificationsPlugin.kt | 2 ++ .../essentials/services/NotificationListener.kt | 15 ++++++++++++++- .../ui/features/display/IslandSubSettingsUI.kt | 12 ++++++++++++ .../essentials/viewmodels/MainViewModel.kt | 10 ++++++++++ app/src/main/res/values/strings.xml | 2 ++ 8 files changed, 50 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt index 63519c749..507ae9618 100644 --- a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt +++ b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt @@ -628,6 +628,7 @@ class SettingsRepository( const val KEY_ISLAND_NOTIF_KEEP_PROGRESS = "island_notif_keep_progress" const val KEY_ISLAND_NOTIF_QUEUE = "island_notif_queue" const val KEY_ISLAND_NOTIF_SKIP_SILENT = "island_notif_skip_silent" + const val KEY_ISLAND_NOTIF_FOLLOW_DND = "island_notif_follow_dnd" const val KEY_ISLAND_NOTIF_FILTER_APPS = "island_notif_filter_apps" const val KEY_ISLAND_NOTIF_FILTER_APPS_LIST = "island_notif_filter_apps_list" const val KEY_ISLAND_SHOW_NOTIFICATIONS = "island_show_notifications" @@ -4165,6 +4166,9 @@ class SettingsRepository( fun isIslandNotifSkipSilentEnabled(): Boolean = getBoolean(KEY_ISLAND_NOTIF_SKIP_SILENT, true) fun setIslandNotifSkipSilentEnabled(enabled: Boolean) = putBoolean(KEY_ISLAND_NOTIF_SKIP_SILENT, enabled) + fun isIslandNotifFollowDndEnabled(): Boolean = getBoolean(KEY_ISLAND_NOTIF_FOLLOW_DND, true) + fun setIslandNotifFollowDndEnabled(enabled: Boolean) = putBoolean(KEY_ISLAND_NOTIF_FOLLOW_DND, enabled) + fun isIslandNotifFilterAppsEnabled(): Boolean = getBoolean(KEY_ISLAND_NOTIF_FILTER_APPS, false) fun setIslandNotifFilterAppsEnabled(enabled: Boolean) = putBoolean(KEY_ISLAND_NOTIF_FILTER_APPS, enabled) diff --git a/app/src/main/java/com/sameerasw/essentials/domain/model/ActiveNotificationAlert.kt b/app/src/main/java/com/sameerasw/essentials/domain/model/ActiveNotificationAlert.kt index 655ed8184..78250276c 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/model/ActiveNotificationAlert.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/model/ActiveNotificationAlert.kt @@ -27,5 +27,6 @@ data class ActiveNotificationAlert( val actions: List = emptyList(), val chatIcon: Bitmap? = null, val isSilent: Boolean = false, + val blockedByDnd: Boolean = false, ) diff --git a/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt b/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt index 32dfcaf44..61443cc45 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt @@ -978,6 +978,11 @@ object FeatureRegistry { R.string.island_notif_skip_silent_desc, "island_notif_skip_silent", ), + SearchSetting( + R.string.island_notif_follow_dnd_title, + R.string.island_notif_follow_dnd_desc, + "island_notif_follow_dnd", + ), SearchSetting( R.string.island_notif_keep_progress_title, R.string.island_notif_keep_progress_desc, diff --git a/app/src/main/java/com/sameerasw/essentials/island/plugins/notifications/NotificationsPlugin.kt b/app/src/main/java/com/sameerasw/essentials/island/plugins/notifications/NotificationsPlugin.kt index 153468a0a..3b055b03c 100644 --- a/app/src/main/java/com/sameerasw/essentials/island/plugins/notifications/NotificationsPlugin.kt +++ b/app/src/main/java/com/sameerasw/essentials/island/plugins/notifications/NotificationsPlugin.kt @@ -42,6 +42,7 @@ class NotificationsPlugin : BaseIslandPlugin() { SettingsRepository.KEY_ISLAND_SHOW_GLOW, SettingsRepository.KEY_ISLAND_NOTIF_QUEUE, SettingsRepository.KEY_ISLAND_NOTIF_SKIP_SILENT, + SettingsRepository.KEY_ISLAND_NOTIF_FOLLOW_DND, SettingsRepository.KEY_ISLAND_NOTIF_FILTER_APPS, SettingsRepository.KEY_ISLAND_NOTIF_FILTER_APPS_LIST, SettingsRepository.KEY_ISLAND_NOTIF_TAP_TO_OPEN, @@ -162,6 +163,7 @@ class NotificationsPlugin : BaseIslandPlugin() { private fun passesFilters(alert: ActiveNotificationAlert): Boolean { if (alert.isSilent && settings.isIslandNotifSkipSilentEnabled()) return false + if (alert.blockedByDnd && settings.isIslandNotifFollowDndEnabled()) return false if (!settings.isIslandNotifFilterAppsEnabled()) return true return settings.loadIslandNotifFilterApps().any { it.isEnabled && it.packageName == alert.packageName } } diff --git a/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt index 053d330fd..f040e02a2 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt @@ -266,6 +266,16 @@ class NotificationListener : NotificationListenerService() { return false } + // Do Not Disturb holds this notification back; apps and conversations allowed through DND still pass. + fun isBlockedByDnd( + sbn: StatusBarNotification, + rankingMap: RankingMap? = null, + ): Boolean { + val map = rankingMap ?: currentRanking ?: return false + val ranking = Ranking() + return map.getRanking(sbn.key, ranking) && !ranking.matchesInterruptionFilter() + } + private fun populateActiveUnreadNotifications() { unreadNotifications.clear() try { @@ -1013,7 +1023,10 @@ class NotificationListener : NotificationListenerService() { } if (!isRepost && isHeadsUpNotification(sbn, rankingMap, allowSilent = true)) { - val alert = extractNotificationAlert(sbn)?.copy(isSilent = isSilentNotification(sbn, rankingMap)) + val alert = extractNotificationAlert(sbn)?.copy( + isSilent = isSilentNotification(sbn, rankingMap), + blockedByDnd = isBlockedByDnd(sbn, rankingMap), + ) if (alert != null) { notifyAlertPosted(alert) } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/features/display/IslandSubSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/features/display/IslandSubSettingsUI.kt index c9088c5fe..575fe3f79 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/features/display/IslandSubSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/features/display/IslandSubSettingsUI.kt @@ -935,6 +935,18 @@ fun IslandNotificationSettingsUI( modifier = Modifier.highlight(highlightSetting == "island_notif_skip_silent"), ) + IconToggleItem( + iconRes = R.drawable.rounded_do_not_disturb_on_24, + title = stringResource(R.string.island_notif_follow_dnd_title), + description = stringResource(R.string.island_notif_follow_dnd_desc), + isChecked = viewModel.isIslandNotifFollowDnd.value, + onCheckedChange = { checked -> + HapticUtil.performVirtualKeyHaptic(view) + viewModel.setIslandNotifFollowDnd(checked) + }, + modifier = Modifier.highlight(highlightSetting == "island_notif_follow_dnd"), + ) + IconToggleItem( iconRes = R.drawable.rounded_downloading_24, title = stringResource(R.string.island_notif_keep_progress_title), diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt index a7733beea..99f679edd 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -264,6 +264,7 @@ class MainViewModel : ViewModel() { val isIslandNotifKeepProgress = mutableStateOf(true) val isIslandNotifQueue = mutableStateOf(true) val isIslandNotifSkipSilent = mutableStateOf(true) + val isIslandNotifFollowDnd = mutableStateOf(true) val isIslandNotifFilterApps = mutableStateOf(false) val isIslandShowNotifications = mutableStateOf(true) val isIslandNotifTapToOpen = mutableStateOf(false) @@ -924,6 +925,9 @@ class MainViewModel : ViewModel() { SettingsRepository.KEY_ISLAND_NOTIF_SKIP_SILENT -> isIslandNotifSkipSilent.value = settingsRepository.isIslandNotifSkipSilentEnabled() + SettingsRepository.KEY_ISLAND_NOTIF_FOLLOW_DND -> + isIslandNotifFollowDnd.value = settingsRepository.isIslandNotifFollowDndEnabled() + SettingsRepository.KEY_ISLAND_NOTIF_FILTER_APPS -> isIslandNotifFilterApps.value = settingsRepository.isIslandNotifFilterAppsEnabled() @@ -2433,6 +2437,7 @@ class MainViewModel : ViewModel() { isIslandNotifKeepProgress.value = settingsRepository.isIslandNotifKeepProgressEnabled() isIslandNotifQueue.value = settingsRepository.isIslandNotifQueueEnabled() isIslandNotifSkipSilent.value = settingsRepository.isIslandNotifSkipSilentEnabled() + isIslandNotifFollowDnd.value = settingsRepository.isIslandNotifFollowDndEnabled() isIslandNotifFilterApps.value = settingsRepository.isIslandNotifFilterAppsEnabled() isIslandShowNotifications.value = settingsRepository.isIslandShowNotificationsEnabled() isIslandNotifTapToOpen.value = settingsRepository.isIslandNotifTapToOpenEnabled() @@ -5735,6 +5740,11 @@ class MainViewModel : ViewModel() { settingsRepository.setIslandNotifSkipSilentEnabled(enabled) } + fun setIslandNotifFollowDnd(enabled: Boolean) { + isIslandNotifFollowDnd.value = enabled + settingsRepository.setIslandNotifFollowDndEnabled(enabled) + } + fun setIslandNotifFilterApps(enabled: Boolean) { isIslandNotifFilterApps.value = enabled settingsRepository.setIslandNotifFilterAppsEnabled(enabled) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index eb85a1a44..720b7399d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3221,6 +3221,8 @@ Lock screen Only show notifications from the apps you pick Ignore notifications that arrive without sound + Follow Do Not Disturb + Hide notifications that Do Not Disturb silences Show downloads and other ongoing progress Keep several notifications instead of replacing the current one Tap an expanded notification to open its app From 0c8630ad0a511aa09256aa38a9c8c63d24134a37 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Tue, 13 Oct 2026 12:02:31 +0530 Subject: [PATCH 2/2] fix: update DND blocking logic to account for suppressed visual peek effects --- .../sameerasw/essentials/services/NotificationListener.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt index f040e02a2..02528f2b1 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt @@ -266,14 +266,18 @@ class NotificationListener : NotificationListenerService() { return false } - // Do Not Disturb holds this notification back; apps and conversations allowed through DND still pass. + // Do Not Disturb holds this notification back or suppresses its peek; apps and conversations allowed through still pass. fun isBlockedByDnd( sbn: StatusBarNotification, rankingMap: RankingMap? = null, ): Boolean { val map = rankingMap ?: currentRanking ?: return false val ranking = Ranking() - return map.getRanking(sbn.key, ranking) && !ranking.matchesInterruptionFilter() + if (!map.getRanking(sbn.key, ranking)) return false + val peekSuppressed = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && + ranking.suppressedVisualEffects and NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK != 0 + return !ranking.matchesInterruptionFilter() || peekSuppressed } private fun populateActiveUnreadNotifications() {