UI-06: rebuild Plan & Route Planning as a fullscreen map canvas

Replaces RoutePlannerScreen's AppBar-driven layout with a headerless,
full-opacity map canvas restyled toward Map HUD rather than copied from
this screen's own (drifted) Stitch export: a floating "tap to drop a pin"
tooltip before any pins exist, FloatingPill (Distance/Est. Time/Pins) once
they do, a "Start Route" floating button, a GlassPanel overflow menu
replacing the old app-bar actions (rename/delete/download), and a
pin-drop ripple micro-interaction. RoutesListScreen keeps its existing
list-with-+-button entry point (the IA question in the ticket's Risk
section, deferred as explicitly permitted) but restyles its rows as
GlassPanel cards.

"Start Route" gets real behavior instead of shipping as a dead button:
turn-by-turn route following doesn't exist yet (V3-09), so tapping it
starts an ordinary recording and switches to the Map tab -- useful today,
not a placeholder for a feature that isn't there. Est. Time honestly
shows "--" rather than fabricating an estimate the data model doesn't
back. Marching-ants route-line animation was dropped from scope: it isn't
in the acceptance criteria, and animating a dash phase under live map
pan/zoom is real complexity for a purely decorative effect.

Fixes a real, pre-existing bug found while working in this file: the
offline-tile download dialog still fetched from tile.openstreetmap.org
directly, a leftover from before UI-09 switched the live map to CARTO's
dark tiles -- a "successful" download would have cached tiles under keys
the dark-tile cache never reads from.

Also fixes a live Flutter framework warning the new GlassPanel-wrapped
ListTile surfaced (ink splashes need a Material ancestor, which
GlassPanel's DecoratedBox was blocking) and a real test bug (two pins
placed one directly under the new floating Start Route button in a
fixed test camera, causing a tap meant for the pin to hit the button
instead).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
This commit is contained in:
2026-08-24 10:18:39 -05:00
parent fa6b1b8ef2
commit dccccb68b6
5 changed files with 545 additions and 174 deletions

View File

