Refresh the Flutter snapshot: UI-01 through UI-09, plus a fresh installable APK

Full UI redesign pass complete: persistent tab shell with an always-visible
background map, Modern Professional Dark theme, monochrome dark map tiles,
offline skeleton map, a shared GlassPanel/FloatingPill component kit,
customizable HUD telemetry widgets, and the Map HUD / Plan & Route Planning /
Rides History screen rebuilds. 374 tests passing, up from 316.

The APK is a fresh release build (debug-signed, no release signing config
exists yet).

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 14:41:35 -05:00
parent 4c634354bd
commit 587f75eab4
42 changed files with 4401 additions and 89 deletions

View File

@@ -0,0 +1,148 @@
# UI-07 — Rides History redesign
**Depends on** UI-01, UI-03 · **Size** M · **Status** Done
## Goal
Rebuild the Trips list as "Rides History": a dimmed live map background (per UI-01, not
a static image), a search bar, and richer ride cards with a real route-thumbnail map —
restyled to Map HUD's (UI-05) established colors/icons, not the Stitch export's mockup
styling.
## Context
Today's `TripsScreen` is a plain `ListView` of `_TripTile`s (icon, name, one-line
subtitle) over a solid background, reached by pushing off Record. The mockup adds a
search bar, a filter button, a summary-stats row, and cards with an actual map-thumbnail
image per ride. By explicit instruction, the background here is the same live map every
other tab has (dimmed), not the mockup's static blurred screenshot — see UI-01.
**Generation artifact to fix, not copy:** the mockup's summary row is four identical
"Total Dist: 482 KM" cards. This ticket defines real, distinct summary stats.
## Design
- **Background:** UI-01's shared dimmed map, same as Settings and Plan get when not the
active-content tab.
- **Search + filter:** a text field over ride name/date, plus a filter affordance (by
activity type — V3-01 already has this data — and/or date range). Filtering logic is
new; nothing today searches or filters the trips list.
- **Summary row:** real distinct stats — total distance, total moving time, ride count,
and one more genuinely useful figure (e.g. average speed across all rides, or longest
ride) rather than repeating one number four times. Computed from existing `Trip`
aggregate columns already stored on every completed ride — no new data needed, only a
query across all of them.
- **Ride cards:** title, date, and a distance/time/avg-speed stat row (matching the
mockup), plus a **real map thumbnail** — a small, non-interactive `RideMap` (or a
lightweight static rendering of the stored path) rather than a stock image, since we
actually have the ride's own path data, unlike the mockup's placeholder photos.
- **Colors/icons:** Map HUD's palette and icon fill-states, per the design north star —
not the mockup's card-specific styling choices (e.g. the third card's grayscale/
reduced-opacity treatment, which reads as an arbitrary "older ride" decoration with no
defined rule — drop it unless a real rule for it is specified).
## Implementation
1. Rebuild `TripsScreen`'s background to sit over UI-01's dimmed shared map instead of a
plain scaffold background.
2. Add search (text query against `Trip.name`/date) and filter (activity type at
minimum, reusing V3-01's `Activity` enum and `activityIcon`/`activityLabel` helpers).
3. Add a summary-stats query/provider computing totals across `watchCompletedTrips()`
(or a dedicated aggregate query if computing it client-side over a large ride history
becomes a real cost — measure before optimizing).
4. Rebuild `_TripTile` as a card with a small live/static `RideMap` thumbnail (reusing
the existing `RideMap` widget at a small size and non-interactive, rather than
building a second map-rendering path) plus the distance/time/avg-speed row.
5. Confirm this screen's tab identity/name in the nav bar — "Rides" (mockup's label) vs.
"History" vs. keeping "Rides" as used elsewhere in the codebase already (V3-07's
Routes screen already uses "Rides" as the trips-tab label on the record screen) —
match whatever UI-01's nav bar ships with.
## Acceptance criteria
- [x] Background is the shared live map (dimmed), not a static image
- [x] Search narrows the list by name/date
- [x] Filter narrows the list by activity type
- [x] Summary row shows four distinct, real statistics, not one number repeated
- [x] Each ride card shows an actual thumbnail of that ride's own path, not a stock photo
- [x] Merge/delete/rename actions from today's `TripsScreen` still work
- [x] Colors and icon fill-states match Map HUD, not the Stitch export's card styling
## Tests
- Widget: search filters the visible list correctly
- Widget: filter-by-activity narrows correctly
- Unit: summary-stats aggregation matches a hand-computed total over a fixed set of
seeded trips
- All existing `trips list` widget tests (empty state, newest-first ordering, active-ride
exclusion, merge-at-exactly-two-selections, delete) updated to the new layout and still
passing
## Risks
- Rendering a small `RideMap` thumbnail per card in a long list could be a real
performance cost (many simultaneous `FlutterMap` instances) — consider a lightweight
static polyline-on-canvas rendering instead of a full interactive map per thumbnail if
this proves too expensive; measure with a real ride history of realistic length before
deciding.
## Out of scope
Any change to trip data, merge, or split logic (V3-10) — presentation and search/filter
only.
## Outcome
`TripsScreen` is now a headerless screen (per UI-01) over the shared dimmed background
map, with a `GlassPanel`-wrapped search bar (`ride-search`) plus a `PopupMenuButton`
activity filter (`activity-filter`), a four-card distinct summary row, and `_TripCard`s
carrying a real 88x88 `RideMap` thumbnail of that ride's own recorded path.
**`RideHistorySummary`** is a plain pure function (`RideHistorySummary.compute(List<Trip>)`),
not a Riverpod provider — folding over an already-fetched list is cheap regardless of ride
count, and being a pure function is what made it directly unit-testable against a
hand-computed total, per the ticket's own Tests section. Its average speed is
distance-weighted across the whole history (not an average of each ride's own average),
so a handful of long rides isn't drowned out by many short ones. The summary reflects the
full unfiltered history, not the currently-searched/filtered subset — it's an overview
stat, not a count of what's visible below it.
**Bug found and fixed while wiring the thumbnails:** the shared `RideMap` widget
unconditionally rendered `TileAttribution` (added in UI-09) inside every `FlutterMap`,
including tiny 88x88 list thumbnails — both a real UX problem (attribution controls
cluttering dozens of small thumbnails) and an actual test collision (`find.byType(Icon)`
scoped to one trip's keyed subtree started matching the attribution button's own icon
too, once thumbnails were added). Fixed by adding a `showAttribution` bool parameter to
`RideMap` (default `true`, so every existing full-size map call site — Map tab
background, Route Planner, Trip Detail — is unaffected) and passing `showAttribution:
false` specifically for `_TripCard`'s thumbnail.
**The same `GlassPanel`-needs-`Material`-ancestor bug UI-06 already hit** recurred
identically in `_TripCard` (rebuilt from `StatelessWidget` to `ConsumerWidget` to watch
the per-trip point/segment streams for its thumbnail) and was fixed the same way:
`Material(type: MaterialType.transparency)` wrapping the `InkWell`.
**Rename is correctly out of scope for this file**: it lives on `TripDetailScreen`
(`_rename`), not the list, and was never touched here — the ticket's "merge/delete/rename
still work" line is satisfied by leaving Trip Detail's own rename alone while restyling
only the list's merge/delete selection toolbar.
**Tests:** `flutter analyze` clean. `flutter test` green at **374 tests** (up from 370),
adding: a widget test that search narrows the list to matching rides, a widget test that
filtering by activity narrows the list, and two unit tests for `RideHistorySummary.compute`
(a hand-computed multi-trip total, and the empty-history zero-division guard) in a new
`test/ride_history_summary_test.dart`. Two pre-existing `widget_test.dart` assertions
needed updating for the new card structure: `'an active ride does not appear in the
list'` asserted `find.byType(Card)` (now `find.byType(RideMap)`, unique to a trip card
since `_SearchAndFilter`/`_SummaryStatCard` don't render one), and the icon-count
collision above resolved once attribution was suppressed on thumbnails.
**Android emulator verification** (`Medium_Phone_API_35`): confirmed end-to-end after
working around significant, unrelated host resource pressure during this session (the
emulator repeatedly hit System UI/app ANRs under low host memory; resolved by freeing
host RAM, a full emulator restart, and a `force-stop`+relaunch of the app — see session
notes, not an app defect). Verified on-device: search field narrows the list correctly
(a query matching neither ride's name/date shows "No rides match your search.", clearing
it restores both); the activity filter's `PopupMenuButton` opens with all seven
activities plus "All activities", correctly highlights the currently-active choice,
narrows to zero rides when filtered to an activity neither seeded ride has (Bicycle),
and shows both again when filtered to the activity they actually have (Motorcycle); the
four summary cards (Total Dist/Total Time/Rides/Avg Speed) render as genuinely distinct
values, not the mockup's four-identical-cards artifact; both ride cards render real,
distinct polyline thumbnails with no attribution icon visible; long-pressing a card
enters selection mode with the Cancel/count/Merge/Delete toolbar, Merge correctly stays
disabled at one selection and enables at exactly two. Colors/icons match Map HUD's
established palette (translucent `GlassPanel` cards, blue accent, same activity icon set)
throughout.