Files
rippr/docs/ui-redesign/UI-07-rides-history.md
uhryniuk 7c1d6570a2 UI-07: rebuild Rides History with search, activity filter, and real ride thumbnails
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
2026-08-24 14:32:00 -05:00

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 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

  • 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 TripsScreen still 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 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 _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.