map follow on drone focus. rubberband pointing to next waypoint. drone icon bigger 40px
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -9,8 +10,10 @@ import '../../../domain/mission/mission_stats.dart';
|
||||
import '../../../domain/mission/mission_warnings.dart';
|
||||
import '../../../domain/mission/route_geometry.dart';
|
||||
import '../../../domain/wind/wind_math.dart';
|
||||
import '../../../transport/flight_controller_link.dart';
|
||||
import '../../providers/active_drone_profile_provider.dart';
|
||||
import '../../providers/current_mission_provider.dart';
|
||||
import '../../providers/follow_drone_provider.dart';
|
||||
import '../../providers/map_controller_provider.dart';
|
||||
import '../../providers/telemetry_provider.dart';
|
||||
import '../../providers/terrain_provider.dart';
|
||||
@@ -28,22 +31,50 @@ import '../../widgets/warnings_panel.dart';
|
||||
/// die Kameraposition bleibt also beim Umschalten erhalten. Wegpunkte lassen
|
||||
/// sich weiterhin ueber die Liste anpassen (z.B. spontane Hoehenaenderung),
|
||||
/// nur das Karten-Reticle zum Anlegen/Verschieben entfaellt hier.
|
||||
class FlyScreen extends ConsumerWidget {
|
||||
class FlyScreen extends ConsumerStatefulWidget {
|
||||
const FlyScreen({super.key});
|
||||
|
||||
void _onCenterOnWaypoint(WidgetRef ref, int index) {
|
||||
@override
|
||||
ConsumerState<FlyScreen> createState() => _FlyScreenState();
|
||||
}
|
||||
|
||||
class _FlyScreenState extends ConsumerState<FlyScreen> {
|
||||
late final MapController _mapController;
|
||||
StreamSubscription<MapEvent>? _mapEventSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_mapController = ref.read(mapControllerProvider);
|
||||
_mapEventSub = _mapController.mapEventStream.listen(_onMapEvent);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_mapEventSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Jede manuelle Kartengeste (Pan/Pinch/Fling, ...) bricht den ueber den
|
||||
/// GPS-Knopf in der Kopfleiste (FlyMapControls) aktivierten Folgemodus ab
|
||||
/// - nur Bewegungen mit source == mapController sind programmatisch
|
||||
/// (durch die Telemetrie-Nachfuehrung unten selbst oder andere
|
||||
/// Kartensprmuenge ausgeloest) und sollen ihn nicht beenden.
|
||||
void _onMapEvent(MapEvent event) {
|
||||
if (ref.read(followDroneProvider) &&
|
||||
event.source != MapEventSource.mapController) {
|
||||
ref.read(followDroneProvider.notifier).set(false);
|
||||
}
|
||||
}
|
||||
|
||||
void _onCenterOnWaypoint(int index) {
|
||||
final waypoints = ref.read(currentMissionProvider);
|
||||
if (index < 0 || index >= waypoints.length) return;
|
||||
final wp = waypoints[index];
|
||||
final controller = ref.read(mapControllerProvider);
|
||||
controller.move(LatLng(wp.lat, wp.lon), controller.camera.zoom);
|
||||
_mapController.move(LatLng(wp.lat, wp.lon), _mapController.camera.zoom);
|
||||
}
|
||||
|
||||
void _openWaypointList(
|
||||
BuildContext context,
|
||||
WidgetRef ref, {
|
||||
PanelTab initialTab = PanelTab.list,
|
||||
}) {
|
||||
void _openWaypointList({PanelTab initialTab = PanelTab.list}) {
|
||||
Navigator.of(context).push(
|
||||
PageRouteBuilder<void>(
|
||||
opaque: true,
|
||||
@@ -57,7 +88,7 @@ class FlyScreen extends ConsumerWidget {
|
||||
initialTab: initialTab,
|
||||
onClose: () => Navigator.of(routeContext).pop(),
|
||||
onCenterOnWaypoint: (index) {
|
||||
_onCenterOnWaypoint(ref, index);
|
||||
_onCenterOnWaypoint(index);
|
||||
Navigator.of(routeContext).pop();
|
||||
},
|
||||
onDelete: (index) =>
|
||||
@@ -79,11 +110,7 @@ class FlyScreen extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _openWarningsPanel(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
List<MissionWarning> warnings,
|
||||
) {
|
||||
void _openWarningsPanel(List<MissionWarning> warnings) {
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
builder: (routeContext) => WarningsPanel(
|
||||
warnings: warnings,
|
||||
@@ -92,9 +119,9 @@ class FlyScreen extends ConsumerWidget {
|
||||
Navigator.of(routeContext).pop();
|
||||
switch (warning.action) {
|
||||
case WarningAction.altitude:
|
||||
_openWaypointList(context, ref, initialTab: PanelTab.altitude);
|
||||
_openWaypointList(initialTab: PanelTab.altitude);
|
||||
case WarningAction.speed:
|
||||
_openWaypointList(context, ref, initialTab: PanelTab.speed);
|
||||
_openWaypointList(initialTab: PanelTab.speed);
|
||||
case WarningAction.map:
|
||||
fitMapToWaypoints(ref, ref.read(currentMissionProvider));
|
||||
}
|
||||
@@ -104,10 +131,23 @@ class FlyScreen extends ConsumerWidget {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
final waypoints = ref.watch(currentMissionProvider);
|
||||
final droneProfile = ref.watch(activeDroneProfileProvider);
|
||||
final telemetry = ref.watch(telemetryProvider).value;
|
||||
final followDrone = ref.watch(followDroneProvider);
|
||||
|
||||
// Solange der ueber den GPS-Knopf in der Kopfleiste (FlyMapControls)
|
||||
// aktivierte Folgemodus aktiv ist, jede neue Telemetrie-Position
|
||||
// programmatisch nachziehen (source == mapController, siehe
|
||||
// _onMapEvent - bricht den Modus dadurch nicht selbst wieder ab).
|
||||
ref.listen<AsyncValue<TelemetryFrame>>(telemetryProvider, (previous, next) {
|
||||
if (!followDrone) return;
|
||||
final frame = next.value;
|
||||
if (frame != null && frame.hasFix) {
|
||||
_mapController.move(LatLng(frame.lat, frame.lon), _mapController.camera.zoom);
|
||||
}
|
||||
});
|
||||
|
||||
final routeGeometry = buildRouteGeometry(
|
||||
waypoints,
|
||||
@@ -147,8 +187,8 @@ class FlyScreen extends ConsumerWidget {
|
||||
final droneMarker = telemetry != null && telemetry.hasFix
|
||||
? Marker(
|
||||
point: LatLng(telemetry.lat, telemetry.lon),
|
||||
width: 28,
|
||||
height: 28,
|
||||
width: 40,
|
||||
height: 40,
|
||||
alignment: Alignment.center,
|
||||
child: DroneMarkerIcon(headingDeg: telemetry.headingDeg),
|
||||
)
|
||||
@@ -156,17 +196,37 @@ class FlyScreen extends ConsumerWidget {
|
||||
? null
|
||||
: Marker(
|
||||
point: LatLng(waypoints.first.lat, waypoints.first.lon),
|
||||
width: 28,
|
||||
height: 28,
|
||||
width: 40,
|
||||
height: 40,
|
||||
alignment: Alignment.center,
|
||||
child: const DroneMarkerIcon(),
|
||||
);
|
||||
|
||||
// Gestrichelte Linie von der Drohne zum aktiven Wegpunkt (Doku 3.4:
|
||||
// TelemetryFrame.activeWaypointIndex aus MSP_NAV_STATUS) - derselbe
|
||||
// Darstellungsstil wie das Reticle-Gummiband im Plan-Modus (MissionMap.
|
||||
// rubberBandLine), hier aber Drohne -> naechster Wegpunkt statt letzter
|
||||
// Wegpunkt -> Kartenmitte.
|
||||
List<LatLng>? rubberBand;
|
||||
final activeWpIndex = telemetry?.activeWaypointIndex;
|
||||
if (telemetry != null &&
|
||||
telemetry.hasFix &&
|
||||
activeWpIndex != null &&
|
||||
activeWpIndex >= 0 &&
|
||||
activeWpIndex < waypoints.length) {
|
||||
final target = waypoints[activeWpIndex];
|
||||
rubberBand = [
|
||||
LatLng(telemetry.lat, telemetry.lon),
|
||||
LatLng(target.lat, target.lon),
|
||||
];
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
MissionMap(
|
||||
mapController: ref.watch(mapControllerProvider),
|
||||
mapController: _mapController,
|
||||
routeSegments: routeGeometry.segments,
|
||||
rubberBandLine: rubberBand,
|
||||
waypointMarkers: [
|
||||
for (var i = 0; i < waypoints.length; i++)
|
||||
CircleMarker(
|
||||
@@ -209,8 +269,8 @@ class FlyScreen extends ConsumerWidget {
|
||||
totalDistanceM: missionStats.distanceM,
|
||||
totalDurationSec: missionStats.durationSec,
|
||||
warnings: warnings,
|
||||
onDetailsTap: () => _openWaypointList(context, ref),
|
||||
onWarningsTap: () => _openWarningsPanel(context, ref, warnings),
|
||||
onDetailsTap: _openWaypointList,
|
||||
onWarningsTap: () => _openWarningsPanel(warnings),
|
||||
onSettingsTap: () => Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (_) => const SettingsScreen(),
|
||||
)),
|
||||
|
||||
Reference in New Issue
Block a user