mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
The My Fitness dashboard sat on a spinner for 2-3s on a phone with a watch connected. Three things compounded: - `readWorkouts` made one `aggregate()` IPC per session, in series. The dashboard's window is 28 days (`WorkoutStats.WINDOW_DAYS`) against the New Workout carousel's 7, so a daily trainer paid 30-60 sequential round trips. - `refresh()` launched on `viewModelScope` (`Dispatchers.Main.immediate`) and `healthConnectStatus()` never left it, so the PackageManager query and the lazy Health Connect service bind ran on the UI thread. `readWorkouts` already hopped to IO with a comment about exactly this hazard; the permission probe in front of it did not. - The screen stayed on `State.Loading` until `healthConnectStatus` went non-null, which happened only after the whole read finished — so the user's published kind 1301 workouts, already indexed in `LocalCache`, waited behind Health Connect for data the screen's own KDoc calls "not a precondition". Fixed all three, and split the read into two stages. The session list is one IPC and already carries activity, title, start, duration and source — enough for the counts, total time, streak, active days, per-activity split, recent list and the longest-duration best. The metrics that need a per-session aggregate (distance, calories, heart rate, steps, elevation) arrive second, now fanned out under a permit cap rather than run one at a time. That partial pass is honest rather than approximate: `WorkoutStats.total` and `bests` already treat an absent metric as contributing nothing instead of zero, and the screen already gates each metric cell on `> 0` / non-null, so an unknown metric reads as a hidden cell, never as "0 km". `State.Ready` carries `metricsPending` so the dashboard says what is still filling in. Aggregation stays per raw session with merging after it, exactly as before: `WorkoutMerger` sums its members' metrics, so aggregating a merged span would fold in the breaks between segments and change the totals. Two flicker guards: the status is published before the read starts (it is cheap, and gating on it is what lets the published log render immediately), but an *empty* report keeps waiting while the session list is in flight, so a user whose workouts are one IPC away never sees the empty state or the connect prompt flash. A resume re-reads in place rather than blanking a dashboard that is already correct, and each refresh cancels the previous one so two reads cannot interleave. Tests: 6 new in `PartialMetricsReportTest` pinning that the first pass is final for everything not metric-derived and absent (not zero) for everything that is; 2 in `WorkoutMergerTest` for skeleton/loaded grouping parity and for why per-session aggregation has to precede the merge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VgfgpDWnu635p2qc2K2yMX