Refresh the Flutter snapshot: v3 tickets V3-04 through V3-16, plus a fresh installable APK
V3-04 through V3-07, V3-10, V3-16 shipped complete; V3-11/V3-12/V3-14 shipped code-complete pending device/account verification; V3-08/V3-09 deferred behind a new V3-17 (self-hosted OSRM investigation). 316 tests passing, up from 221. The APK is a fresh release build (debug-signed, no release signing config exists yet) with two build fixes applied: core library desugaring enabled for flutter_local_notifications, and sentry_flutter bumped to 9.27.0 (8.14.2's bundled Kotlin plugin was incompatible with this project's Kotlin 2.4.0 toolchain).
This commit is contained in:
90
rippr-flutter-src/docs/v3/V3-04-live-map.md
Normal file
90
rippr-flutter-src/docs/v3/V3-04-live-map.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# V3-04 — Live map on the recording screen
|
||||
|
||||
**Phase** Live map · **Depends on** nothing · **Size** M · **Status** Done
|
||||
|
||||
## Goal
|
||||
While recording, show the path as it is drawn, on the recording screen.
|
||||
|
||||
## Context
|
||||
v2 shipped without one deliberately: the phone rides in a pocket, so a live map would burn
|
||||
battery for something nobody is looking at. **That decision is reversed** — Dylan wants it
|
||||
for visual appeal, and because a mounted phone is now a real use case (V3-05).
|
||||
|
||||
The map component already exists (`RideMap`) and already handles per-segment polylines,
|
||||
render-only decimation and the zoom clamp. This ticket is mostly about *lifecycle*, not
|
||||
drawing.
|
||||
|
||||
## Design
|
||||
Two constraints survive the reversal and are non-negotiable:
|
||||
|
||||
- **`RecordingEngine` must never reference a map.** Rendering belongs to a visible screen's
|
||||
widget lifecycle. The engine already exposes everything needed.
|
||||
- **No tile fetch or redraw while backgrounded.** A pocketed phone must cost exactly what
|
||||
it costs today.
|
||||
|
||||
Feed the map from a stream of the current trip's points. `watchTripStats` exists but
|
||||
returns aggregates; this needs the points themselves — add a `watchPointsForTrip` Drift
|
||||
stream, which updates naturally on each writer flush (~2 s), not per fix.
|
||||
|
||||
Follow the rider: keep the latest point centred, with a manual-pan override that stops
|
||||
auto-follow until re-enabled.
|
||||
|
||||
Behind the existing map toggle, off by default while it is unproven on battery.
|
||||
|
||||
## Implementation
|
||||
1. `watchPointsForTrip(tripId)` in `AppDatabase`
|
||||
2. Hoist the map above the stats card on the record screen, behind the toggle
|
||||
3. `WidgetsBindingObserver` — on `AppLifecycleState.paused`, stop tile fetching; resume on
|
||||
`resumed`. This is the load-bearing part.
|
||||
4. Auto-follow with a pan override
|
||||
5. Keep the numeric readout visible; the map must not push SPEED off screen (the record
|
||||
screen already scrolls — see the overflow fix in `port/PROGRESS.md`)
|
||||
|
||||
## Acceptance criteria
|
||||
- [ ] The path appears and extends while recording
|
||||
- [ ] Backgrounding the app stops all tile activity, verified in a network log
|
||||
- [ ] `RecordingEngine` still has no map import — grep it
|
||||
- [ ] With the toggle off, no map widget is constructed at all
|
||||
- [ ] Speed and elapsed remain visible without scrolling on a common phone size
|
||||
|
||||
## Tests
|
||||
- Widget: map appears only when recording and the toggle is on
|
||||
- Widget: lifecycle transition to paused stops the tile layer
|
||||
- The existing map tests still pass
|
||||
|
||||
## Risks
|
||||
- **Battery.** This is the whole reason v2 said no. Measure before defaulting it on
|
||||
(V3-13).
|
||||
- Redrawing per fix rather than per flush would be wasteful; drive from the database
|
||||
stream, which is already batched.
|
||||
|
||||
## Out of scope
|
||||
Mounted mode (V3-05). Other riders on the map (v4).
|
||||
|
||||
## Outcome
|
||||
Shipped as designed. `AppDatabase.watchPointsForTrip`/`watchSegmentsForTrip` feed two
|
||||
`autoDispose.family` providers (`livePointsProvider`, `liveSegmentsProvider`) keyed by
|
||||
trip id; a `_LiveMap` adapter widget on the record screen reads them and hands the result
|
||||
to the existing `RideMap`, unchanged in shape. `RecordingEngine` was never touched —
|
||||
verified by `test/architecture_test.dart`, which greps the source rather than trusting a
|
||||
comment.
|
||||
|
||||
`RideMap` itself grew two small, general capabilities rather than a parallel "live"
|
||||
widget: a `WidgetsBindingObserver` that drops the `TileLayer` entirely (not just visually,
|
||||
via widget tree omission) outside `AppLifecycleState.resumed`, and an optional `follow`
|
||||
flag that recentres on the latest point via `didUpdateWidget` + a post-frame
|
||||
`MapController.move`, cancelled permanently by the first user-gesture pan. Both apply
|
||||
to the trip-detail map too, which is a free win: a backgrounded detail screen no longer
|
||||
holds tiles fetching either.
|
||||
|
||||
Three existing record-screen tests (`recording swaps to PAUSE and STOP`, `paused offers
|
||||
RESUME`, `discard asks before destroying anything`) had to gain an explicit `map: false` —
|
||||
they predate this ticket and would otherwise have started constructing a real
|
||||
`FlutterMap`/`TileLayer` against an active trip, which is exactly the tile-fetch-in-tests
|
||||
problem the trip-detail tests already route around.
|
||||
|
||||
3 new tests: the grep-based engine-purity check in `architecture_test.dart`, plus two in
|
||||
`widget_test.dart` — map presence/absence by toggle and trip state, and the lifecycle
|
||||
transition (paused drops `TileLayer` but keeps `PolylineLayer`; resumed brings it back),
|
||||
driven via the standard `flutter/lifecycle` platform-message technique rather than a
|
||||
private binding API. `flutter analyze` clean; full suite green (224 tests, up from 221).
|
||||
Reference in New Issue
Block a user