Turns the newly-added issues in docs/FEEDBACK.md into four more self-contained tickets: always-interactive map with a recenter button, Route Planner's Null Island centering bug, HUD reflow-on-hide, and title-driven HUD sizing. Sequenced FB-08 before FB-09 since both rework the same HUD layout files.
7.6 KiB
FB-07 — Route Planner opens on Null Island instead of the rider's real location
Depends on FB-01 (reuses its ambient-location provider) · Size S/M · Status Not started
Goal
A new route, or a route with fewer than two pins, must open the map on the rider's real location. Today it opens on the middle of the ocean.
Context
Direct user feedback (docs/FEEDBACK.md):
Pin drops still do not work at all, the map when placing the pins doesn't render at a all, it's just a blank canvas that you can zoom in and out of but no detail appears. The pins can also be placed but nothing is rendered on the map so you have no clue where they are.
lib/src/ui/routes/route_planner_screen.dart, build() (~line 165):
child: FlutterMap(
mapController: _mapController,
options: MapOptions(
initialCameraFit: _initialFit(waypoints),
initialCenter: waypoints.isEmpty
? const ll.LatLng(0, 0)
: ll.LatLng(waypoints.first.latitude, waypoints.first.longitude),
initialZoom: waypoints.length <= 1 ? ambientZoom : maxTileZoom - 3,
maxZoom: maxTileZoom,
onTap: (tapPosition, point) {
repo.addWaypoint(widget.routeId, point.latitude, point.longitude);
...
ll.LatLng(0, 0) is Null Island — a single point in the Atlantic Ocean with no land
and no map detail at any zoom level. A brand-new route (0 pins), or a route with exactly
1 pin dropped near that point, opens the map centered there. At street-level zoom, an
area of open ocean legitimately renders as a flat, featureless expanse — this matches
the report exactly: "a blank canvas... no detail appears," and a pin dropped anywhere
near that same spot lands in the same empty area, with nothing else on screen to show
where it is relative to.
RoutePlannerScreen manages its own FlutterMap directly — it does not use RideMap
and does not currently read ambientPositionProvider at all (confirmed by grep: no
reference to ambientPositionProvider anywhere in route_planner_screen.dart). FB-01
already built exactly the provider this ticket needs — lib/src/app/providers.dart
(~line 66):
final ambientPositionProvider = StreamProvider.autoDispose<LocationFix?>((ref) async* {
if (!ref.watch(mapEnabledProvider)) {
yield null;
return;
}
final source = ref.watch(locationSourceProvider);
try {
await source.start(); // idempotent; safe even if a recording already started it
} on LocationException {
yield null; // permission denied / service disabled — ambient mode is best-effort
return;
}
yield* source.fixes.map<LocationFix?>((fix) => fix);
});
LocationFix is defined in lib/src/recording/location_source.dart.
initialCenter/initialZoom are read exactly once, at FlutterMap construction —
not on every rebuild. The existing comment in this file already documents this
constraint for the waypoints stream (~line 158): the screen waits for
waypointsAsync.hasValue before building FlutterMap at all, specifically so the
first real camera position is correct from the start. The same constraint applies here:
reading ambientPositionProvider must happen before FlutterMap is constructed, not
patched in afterward with a controller move — mirror the existing wait-for-first-value
pattern, do not invent a new one.
Design
- Watch
ambientPositionProviderinbuild(), the same wayShellScaffoldalready does (lib/src/ui/app_shell.dart~line 74):final ambientFix = ref.watch(ambientPositionProvider).valueOrNull; final ambientPosition = ambientFix == null ? null : ll.LatLng(ambientFix.latitude, ambientFix.longitude); - Use
ambientPositionforinitialCenterwhen there are fewer than 2 waypoints, falling back to(0, 0)only when no ambient fix is available yet (permission denied, service disabled, or the fix has not arrived yet):initialCenter: waypoints.isEmpty ? (ambientPosition ?? const ll.LatLng(0, 0)) : ll.LatLng(waypoints.first.latitude, waypoints.first.longitude), - Do not block the map on waiting for the ambient fix the way
waypointsAsyncis blocked on its own first value. A missing ambient fix must fall back to(0, 0)immediately, not show a spinner — the rider must always eventually reach a usable map, even with location permission denied. This matches the existingRideMapfallback behavior in FB-01 exactly (seeride_map.dart'sbounds == nullbranch). - Leave the 1-waypoint and 2-plus-waypoint camera logic unchanged — a route that already has a real pin should still center on that pin, not on the ambient position, since the pin is a stronger signal of where the rider actually wants to look.
- Confirm tiles genuinely render once centered on a real location. After this fix,
drop a pin near a real city on the Android emulator (use
adb emu geo fixto set a real location, as FB-01's own verification pass did). Take a screenshot. Confirm street-level map detail actually appears, not just a differently-colored blank area. If tiles still do not render even at a real location, that is a second, distinct bug — investigateTileLayer's setup in this file (urlTemplate,tileProvider,mapConnectivityProvider'sskeletonMode) before assuming this ticket's fix is sufficient, and document the real cause in the Outcome section.
Implementation
- Add
final ambientFix = ref.watch(ambientPositionProvider).valueOrNull;and theambientPositionconversion tobuild(). - Change the
initialCenterfallback for the empty-waypoints case fromconst ll.LatLng(0, 0)toambientPosition ?? const ll.LatLng(0, 0). - Run the app on the Android emulator. Set a real mock location. Open a new route. Confirm the map opens on that location with visible street detail, not open ocean.
Acceptance criteria
- A brand-new route (0 pins) opens the map centered on the rider's real location, when a location fix is available.
- A brand-new route still opens on
(0, 0)when no location fix is available (permission denied, service disabled, or no fix yet) — no crash, no infinite spinner. - A route with 1 or more pins still centers on the pin, unchanged from today.
- Dropping a pin near a real city on the emulator shows visible street-level map detail underneath it, confirmed by a real screenshot.
flutter analyzeclean,flutter testgreen, test count only goes up.
Tests
- Widget test: pump
RoutePlannerScreenfor a route with 0 waypoints, withambientPositionProvideroverridden to a fixedLocationFix— assert theFlutterMap'soptions.initialCentermatches that fix, not(0, 0). - Widget test: pump
RoutePlannerScreenfor a route with 0 waypoints, withambientPositionProvideroverridden to emitnull(no fix available) — assertinitialCenterfalls back to(0, 0), matching today's existing behavior. - Widget test: pump
RoutePlannerScreenfor a route with 1 real waypoint — assertinitialCenterstill matches that waypoint's own coordinates, not the ambient position, even whenambientPositionProvideremits a different fix.
Risks
- If the on-device check in this ticket's Implementation step 3 finds tiles still do not render at a real location, this ticket's fix alone is not sufficient — document the real root cause found and either fix it in this same ticket or state plainly in the Outcome section that a further ticket is needed. Do not claim this ticket is done without confirming real map detail actually appears on a screenshot.
Out of scope
Any change to the Map tab's own map (FB-06 covers its remaining issues). Turn-by-turn route following (V3-09, already deferred elsewhere).