Files
dmc/app/lib/ui/providers/current_mission_provider.dart
T
Constantin LeueandClaude Sonnet 5 b93bbab23b Add fillet flight path geometry and Alt/Speed wheels
- RouteGeometry (lib/domain/mission/route_geometry.dart): physically
  grounded flight path - straight segments + tangential arcs at each
  course change, sized from the drone's minimum turn radius (doc 3.7/4.6),
  replacing a naive spline that would suggest unrealistic turn radii.
  Marks a vertex "bad" (turn angle >= 160°, tangent length exceeding 90%
  of either adjacent leg, or exceeding the waypoint's catch radius) and a
  leg "bad" when the required climb/descent rate exceeds the drone's
  max climb/descent rate. Pure Dart, no Flutter dependency, so it stays
  usable if the mission domain is ever split into its own package (4.22).
  Wind-based turn-radius correction from the prototype isn't ported yet -
  the wind system (doc 3.8) doesn't exist in the Flutter app.
- MissionMap now draws each RouteSegment as its own Polyline, colored red
  when bad instead of a single plain white line; waypoint markers turn red
  too when their vertex is bad.
- ValueWheel: vertical drag-to-adjust tape control (HTML prototype's
  #altWheel/#spdWheel), custom-painted tick marks, blue center indicator,
  gradient fade at top/bottom. Wired into PlanScreen on both screen edges
  with Alt/Speed readouts.
- curAlt/curSpeed now live in PlanScreen state instead of fixed constants:
  dropping a new waypoint uses whatever the wheels are currently set to;
  entering editing mode on an existing waypoint loads its values into the
  wheels; adjusting a wheel while editing writes live into that waypoint
  (provider gained setAltitude/setSpeed to match the existing
  moveWaypoint/toggleAction pattern).

Added dedicated unit tests for the geometry (empty list, straight line,
feasible 90° turn producing a line-arc-line segment sequence, an
infeasible near-180° turn, and an infeasible descent rate) rather than
relying on eyeballing it on the emulator - this is exactly the kind of
ported-math correctness that's hard to verify visually but easy to get
subtly wrong. All 11 tests (previous 6 + these 5) and flutter analyze
pass. Also manually verified the wheels on the Pixel_10a emulator: drag
changes the value and the on-screen readout in real time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 23:13:24 +02:00

71 lines
2.2 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../domain/waypoint/flat_waypoint_list.dart';
/// Haelt die Wegpunktliste der aktuell in Bearbeitung befindlichen Mission
/// (Architektur-Doku 3.2/3.6). Reaktiver Einzelwert im Sinne von 4.16 -
/// daher Riverpod statt Bloc; die App-Mode State Machine bleibt in
/// AppModeCubit.
class CurrentMissionNotifier extends Notifier<List<Waypoint>> {
@override
List<Waypoint> build() => [];
void addWaypoint(Waypoint waypoint) {
state = [...state, waypoint];
}
/// Aktualisiert die Position eines Wegpunkts (Drag-to-Reposition im
/// Editier-Modus: Kartenverschieben bewegt den Zielpunkt live mit).
void moveWaypoint(int index, double lat, double lon) {
state = [
for (var i = 0; i < state.length; i++)
if (i == index) state[i].copyWith(lat: lat, lon: lon) else state[i],
];
}
/// Aktualisiert die Zielhoehe eines Wegpunkts (Alt-Drehrad im
/// Editier-Modus schreibt live in den Zielpunkt).
void setAltitude(int index, double altitudeM) {
state = [
for (var i = 0; i < state.length; i++)
if (i == index) state[i].copyWith(altitudeM: altitudeM) else state[i],
];
}
/// Aktualisiert die Zielgeschwindigkeit eines Wegpunkts (Speed-Drehrad im
/// Editier-Modus schreibt live in den Zielpunkt).
void setSpeed(int index, double speedMs) {
state = [
for (var i = 0; i < state.length; i++)
if (i == index) state[i].copyWith(speedMs: speedMs) else state[i],
];
}
/// Schaltet die Execute-Aktion eines Wegpunkts um (Halo-Radialmenue:
/// erneutes Antippen derselben Aktion setzt sie zurueck auf "none").
void toggleAction(int index, WaypointAction action) {
state = [
for (var i = 0; i < state.length; i++)
if (i == index)
state[i].copyWith(
action:
state[i].action == action ? WaypointAction.none : action,
)
else
state[i],
];
}
void removeAt(int index) {
state = [
for (var i = 0; i < state.length; i++)
if (i != index) state[i],
];
}
}
final currentMissionProvider =
NotifierProvider<CurrentMissionNotifier, List<Waypoint>>(
CurrentMissionNotifier.new,
);