diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/data/reminders/ReminderChannels.kt b/app/src/main/java/de/jeanlucmakiola/calendula/data/reminders/ReminderChannels.kt index 95fbfff5..0a82fe55 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/data/reminders/ReminderChannels.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/data/reminders/ReminderChannels.kt @@ -106,15 +106,17 @@ class ReminderChannels @Inject constructor( fun status(calendarIds: Collection): ReminderChannelStatus { val manager = manager val appEnabled = manager.areNotificationsEnabled() - val blocked = blockedReminderCalendars( - calendarIds = calendarIds, - appEnabled = appEnabled, - channels = manager.notificationChannels.associate { - it.id to ReminderChannelState(it.importance == NotificationManager.IMPORTANCE_NONE, it.group) + val channels = manager.notificationChannels.associate { + it.id to ReminderChannelState(it.importance == NotificationManager.IMPORTANCE_NONE, it.group) + } + val blockedGroups = manager.notificationChannelGroups.filter { it.isBlocked }.mapTo(HashSet()) { it.id } + return ReminderChannelStatus( + appBlocked = !appEnabled, + blockedCalendarIds = blockedReminderCalendars(calendarIds, appEnabled, channels, blockedGroups), + groupBlockedCalendarIds = calendarIds.filterTo(HashSet()) { + channels[reminderChannelId(it)]?.groupId in blockedGroups }, - blockedGroupIds = manager.notificationChannelGroups.filter { it.isBlocked }.mapTo(HashSet()) { it.id }, ) - return ReminderChannelStatus(appBlocked = !appEnabled, blockedCalendarIds = blocked) } private fun plan( @@ -141,8 +143,7 @@ class ReminderChannels @Inject constructor( if (channels.isEmpty()) return val byId = existing.associateBy { it.id } val template = byId[LEGACY_REMINDER_CHANNEL_ID] - // Only a channel's first creation sets its behaviour; later calls just - // rename it, so an existing one passes its own importance back. + // Importance only applies on creation; an existing channel keeps its own. manager.createNotificationChannels( channels.map { spec -> val current = byId[spec.id] @@ -210,4 +211,6 @@ class ReminderChannels @Inject constructor( data class ReminderChannelStatus( val appBlocked: Boolean = false, val blockedCalendarIds: Set = emptySet(), + /** The blocked ones whose whole account group is off, which only the app's own screen can undo. */ + val groupBlockedCalendarIds: Set = emptySet(), ) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/NotificationSettings.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/NotificationSettings.kt index 85d5b064..46b90ee7 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/NotificationSettings.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/NotificationSettings.kt @@ -104,7 +104,6 @@ internal fun NotificationsScreen( val scope = rememberCoroutineScope() val channelStatus by viewModel.channelStatus.collectAsStateWithLifecycle() val calendarIds = state.reminderCalendars.map { it.id } - // Re-read whenever the screen returns from the system settings it links to. LifecycleResumeEffect(calendarIds) { viewModel.refreshChannelStatus(calendarIds) onPauseOrDispose { } @@ -112,8 +111,8 @@ internal fun NotificationsScreen( val openCalendarSound: (Long) -> Unit = { calendarId -> scope.launch { val channelId = viewModel.reminderChannelFor(calendarId) - // With the whole app blocked, only the app's own screen can allow it. - context.openNotificationSettings(channelId.takeUnless { channelStatus.appBlocked }) + val appScreen = channelStatus.appBlocked || calendarId in channelStatus.groupBlockedCalendarIds + context.openNotificationSettings(channelId.takeUnless { appScreen }) } } @@ -126,6 +125,7 @@ internal fun NotificationsScreen( if (IS_SAMSUNG && !state.notificationCategoryHintDismissed) { NotificationCategoryHint( onOpen = { + viewModel.dismissNotificationCategoryHint() scope.launch { viewModel.syncReminderChannels() context.openNotificationSettings() @@ -207,9 +207,7 @@ internal fun NotificationsScreen( onClick = { showSnooze = true }, ) - // Per-calendar settings, folded behind one header: every switched-on - // calendar gets its notification switch and sound (#145, #272); the ones - // you can edit also keep, drop or replace the default reminders. + // Per-calendar notifications and reminder overrides (#145, #272). if (state.reminderCalendars.isNotEmpty()) { Spacer(Modifier.height(24.dp)) GroupedRow( diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt index ce2e2adf..8b287aff 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt @@ -50,6 +50,7 @@ import de.jeanlucmakiola.calendula.ui.theme.AppFontSettings import de.jeanlucmakiola.calendula.ui.theme.FONT_CUSTOM_TOKEN import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -377,8 +378,11 @@ class SettingsViewModel @Inject constructor( /** What the system blocks of the reminders; re-read with [refreshChannelStatus] (#145). */ val channelStatus: StateFlow = _channelStatus.asStateFlow() + private var channelStatusJob: Job? = null + fun refreshChannelStatus(calendarIds: Collection) { - viewModelScope.launch { + channelStatusJob?.cancel() + channelStatusJob = viewModelScope.launch { onChannels { status(calendarIds) }?.let { _channelStatus.value = it } } } diff --git a/floret-kit b/floret-kit index 730c9eb2..20b136aa 160000 --- a/floret-kit +++ b/floret-kit @@ -1 +1 @@ -Subproject commit 730c9eb25a6bf2f93e3388bf132769d6dd1afd91 +Subproject commit 20b136aaf71277643267018ed1c57dae0b0c54ec