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.
This commit is contained in:
makiolaj committed 2026-10-22 08:01:36 +02:00
1 parent 247dbcb751
commit 301ce2fec9
3 files changed
+25 -4

No files matched your search

@@ -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()
}
}
@@ -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(
@@ -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))
}
}