docs: mark M6 done
This commit is contained in:
+85
-9
@@ -10,14 +10,15 @@ Status legend: ✅ done · 🚧 in progress · ⬜ not started
|
||||
|
||||
## Current state (one line)
|
||||
|
||||
✅ **M5 done.** The Alarms tab is real: a live list that says when each alarm
|
||||
will ring and marks the next one, and an editor that writes as you go — the
|
||||
Material time picker, a repeat-day row that starts on your locale's own first
|
||||
day, a ringtone picker with the device's sounds, a file of your own and a Silent
|
||||
option that still vibrates, and five per-alarm overrides each of which can say
|
||||
"follow the app default" rather than silently copying it. The other three tabs
|
||||
are still empty states: **M6** (timers — multiple concurrent, presets, the
|
||||
foreground service and expiry ringing) is next.
|
||||
✅ **M6 done.** The Timers tab is real: several timers at once, a keypad and
|
||||
saved presets that set one in a couple of taps, pause/reset/"+1 min" from the
|
||||
row, the live pill *and* a notification whose countdown the system draws, a
|
||||
foreground service alive exactly while something is active, and expiry that
|
||||
rings through M3's audio path — waiting its turn when an alarm is already
|
||||
ringing rather than talking over it. Every countdown is anchored on the device's
|
||||
uptime, so moving the system clock cannot move a timer in either direction. Two
|
||||
tabs are still empty states: **M7** (stopwatch — start/stop/reset, laps with
|
||||
splits, and the app-wide big-readout typography settled at last) is next.
|
||||
|
||||
---
|
||||
|
||||
@@ -248,11 +249,86 @@ line ("in 9h 12m") on each row. Built on `GroupedSurface`/`GroupedRow`.
|
||||
settings screen. The seven new instrumentation tests compile in the gate but
|
||||
have **never run on hardware**, as no device was attached.
|
||||
|
||||
### ⬜ M6 — Timers
|
||||
### ✅ M6 — Timers
|
||||
Multiple concurrent timers, presets, labels, add/pause/reset/+1min. Foreground
|
||||
service with notification controls, expiry ringing reusing M3's audio path.
|
||||
Elapsed-realtime anchored.
|
||||
|
||||
✅ 843 JVM tests, 219 of them new, and **still no Robolectric** — kept for a
|
||||
second screen *and* for a foreground service, which is the other place a
|
||||
project usually gives in. Every decision is a pure object (`TimerReadings`,
|
||||
`TimerExpiry`, `TimerRingPolicy`, `TimerNotificationPolicy`, `TimerPresets`,
|
||||
`TimerDurationEntry`, `TimerListRows`, `TimerRoutes`) or a method on the
|
||||
engine or one of the two ViewModels, tested over the **real**
|
||||
`TimerRepositoryImpl` and the **real** `TimerEngine`. `TimerService` holds no
|
||||
policy and no state beyond one flag — even its two `delay` amounts come from
|
||||
the engine, so the timings are asserted in a JVM test. Three new
|
||||
`ArchitectureRulesTest` rules pin it, one of which makes a *second*
|
||||
`MediaPlayer` a build failure. The four new instrumentation tests bring the
|
||||
compiled total to 32.
|
||||
|
||||
The decisions worth knowing: the expiry slot is **one** registration on the
|
||||
`ELAPSED_REALTIME_WAKEUP` base — so a `TIME_SET` cannot warp a running timer,
|
||||
and a timer never populates `getNextAlarmClock()`, which belongs to the user's
|
||||
next *alarm* — and its fire is an **idempotent sweep carrying no id**, so a
|
||||
late delivery expires everything due, an early one writes nothing, and the
|
||||
anchors *are* the watermark; the service adds a second, independent trigger
|
||||
for the same sweep, because neither has to be reliable alone; one
|
||||
`systemExempted` foreground service covers the countdown and the ring, alive
|
||||
on the live pill's own "active, not running" predicate, and the countdown
|
||||
holds **no wake lock** (the alarm slot is what wakes the device); an alarm
|
||||
ringing **wins the audio** and the timer's ring is *deferred, not lost*,
|
||||
because the arbitration is a pure function of current state rather than an
|
||||
event — so the reverse case is symmetric and free; two timers expiring
|
||||
together share one ring session and one ten-minute window, and each is stopped
|
||||
on its own, because silently resetting both destroys the information "which
|
||||
one finished"; auto-silence stops the noise and leaves the row EXPIRED, which
|
||||
is the load-bearing difference from an alarm; presets are app-wide in
|
||||
DataStore with out-of-range values **dropped, never clamped**; and the
|
||||
notification's countdown is the platform chronometer, so the service posts
|
||||
once per state change rather than once per second for forty-five minutes.
|
||||
|
||||
Two things the checklist did not name were changed anyway, and are recorded in
|
||||
`ARCHITECTURE.md`: **"+1 min" on an expired timer now resumes it** with
|
||||
exactly the extra, in one transaction (M2 left it paused, written before any
|
||||
UI existed and pinned by no test — and resuming is the only way to avoid
|
||||
emitting an intermediate PAUSED frame to the pill and the row); and **the live
|
||||
pill's timer actions now go through `TimerEngine`**, because after M6 there is
|
||||
an AlarmManager registration and a service that have to move with the state.
|
||||
The pill's contract is otherwise unchanged: M6 *extracted* its precedence into
|
||||
`TimerReadings` so the pill, the notification and the ring share one ordering,
|
||||
and `LivePillSelectorTest` passing **unmodified** is the proof.
|
||||
|
||||
No schema change (the database stays at v2; the ring session's one volatile
|
||||
fact is a DataStore record, which is `PLAN.md` §5's own record-versus-rows
|
||||
split and keeps it out of M10's backup), **no new permission** (a timer never
|
||||
takes over the screen — no full-screen intent, no ring activity), and **no
|
||||
floret-kit change**: the kit earned `CollapsingScaffold`'s
|
||||
`floatingActionButton` slot at 0.6.0 for exactly this shape of tab, and M6
|
||||
consumes it.
|
||||
|
||||
Knowingly open: no hand reordering of timers (`sort_order` and
|
||||
`TimerRepository.reorder` stay caller-less, as they do for alarms), no
|
||||
per-timer vibrate, volume-ramp or dismiss-challenge overrides (`timers` has
|
||||
one nullable settings column and four more would be a schema change), no
|
||||
"stop all" when several timers are expired, no undo after a delete (traded for
|
||||
a confirmation, as with alarms), no ramp on a timer's ring, no presets
|
||||
management screen and no editing of `default_timer_duration_millis` or
|
||||
`default_timer_ringtone_uri` — M10's settings screen. `TimerSnapshot.anchorIsStale`
|
||||
is still not surfaced in the UI. Of `AlarmClock`'s intent contract only the
|
||||
in-process hook `ShellNavigation.tabForAction` exists, wired to one action;
|
||||
M9 owns the rest. The four new instrumentation tests compile in the gate but
|
||||
have **never run on hardware**, as no device was attached — the same caveat
|
||||
M5's seven carry.
|
||||
|
||||
Two cases in the new suite are left **red** and are a design conversation
|
||||
rather than a behaviour gap; both are defects in the test, not in the code:
|
||||
`TimerIntentsTest` §5.17 #3 asserts `containsNoDuplicates` over a list that
|
||||
contains the *same* `requestCodeFor(REQUEST_PAUSE, 1L)` call twice by
|
||||
construction, which no deterministic function can satisfy; and `TimerPrefsTest`
|
||||
§5.10 #11 opens a second DataStore over a live file in the same scope, which
|
||||
DataStore refuses by design — as `SettingsPrefsTest` itself documents.
|
||||
|
||||
### ⬜ M7 — Stopwatch
|
||||
Start/stop/reset, laps with splits and cumulative times, best/worst lap emphasis.
|
||||
Foreground service so it survives backgrounding; laps persist across process
|
||||
|
||||
Reference in New Issue
Block a user