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)) + } }