fix(timers): settle timer vibration outside the store's init and resume a ramp

The DataStore migration ran its write inside the store's initialisation, so a failed write (a full disk) would have failed every preference read, the alarm path's included. The timer-vibrate key is now nullable instead: unsettled reads as the alarm default, which is what timers did before, and ClockulaApp settles it once at the head of its startup pass, off only for a never-updated package with an empty store. A failed settle leaves the fallback in place.

A timer ring restarted mid-session (handed to a second expired timer, back after an alarm held it, a recreated service) now picks its ramp up from the session's elapsed time instead of either ramping again from a whisper or jumping straight to full volume. RingAudioPlayer takes the offset; alarms pass none.

The Vibrate row's switch is display-only, so a screen reader stops on the row once.

Refs #22
This commit is contained in:
makiolaj committed 2026-10-21 19:06:50 +02:00
1 parent 8910ab7fbf
commit 84d31fe7f8
16 files changed
+223 -236

No files matched your search

+14 -6
View File
@@ -1910,12 +1910,20 @@ Vibration is `ClockDefaults.timerVibrate`, never the alarms' `vibrate`: an
alarm that should buzz the bed and a kitchen timer that should only beep are
different questions. A fresh install has it **off**. An install from before
the setting existed had its timers vibrate exactly when the alarm default did,
so `TimerVibrateMigration` (a DataStore migration, run before the first read)
writes that value once rather than silently changing what its timers do. Only
a package never updated since install *and* an empty store count as fresh;
anything else (an update, an Auto Backup restore, a store replaced after
corruption) keeps the old behaviour. A ring that hands over from one expired
timer to another mid-ring does not start the ramp again. Silent with vibration off is still not silence:
and keeps that. The key is nullable: absent reads as the alarm default, and
`SettingsPrefs.settleTimerVibrate` writes a value of its own once, at the head
of `ClockulaApp`'s startup pass (before the boot gate writes the store). Only a
package never updated since install *and* an empty store count as fresh and
get `false`; anything else (an update, an Auto Backup restore, a store replaced
after corruption) gets the alarm default copied. It is deliberately not a
DataStore migration: a migration's write runs inside the store's
initialisation, and a failed one would fail every read, the alarm path's
included. A failed settle only leaves the fallback in place.
A ring restarted mid-session (handed to a second expired timer, back after an
alarm held it, a recreated service) picks its ramp up where the session is,
`TimerRing.rampElapsed` from the window's remaining time, rather than ramping
again from a whisper. Silent with vibration off is still not silence:
`AudioSourcePolicy` gives Silent no sources, and the player's `onSilenced`
starts the vibrator regardless of the setting.