FB-01: ambient GPS position drives the shared background map while idle
Adds ambientPositionProvider (never calls LocationSource.stop(), only the idempotent start()) and wires it into ShellScaffold whenever no trip is active, so the persistent background map centers and follows the device's live GPS position at street-level zoom instead of sitting at (0,0)/zoom 2 until a recording starts. RideMap gains an ambientPosition param that the existing chase-camera/pan-cancel/location-marker logic falls back to whenever there are no recorded points, with recorded points always taking priority. Also brings in the docs/feedback ticket set (FB-01..FB-05, README, FEEDBACK.md) that this worktree's branch point predated. Adds 10 tests (374 -> 384): RideMap-level ambient centering/chase/pan-cancel/ marker coverage in test/ride_map_test.dart, plus shell-wiring and provider-level (never-calls-stop, mapEnabledProvider-off) coverage in test/widget_test.dart. flutter analyze remains clean.
This commit is contained in:
@@ -57,6 +57,30 @@ final locationSourceProvider = Provider<LocationSource>((ref) {
|
||||
return source;
|
||||
});
|
||||
|
||||
/// The device's current position when nothing is being recorded — drives the shared
|
||||
/// background map's "look like Google Maps while idle" behavior. Deliberately NOT
|
||||
/// gated through `recordingEngineProvider`/`RecordingEngine.start()` — this must work
|
||||
/// whether or not a ride is ever recorded. Never calls `LocationSource.stop()`: the
|
||||
/// underlying `locationSourceProvider` instance is shared with the recording engine,
|
||||
/// and `stop()` is not reference-counted (see FB-01's ticket for why).
|
||||
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);
|
||||
// No `stop()` call, ever, on dispose — see the doc comment above. Only this
|
||||
// provider's own subscription to the broadcast `fixes` stream ends; the shared
|
||||
// platform subscription is left exactly as it was.
|
||||
});
|
||||
|
||||
/// Loaded once at startup; null until then so nothing blocks the first frame.
|
||||
final configProvider = StateProvider<Config?>((ref) => null);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user