Add center reticle control for dropping waypoints
- ReticleButton: fixed-at-screen-center "+" circle (HTML prototype: #centerCluster/#reticleBtn), tapping it drops a waypoint at the current map center. - CurrentMissionNotifier (Riverpod): holds the waypoint list for the mission being edited. Reactive single-value state per doc 4.16, so Riverpod rather than a new Bloc/Cubit. - MissionMap now renders each waypoint as a CircleLayer marker (white border, dark fill, matching the prototype's L.circleMarker styling). - PlanScreen owns the MapController needed to read the camera center on tap, and wires the reticle to the provider. Default altitude/speed/catch-radius (60m / 15 m/s / 60m) are hardcoded to the T1 Ranger default profile values from the prototype for now - TODO once drone profile settings (doc 3.5) exist. Verified with flutter analyze, flutter test (incl. a new test covering the add-waypoint path), and a real run on the Pixel_10a emulator: panned the map and dropped two waypoints, both markers stayed correctly georeferenced. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
fc7d014fda
commit
db6b46140c
@@ -1,14 +1,77 @@
|
||||
import 'package:flutter/material.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 '../../providers/current_mission_provider.dart';
|
||||
import '../../widgets/mission_map.dart';
|
||||
import '../../widgets/reticle_button.dart';
|
||||
|
||||
/// Missionsplanung: Karte + Wegpunktliste (Architektur-Doku 3.6/3.7).
|
||||
/// Kein eigenes Scaffold - die Kopfleiste (Plan/Fly-Umschalter) und der
|
||||
/// gemeinsame Scaffold-Rahmen liegen im AppShell darueber. Wegpunktliste,
|
||||
/// Draeder und Bottom-Bar folgen als naechster Schritt.
|
||||
class PlanScreen extends StatelessWidget {
|
||||
class PlanScreen extends ConsumerStatefulWidget {
|
||||
const PlanScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => const MissionMap();
|
||||
ConsumerState<PlanScreen> createState() => _PlanScreenState();
|
||||
}
|
||||
|
||||
class _PlanScreenState extends ConsumerState<PlanScreen> {
|
||||
final _mapController = MapController();
|
||||
|
||||
// TODO: aus dem aktiven Drohnen-Profil (Doku 3.5) uebernehmen, sobald die
|
||||
// Profilverwaltung existiert. Werte entsprechen dem Default-Profil
|
||||
// T1 Ranger im HTML-Demonstrator (curAlt=60, cruiseSpeed=15, catchRadius=60).
|
||||
static const _defaultAltitudeM = 60.0;
|
||||
static const _defaultSpeedMs = 15.0;
|
||||
static const _defaultCatchRadiusM = 60.0;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_mapController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _addWaypointAtMapCenter() {
|
||||
final center = _mapController.camera.center;
|
||||
ref.read(currentMissionProvider.notifier).addWaypoint(
|
||||
Waypoint(
|
||||
lat: center.latitude,
|
||||
lon: center.longitude,
|
||||
altitudeM: _defaultAltitudeM,
|
||||
speedMs: _defaultSpeedMs,
|
||||
catchRadiusM: _defaultCatchRadiusM,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final waypoints = ref.watch(currentMissionProvider);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
MissionMap(
|
||||
mapController: _mapController,
|
||||
waypointMarkers: [
|
||||
for (final wp in waypoints)
|
||||
CircleMarker(
|
||||
point: LatLng(wp.lat, wp.lon),
|
||||
radius: 6,
|
||||
color: const Color(0xFF2B2B28),
|
||||
borderStrokeWidth: 1.5,
|
||||
borderColor: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: ReticleButton(onTap: _addWaypointAtMapCenter),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user