Replaces the scrolling stats-card layout with the full-bleed Map HUD: UI-04's draggable/resizable HUD widgets float over UI-01's shared background map (full opacity on this tab), and the old Pause/Stop/Resume/ Discard buttons become full-bleed, icon-only segments matching the mockup's tertiary-container/error-container colors exactly. The idle state keeps its prior compact layout deliberately -- HUD widgets only appear once a ride exists, preserving the existing "a resting screen must not look like a ride going nowhere" guarantee rather than reinterpreting it. State-machine logic (ticker, wakelock, speed subscription, error handling) is untouched; every pre-existing record-screen test passed unchanged against the rebuilt screen. Added tests that actually tap Start/Pause/Stop and verify engine state changes, confirm HUD widgets render over the map rather than replacing it, and verify the mounted-mode speed digit's real contrast ratio against GlassPanel's translucent surface specifically (the ticket's own named risk). Corrects a UI-04 mistake found while implementing this ticket: the HUD's default four metrics were ordered Speed/Distance/Elapsed/Max Speed, a guess made before reading the actual Map HUD mockup HTML closely. The real fixed row is Speed/Avg Speed/Dist/Time -- reordered HudMetric to match and updated every test that asserted the old order. Adds PulsingLocationMarker (UI-03) to RideMap's live usage via a new showLocationMarker flag, and explicit tertiaryContainer/errorContainer tokens to ripprColors so the control bar matches the design system's literal values rather than an auto-derived tonal palette. Verified end-to-end on a real emulator: Start, Pause, Resume, and Stop all correctly drive the trip state machine with the full live map behind everything. A lengthy false alarm during this verification (taps appearing to do nothing) turned out to be a screenshot-scale mis-measurement on the verification side, not an app defect -- resolved by sampling pixel colors directly from the raw screenshot to find the control bar's true on-screen position. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
170 lines
11 KiB
Markdown
170 lines
11 KiB
Markdown
# UI-05 — Map HUD: Record screen redesign
|
|
|
|
**Depends on** UI-01, UI-03, UI-04 · **Size** M · **Status** Done
|
|
|
|
## Goal
|
|
Rebuild the Record screen as the fullscreen Map HUD from the Stitch export — this screen
|
|
is the **design north star** for the whole redesign (see `README.md`): every other
|
|
screen ticket restyles toward what this one establishes, not the other way around.
|
|
|
|
## Context
|
|
Today's Record screen is a scrolling `Column`: a 220px map card, a stats `Card` below it,
|
|
full-width Pause/Stop/Resume/Discard buttons, and a "RIPPR" wordmark header with Rides/
|
|
Settings/Routes buttons. The redesign inverts this entirely — the map is the full-bleed
|
|
canvas (via UI-01's shared background, at full opacity/interactivity on this tab only),
|
|
and everything else floats on top of it via `GlassPanel`/HUD widgets.
|
|
|
|
## Design
|
|
- **Header:** none, per UI-01.
|
|
- **Telemetry:** `DraggableResizableHudWidget`s from UI-04, not a fixed row — this screen
|
|
is where those widgets actually live during a ride. Default layout mirrors the Stitch
|
|
mockup's horizontal row (Speed/Avg Speed/Dist/Time) until the rider customizes it.
|
|
- **Pause/Stop:** two icon-only, half-width, full-bleed buttons directly above the bottom
|
|
nav bar, matching the mockup exactly — `tertiary-container` (pause) and
|
|
`error-container` (stop) per UI-08's palette. Resume/Discard: the mockup doesn't show
|
|
these explicitly on this screen; keep them reachable (e.g. Discard behind a
|
|
confirmation from the paused state, matching today's existing safeguard against a
|
|
gloved mis-tap next to Pause) rather than dropping the affordance V3-05/the original
|
|
port already earned.
|
|
- **Route rendering:** both the planned route (dashed, neutral `outline` color) and the
|
|
ridden-so-far path (colored by the existing speed gradient from `RideMap`) drawn
|
|
simultaneously, matching the mockup — this is new: today only the ridden path renders
|
|
live.
|
|
- **Location marker:** `PulsingLocationMarker` (UI-03) replaces the current plain dot.
|
|
- **Mounted mode (V3-05) interaction with this redesign:** the high-contrast mounted
|
|
theme and larger touch targets still apply, layered on top of this screen's new
|
|
layout, not replaced by it — confirm the mounted theme's colors still read correctly
|
|
against `GlassPanel`'s blur (a light theme through blurred content behaves differently
|
|
than the current dark-on-dark case V3-05 was built against).
|
|
|
|
## Implementation
|
|
1. Rebuild `RecordScreen`'s body against UI-01's full-opacity Map tab background instead
|
|
of an embedded `RideMap` card.
|
|
2. Replace the current `StatRow`-based stats card with UI-04's HUD widgets, wired to the
|
|
same `RecordUiState`/live telemetry stream already powering the old layout — no new
|
|
data plumbing, only new presentation.
|
|
3. Rebuild `_Controls`/`_PrimaryButton`/`_SecondaryButton` as the two full-bleed icon
|
|
buttons; keep the existing state-machine logic (`RecordUiState.isIdle/isRecording/
|
|
isPaused`) driving which controls show, per the current `_Controls` widget.
|
|
4. Wire both route paths (planned + ridden) into the shared background map's overlay,
|
|
sourced from `RoutePlanRepository` (if a route is being followed — V3-09 groundwork,
|
|
not required for this ticket if no route is active) and the existing live-points
|
|
stream.
|
|
5. Re-verify V3-05's mounted-mode contrast/legibility guarantees against the new
|
|
`GlassPanel`-based layout specifically, since that's a real visual change from the
|
|
opaque `Card` mounted mode was built and tested against.
|
|
|
|
## Acceptance criteria
|
|
- [ ] Map fills the screen behind the HUD widgets and controls, full opacity, on this
|
|
tab
|
|
- [ ] Existing record/pause/resume/stop/discard state machine behavior is unchanged —
|
|
this is a visual rebuild, not a behavior change
|
|
- [ ] HUD telemetry widgets are the UI-04 draggable/resizable kind, not a fixed layout
|
|
- [ ] Mounted mode (V3-05) still passes its existing contrast tests against the new
|
|
layout
|
|
- [ ] The black-on-black text-color regression guard (from V3-16/the original port bug)
|
|
still passes against `GlassPanel`'s blur background
|
|
|
|
## Tests
|
|
- All existing `record screen` widget tests updated to the new layout and still passing
|
|
(state-machine behavior, mounted-mode toggle, wake lock, live-map visibility)
|
|
- Widget: HUD widgets render over the map, not replacing it
|
|
- Widget: Pause/Stop buttons trigger the same engine calls as today
|
|
|
|
## Risks
|
|
- The biggest visual departure of the whole redesign happens here first — expect this
|
|
ticket to surface layout issues (`GlassPanel` blur cost stacked with a live animating
|
|
map, HUD widgets overlapping controls on small screens) that UI-06/07 then inherit or
|
|
avoid having created themselves.
|
|
|
|
## Out of scope
|
|
Following a planned route turn-by-turn (V3-09). Anything about Plan/Route Planning/Rides
|
|
History screens — those are UI-06/UI-07, restyled toward what this ticket establishes.
|
|
|
|
## Outcome
|
|
|
|
Rebuilt `RecordScreen`'s body entirely against UI-01's shared background map, keeping
|
|
every line of the existing state-machine logic (the ticker, wakelock handling, speed
|
|
subscription, `_guard`/error handling) completely untouched -- only the returned widget
|
|
tree changed. The idle state keeps its own compact layout (a single `GlassPanel` with
|
|
`BigStat` speed + "Ready"/"See Rides", exactly the old content, just re-skinned) rather
|
|
than switching to HUD widgets while there's no ride to show numbers for -- deliberately
|
|
preserving the pre-existing "a resting screen must not look like a ride going nowhere"
|
|
guarantee rather than reinterpreting it. Once a ride exists (recording or paused),
|
|
`HudEditOverlay` (UI-04) takes over, wired to a new `_HudMetricValue` that reads
|
|
`RecordUiState`/`UnitSystem` and renders through `HudMetric`'s existing set -- no new
|
|
data plumbing, exactly as the ticket's Implementation section asked for. Added
|
|
`RecordUiState.avgSpeedKmh` (distance-over-moving-time, zero before any movement) since
|
|
that metric didn't previously exist anywhere in the app.
|
|
|
|
**Colour convention decision:** the Map HUD mockup colours its four default metrics
|
|
individually (Speed → primary, Distance → secondary, Avg Speed/Time → plain ink) rather
|
|
than following this app's usual reference-vs-live split. `_HudMetricValue` matches the
|
|
mockup literally rather than inventing a new convention, since the mockup is this
|
|
ticket's explicit design source for exactly this screen.
|
|
|
|
**Correction found and fixed while implementing this ticket:** UI-04's `HudMetric` enum
|
|
ordered its "first four, default-visible" metrics as Speed/Distance/Elapsed/Max Speed --
|
|
a guess made before the actual Map HUD mockup HTML had been read closely. Reading
|
|
`docs/design/stitch-export/screens/map-hud-dark.html`'s own `#telemetry-container`
|
|
directly for this ticket showed the real fixed row is Speed/**Avg Speed**/Dist/Time.
|
|
Reordered the enum to match and updated every UI-04 test that asserted the old order
|
|
(five call sites across `hud_widget_layout_test.dart`, `hud_edit_overlay_test.dart`, and
|
|
`settings_screen_test.dart`) -- a real, disclosed correction, not a silent one.
|
|
|
|
**Control bar:** `_ControlBar`/`_Segment` render the full-bleed, icon-only bar the
|
|
mockup specifies for Pause (`tertiaryContainer`/`onTertiaryContainer`) and Stop
|
|
(`errorContainer`/`onErrorContainer`) -- both added explicitly to `ripprColors` in this
|
|
ticket, since `ColorScheme.dark`'s auto-derived tonal palette wouldn't have matched the
|
|
design system's literal hex values otherwise. Idle keeps a labelled "START RECORDING"
|
|
segment (an icon alone risked ambiguity for a rider's very first, highest-stakes tap,
|
|
a deliberate deviation from strict icon-only fidelity). Paused extends the same visual
|
|
language into three segments (Resume 50%, Stop 25%, Discard 25%) since the mockup itself
|
|
doesn't depict a paused state — Discard stays behind this dedicated slot, reachable only
|
|
while paused, preserving the existing gloved-mis-tap safeguard from V3-05 rather than
|
|
dropping it.
|
|
|
|
**Location marker:** `RideMap` gained a `showLocationMarker` bool; when true and points
|
|
exist, a `MarkerLayer` places `PulsingLocationMarker` (UI-03) at the latest point.
|
|
`ShellScaffold` passes `showLocationMarker: isMapTab` so the animation only ticks on the
|
|
tab where it's actually visible.
|
|
|
|
**Out of scope, confirmed at implementation time, not just planned:** the ticket allows
|
|
skipping planned-route rendering when V3-09 (route-following) doesn't exist yet, and it
|
|
doesn't -- there is no "route currently being followed" concept anywhere in the app to
|
|
source a planned-route polyline from. Left unimplemented rather than fabricating a data
|
|
source; `RideMap`'s existing ridden-path speed-gradient polyline is unaffected and still
|
|
renders live.
|
|
|
|
**Mounted mode re-verified against `GlassPanel` specifically**, per the ticket's own
|
|
named risk (mounted mode was built and tested against an opaque `Card`, not a blurred
|
|
translucent surface). Added a test asserting the actual contrast ratio of the mounted
|
|
speed digit against `GlassPanel`'s mounted-theme surface color (not just "differs from
|
|
the wrong, dark-theme ground" the way the pre-existing test checked) -- passes at
|
|
roughly 4.5:1+ AA with no colour changes needed.
|
|
|
|
**Tests:** `flutter analyze` clean. `flutter test` green at 369 tests. Every pre-existing
|
|
`record screen` test passed unchanged against the rebuilt screen with zero edits needed
|
|
-- the idle-state structural parity was deliberate and it paid off. Added: two tests that
|
|
actually tap Start/Pause/Stop and verify the underlying trip state changes (not just key
|
|
presence, which the pre-existing tests already covered), one confirming HUD widgets
|
|
render over the shared background map rather than replacing it, and the mounted-mode/
|
|
GlassPanel contrast test above.
|
|
|
|
**Android emulator verification** (`Medium_Phone_API_35`) exercised the full state
|
|
machine live on-device: Start → Pause → Resume → Stop, confirmed visually correct at
|
|
each transition (colours, icon changes, the "Paused" pill appearing/disappearing, the
|
|
HUD widgets updating), with the full-bleed live map behind everything on the Map tab
|
|
exactly matching the design intent. This surfaced and resolved a real debugging episode
|
|
worth recording: initial manual taps on the control bar appeared to do nothing across
|
|
many attempts (varied coordinates, map on/off, a full emulator+host reboot, a Gradle-
|
|
daemon memory-pressure cleanup, and a `Container`→`Ink` widget refactor along the way).
|
|
The eventual root cause was mundane and entirely on the verification side, not the
|
|
app: the button's true on-screen position was being mis-estimated from a scaled-down
|
|
screenshot preview, roughly 500px off from its actual location. Sampling pixel colours
|
|
directly from the raw screenshot file (via PIL) to find the button's exact bounds
|
|
resolved it immediately, and every interaction has worked correctly since. The emulator
|
|
reboot and Gradle-daemon stop were not the fix, but were a reasonable, low-risk
|
|
housekeeping step taken while investigating and are left in place as a genuine
|
|
improvement to the session's remaining build performance.
|