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((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 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 waypoints) { ref.read(followDroneProvider.notifier).set(false); fitMapControllerToWaypoints(ref.read(mapControllerProvider), waypoints); }