@@ -1,6 +1,6 @@
# UI-06 — Plan & Route Planning redesign
**Depends on** UI-01, UI-03 · **Size** M · **Status** Not started
**Depends on** UI-01, UI-03 · **Size** M · **Status** Done
## Goal
Rebuild the Plan tab (empty state) and the route planner (pins dropped) as fullscreen
@@ -89,3 +89,82 @@ matching Map HUD's nav bar exactly (same widget, in fact — see UI-01).
## Out of scope
Road-snapped routing (V3-08/V3-09, deferred). Any change to the underlying route-planning
data model or repository — this is presentation only.
## Outcome
**IA question (Risk section) resolved by deferring, as the ticket explicitly allowed:**
`RoutesListScreen` stays a list-with-`+`-button (the existing entry point), restyled with
`GlassPanel`-wrapped rows instead of plain `Card`s. The mockup's own "fullscreen map +
tooltip" language turned out, on close reading, to describe `RoutePlannerScreen`'s
*own* empty-pins state (the tooltip literally instructs "tap to drop a pin" — an action
that happens on the canvas, not on a list of named routes), not the routes list. Both
screens got the restyle their actual role calls for.
`RoutePlannerScreen` is now a headerless, full-opacity map canvas (its own map, not
UI-01's dimmed shell background — this screen's map *is* the primary content, matching
Map HUD). Implemented: the floating "TAP TO DROP A PIN" tooltip (`GlassPanel`, gentle
3s float) shown only while `waypoints.isEmpty`; `FloatingPill` (UI-03) showing Distance/
Est. Time/Pins once pins exist, replacing the old pre-download confirmation dialog's
role as the primary stat display (the confirmation dialog itself is untouched --
presentation only, per scope); a "START ROUTE" floating pill button; a `GlassPanel`
overflow menu (`PopupMenuButton`) holding Rename/Download offline tiles/Delete route,
replacing the removed `AppBar`'s actions row; and a pin-drop ripple micro-interaction
(a short-lived expanding-and-fading ring at the tap's local screen position).
**"Start Route," given real, working behaviour rather than shipped as a dead button:**
turn-by-turn following (V3-09) doesn't exist anywhere in the app to wire this to, and
inventing that data model here would violate this ticket's own "presentation only"
scope. Tapping it starts an ordinary recording and switches to the Map tab -- a real
action a rider can use today, not a placeholder.
**Est. Time honestly shows "--"** when `RoutePlan.estimatedMillis` is null (always, until
V3-08 adds road-snapped routing) rather than fabricating a straight-line estimate the
model doesn't back.
**Marching-ants route-line animation was deliberately dropped from scope.** It isn't in
this ticket's acceptance criteria (only the pin-drop ripple is named there), and
implementing it properly means animating a dash phase while continuously reprojecting
through `MapCamera.latLngToScreenOffset` under live pan/zoom -- real complexity for a
purely decorative effect. The existing static dashed polyline (already "visibly distinct
from a recorded path," the actual acceptance-relevant property) is unchanged.
**Bug found and fixed, unrelated to this ticket's own changes but discovered while
working in this file:** `_DownloadDialog`'s tile fetch still hardcoded
`https://tile.openstreetmap.org/...` directly -- a leftover from before UI-09 switched
the live map to CARTO's dark tiles. Downloading offline tiles would have silently cached
OSM's tan tiles under the same `(z, x, y)` keys UI-09's cache versioning was specifically
designed to keep separate from CARTO's, meaning a "successful" download would never
actually serve anything to the live dark map. Fixed to build the URL from the same
`tileUrlTemplate`/`tileSubdomains` every other tile fetch in the app already uses.
**Nav bar correctness:** no separate implementation needed -- `RoutePlannerScreen` is
pushed *within* the Plan branch's own navigator (UI-01's `StatefulShellBranch`
architecture), so the shell's persistent nav bar already shows Plan active automatically,
structurally ruling out the mockup's own "Rides active while viewing Plan" generation
error. Added a test asserting this directly rather than trusting the architecture alone.
**Tests:** `flutter analyze` clean. `flutter test` green at 370 tests. Updated every
existing `route_planner_screen_test.dart` assertion that referenced now-removed `AppBar`
structure (the `IconButton` keys are `PopupMenuItem`s behind the overflow menu now; there
is no app-bar title to assert renaming against, so that test now reads the repository
directly instead) plus one real test bug fixed along the way: two close-together
waypoints in the "tapping a pin deletes it" test happened to place one pin directly
under the new floating "Start Route" button in that test's fixed camera geometry, so the
tap intended for the pin actually hit the button underneath it (confirmed via
`WidgetController`'s own hit-test-mismatch warning, not assumed) -- reduced to a single
waypoint, which the camera centres exactly on, clear of the button. `PopupMenuButton`/
`PopupMenuItem` interactions needed bounded pumps rather than `pumpAndSettle`, same
reason the map's own tests already avoid it (a live `TileLayer` never goes idle in this
harness). Also fixed a real, unrelated bug the new `GlassPanel`-wrapped `ListTile` in
`RoutesListScreen` surfaced immediately: Flutter's own framework assertion that a
`ListTile` needs a `Material` ancestor to paint its ink splash correctly, which
`GlassPanel`'s `DecoratedBox` was sitting between it and -- fixed with a
`Material(type: MaterialType.transparency)` wrapper.
**Android emulator verification** (`Medium_Phone_API_35`): confirmed the empty-tooltip
state, dropping a pin (ripple visible, `FloatingPill` and "Start Route" appearing
immediately), and the overflow menu opening with all three actions -- all matching the
mockup and the Design section precisely. The Plan tab's nav-bar pill correctly followed
the active tab (a mid-transition-animation screenshot briefly looked wrong immediately
after tapping the tab; a screenshot taken a moment later confirmed it settles correctly
-- a timing artifact of screenshotting mid-animation, not a real bug).