Restyles TripsScreen toward Map HUD's GlassPanel language: a search bar and activity-filter dropdown, four distinct summary stat cards (replacing the mockup's four-identical-cards artifact), and per-ride RideMap thumbnails showing each ride's own recorded path instead of stock photos. RideHistorySummary is a pure function rather than a provider, since folding over an already-fetched trip list is cheap regardless of history length, and it makes the aggregation directly unit-testable. Average speed is distance-weighted across the whole history rather than averaging each ride's own average. Adds a showAttribution flag to RideMap (default true) to suppress the TileAttribution control on tiny list thumbnails, where it was both a UX problem and a test collision with each card's own activity icon. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
9.2 KiB
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 _TripTiles (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
Tripaggregate 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
- Rebuild
TripsScreen's background to sit over UI-01's dimmed shared map instead of a plain scaffold background. - Add search (text query against
Trip.name/date) and filter (activity type at minimum, reusing V3-01'sActivityenum andactivityIcon/activityLabelhelpers). - 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). - Rebuild
_TripTileas a card with a small live/staticRideMapthumbnail (reusing the existingRideMapwidget at a small size and non-interactive, rather than building a second map-rendering path) plus the distance/time/avg-speed row. - 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
- Background is the shared live map (dimmed), not a static image
- Search narrows the list by name/date
- Filter narrows the list by activity type
- Summary row shows four distinct, real statistics, not one number repeated
- Each ride card shows an actual thumbnail of that ride's own path, not a stock photo
- Merge/delete/rename actions from today's
TripsScreenstill work - 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 listwidget 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
RideMapthumbnail per card in a long list could be a real performance cost (many simultaneousFlutterMapinstances) — 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 _TripCards
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.