Files
dmc/app/lib/ui/providers/map_controller_provider.dart

71 lines
3.5 KiB
Dart

import 'package:flutter/widgets.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:latlong2/latlong.dart';
import '../../domain/waypoint/flat_waypoint_list.dart';
import 'follow_drone_provider.dart';
/// Ein einziger, app-lebenslanger MapController statt einer pro
/// PlanScreen-Mount neu erzeugten Instanz.
///
/// Grund: Die Kopfleiste (AppShell/TopModeBar) liegt eine Ebene ueber
/// PlanScreen und braucht fuer Suche/Home/Fit denselben Controller wie die
/// Karte - ein PlanScreen-privates Feld waere von dort nicht erreichbar.
/// Als Nebeneffekt (und in Wahrheit der wichtigere Punkt) bleibt die
/// Kamera-Position/Zoom jetzt auch ueber einen Wechsel nach Fly und
/// zurueck erhalten: flutter_map tastet einen extern uebergebenen
/// MapController beim erneuten Mounten nicht an (siehe
/// FlutterMap._setMapController()), anders als der bisherige Zustand, wo
/// PlanScreens eigener Controller beim Verlassen des Plan-Modus komplett
/// verworfen wurde. Entspricht damit auch besser dem HTML-Demonstrator,
/// der nur eine einzige Leaflet-Instanz fuer die gesamte App kennt.
final mapControllerProvider = Provider<MapController>((ref) {
final controller = MapController();
ref.onDispose(controller.dispose);
return controller;
});
/// Zentriert [controller] auf die Bounding-Box der uebergebenen Wegpunkte
/// (Doku 3.6, analog fitToWaypoints() im HTML-Demonstrator). Nimmt bewusst
/// direkt den [MapController] statt eines Ref/WidgetRef entgegen, damit
/// diese eine Implementierung sowohl aus Widgets (ueber [fitMapToWaypoints]
/// unten) als auch aus Notifiern (CurrentMissionMetaNotifier.
/// _fitMapToWaypoints, WidgetRef und Notifier-Ref sind unterschiedliche,
/// nicht austauschbare Typen) aufgerufen werden kann - vorher gab es hier
/// zwei unabhaengige Kopien der Padding-Logik, die bereits einmal
/// auseinandergelaufen sind (Fit-Button symmetrisch, automatisches Fit beim
/// Missions-Laden noch mit alter Fusszeilen-Aussparung).
void fitMapControllerToWaypoints(MapController controller, List<Waypoint> waypoints) {
if (waypoints.isEmpty) return;
final points = [for (final w in waypoints) LatLng(w.lat, w.lon)];
controller.fitCamera(
CameraFit.bounds(
bounds: LatLngBounds.fromPoints(points),
// Symmetrisch statt vorher unten 140 (Reservierung fuer die
// Fusszeile) - der Fit soll den ganzen Bildschirm ausnutzen statt
// dort unnoetig herauszuzoomen (Doku: "Fusszeile wird ausgespart,
// nicht genutzt").
padding: const EdgeInsets.all(50),
),
);
}
/// Bequemlichkeits-Wrapper fuer Aufrufer aus Widgets (Consumer/
/// ConsumerStatefulWidget mit [WidgetRef]) - geteilt zwischen dem manuellen
/// Fit-Button (MapSearchControls/FlyMapControls) und den map-bezogenen
/// Warnungs-Aktionen (Doku 3.10).
///
/// Beendet dabei immer auch den Kartenfolgemodus (followDroneProvider,
/// Doku 3.11) - dieser Fit bewegt die Kamera ueber denselben MapController
/// wie die Telemetrie-Nachfuehrung (source == mapController), FlyScreens
/// Abbruch bei manueller Karteninteraktion greift hier also nicht. Ohne
/// diesen expliziten Abbruch wuerde der naechste Telemetrie-Frame die
/// Kamera sofort wieder von der gerade eingestellten Uebersicht weg auf die
/// Drohne zurueckziehen. In Plan-Modus/Mock-Kontexten ohne aktiven
/// Folgemodus ist der Aufruf ein no-op.
void fitMapToWaypoints(WidgetRef ref, List<Waypoint> waypoints) {
ref.read(followDroneProvider.notifier).set(false);
fitMapControllerToWaypoints(ref.read(mapControllerProvider), waypoints);
}