diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmEditorViewModel.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmEditorViewModel.kt index e26c246..c49df9a 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmEditorViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmEditorViewModel.kt @@ -248,8 +248,6 @@ class AlarmEditorViewModel @Inject internal constructor( if (engine.ringSession()?.alarm?.id == alarmId) engine.dismiss(alarmId) alarms.delete(alarmId) engine.reschedule() - // "Set for 2 hours from now" must not outlive the alarm on the list. - announcer.retract(alarmId) _state.value = AlarmEditorUiState.Gone } } diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmSetSnackbar.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmSetSnackbar.kt index 26ef529..ac7cd17 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmSetSnackbar.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmSetSnackbar.kt @@ -99,14 +99,24 @@ internal class AlarmSetAnnouncer @Inject constructor( if (announcement == null || elapsed.elapsedRealtime() - announcement.at >= FRESH_FOR) { flowOf(null) } else { + // Worded once, from the first read: later writes — the next + // repeat-day tap, a label keystroke across a minute — must not + // restart a snackbar already showing. They can only take it + // down, when the alarm stops ringing at all. + var worded: AlarmSetNotice? = null engine.upcoming().map { upcoming -> val entry = upcoming.firstOrNull { it.alarm.id == announcement.alarmId } val countdown = AlarmSetCountdown.of(entry, wall.now()) - // Nothing to say is the answer for good: an announcement - // left standing would speak up later, long after the - // action, should something else switch the alarm back on. - if (countdown == null) dropIfCurrent(announcement) - countdown?.let { AlarmSetNotice(announcement.alarmId, it, announcement.sequence) } + if (countdown == null) { + // Nothing to say is the answer for good: left standing, + // it would speak up long after the action, should + // something else switch the alarm back on. + clear(announcement.sequence) + null + } else { + worded ?: AlarmSetNotice(announcement.alarmId, countdown, announcement.sequence) + .also { worded = it } + } } } } @@ -150,12 +160,10 @@ internal class AlarmSetAnnouncer @Inject constructor( } /** [notice] was shown until it timed out or was swiped away. */ - fun consumed(notice: AlarmSetNotice) { - current.update { standing -> standing?.takeUnless { it.sequence == notice.sequence } } - } + fun consumed(notice: AlarmSetNotice) = clear(notice.sequence) - private fun dropIfCurrent(announcement: Announcement) { - current.update { standing -> standing?.takeUnless { it.sequence == announcement.sequence } } + private fun clear(sequence: Long) { + current.update { standing -> standing?.takeUnless { it.sequence == sequence } } } private companion object { diff --git a/app/src/test/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmEditorViewModelTest.kt b/app/src/test/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmEditorViewModelTest.kt index 0ab4d95..9e36626 100644 --- a/app/src/test/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmEditorViewModelTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmEditorViewModelTest.kt @@ -343,6 +343,31 @@ class AlarmEditorViewModelTest { } } + @Test + fun `day taps while a notice shows do not restart it before they settle`(@TempDir tempDir: Path) = + runTest(main.dispatcher) { + val harness = alarmEngineHarness(tempDir, now = fiveToday) + harness.givenAlarmWith(hour = 7) + val viewModel = editor(harness) + advanceUntilIdle() + viewModel.onTimePicked(9, 0) + advanceUntilIdle() + + viewModel.alarmSetNotice.test { + assertThat(awaitItem()?.countdown).isEqualTo(NextFireLabel.InHoursMinutes(4, 0)) + viewModel.onDayToggled(DayOfWeek.MONDAY) + advanceTimeBy(300) + viewModel.onDayToggled(DayOfWeek.TUESDAY) + advanceTimeBy(300) + expectNoEvents() + advanceUntilIdle() + + // One more snackbar, once settled: Wednesday 05:00 to Monday 09:00. + assertThat(awaitItem()?.countdown).isEqualTo(NextFireLabel.InDaysHours(5, 4)) + expectNoEvents() + } + } + @Test fun `leaving while the days settle still announces them, for the list`(@TempDir tempDir: Path) = runTest(main.dispatcher) {