From 301ce2fec9a0f24f217763f78d3c0ef135a9521e Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Thu, 22 Oct 2026 08:01:36 +0200 Subject: [PATCH] fix(alarms): keep a one-shot on when it was given a date mid-snooze Dismissing a snoozed one-shot switched it off even when the user had just scheduled it for a later date from the editor, so the date never rang. Closing a cycle now leaves a one-shot on while its date is still ahead. --- .../jeanlucmakiola/clockula/alarm/AlarmEngine.kt | 6 +++++- .../clockula/ui/alarms/AlarmDatePickers.kt | 7 ++++--- .../clockula/alarm/AlarmEngineScheduleTest.kt | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/alarm/AlarmEngine.kt b/app/src/main/java/de/jeanlucmakiola/clockula/alarm/AlarmEngine.kt index a0fa5a8..da3b040 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/alarm/AlarmEngine.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/alarm/AlarmEngine.kt @@ -9,6 +9,7 @@ import de.jeanlucmakiola.clockula.domain.alarm.AlarmResolution import de.jeanlucmakiola.clockula.domain.alarm.AlarmResolver import de.jeanlucmakiola.clockula.domain.alarm.AlarmRing import de.jeanlucmakiola.clockula.domain.alarm.AlarmRingState +import de.jeanlucmakiola.clockula.domain.alarm.AlarmSchedule import de.jeanlucmakiola.clockula.domain.alarm.DismissOutcome import de.jeanlucmakiola.clockula.domain.alarm.FireOutcome import de.jeanlucmakiola.clockula.domain.alarm.FireSource @@ -379,7 +380,10 @@ class AlarmEngine @Inject constructor( if (closed != state) states.save(closed) val alarm = alarms.find(alarmId) - if (alarm != null && !alarm.repeatDays.isRepeating) disableOrDeleteLocked(alarm) + // A one-shot given a date while it was snoozed still has that date to + // ring on, so this cycle closing does not switch it off (#21). + val dateAhead = alarm != null && AlarmSchedule.upcomingDate(alarm, wallClock.now(), zones.current()) != null + if (alarm != null && !alarm.repeatDays.isRepeating && !dateAhead) disableOrDeleteLocked(alarm) rescheduleLocked() } } diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmDatePickers.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmDatePickers.kt index cd90c84..ba184af 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmDatePickers.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmDatePickers.kt @@ -101,8 +101,9 @@ internal fun ScheduleDatePickerDialog( /** * Picks the days a repeating alarm stays quiet on. A start with no end is a - * one-day pause. A pause already under way keeps its start, so reopening it - * to check or extend it does not rewrite the days already behind it. + * one-day pause. A pause already under way opens with its own start, so + * reopening it to check it and confirming does not rewrite the days already + * behind it. (Tapping a day starts a new range, as in any M3 range picker.) */ @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -114,7 +115,7 @@ internal fun PauseRangePickerDialog( onDismiss: () -> Unit, ) { val start = initial?.start - val end = initial?.end?.takeUnless { it.isBefore(today) } + val end = initial?.end val earliest = start?.let { minOf(it, today) } ?: today val selectable = remember(earliest) { PickerDates.from(earliest) } val state = rememberDateRangePickerState( diff --git a/app/src/test/java/de/jeanlucmakiola/clockula/alarm/AlarmEngineScheduleTest.kt b/app/src/test/java/de/jeanlucmakiola/clockula/alarm/AlarmEngineScheduleTest.kt index 4db1d81..030a2fe 100644 --- a/app/src/test/java/de/jeanlucmakiola/clockula/alarm/AlarmEngineScheduleTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/clockula/alarm/AlarmEngineScheduleTest.kt @@ -121,4 +121,20 @@ class AlarmEngineScheduleTest { assertThat(harness.alarmDao.stored.single().pauseStart).isNull() } + + @Test + fun `a one-shot given a date while snoozed stays on when the snooze is dismissed`(@TempDir tempDir: Path) = + runTest { + val harness = alarmEngineHarness(tempDir) + harness.givenAlarm(hour = 7) + harness.engine.reschedule() + harness.givenRinging(alarmId = 1L, at = offsetInstant("2026-06-10T07:00+02:00")) + harness.engine.snooze(1L) + harness.alarms.edit(1L) { it.copy(scheduledDate = june18) } + + harness.engine.dismiss(1L) + + assertThat(harness.alarms.find(1L)!!.enabled).isTrue() + assertThat(harness.scheduler.next).isEqualTo(Registration(offsetInstant("2026-06-18T07:00+02:00"), 1L)) + } }