Files
dmc/app/lib/ui/providers/current_mission_provider.dart
T
Constantin LeueandClaude Sonnet 5 d3c5a68636 Add waypoint list panel and bottom stats bar
- BottomStatsBar: persistent "Details: N WP · dist m · duration" pill
  (HTML prototype's #bottomBar/#statsRow), tap opens the full waypoint
  list. Mission/drone name and the Send button are intentionally not part
  of this - mission library and upload aren't wired up yet.
- A warn banner ("Route exceeds drone capabilities at highlighted point")
  appears above it whenever buildRouteGeometry finds a bad vertex or leg -
  a direct, low-effort payoff from the fillet geometry work.
- WaypointListPanel: full-screen overlay listing every waypoint
  (altitude/speed/execute/catch radius), matching the prototype's List
  tab. Only one row editable at a time; editing swaps plain text for
  draggable WaypointChip values and an execute dropdown. Row tap (outside
  edit mode) centers the map on that waypoint and closes the panel; delete
  removes it and adjusts the reticle's target index the same way the halo
  menu's remove already does.
- WaypointChip: vertical-drag value chip (HTML prototype's .value-chip),
  fixed step of 10 per 14px matching chipFieldConfig - simpler than the
  original's dual vertical/horizontal axis-lock gesture, which felt like
  more fidelity than the interaction warrants here.
- Provider gained setCatchRadius and setAction (direct assignment, as
  opposed to toggleAction's toggle-to-none semantics used by the halo
  menu) to support the list's editable fields.
- Altitude/Speed full-chart tabs from the prototype (drag-to-adjust
  altitude/speed profile charts) are a separate, larger feature and not
  part of this step.

Centering on a waypoint from the list calls MapController.move(), which -
like the reticle's auto-snap - doesn't emit MoveEnd, so snap detection is
invoked manually afterward to match the prototype's resulting behavior.

Added 4 widget tests (open empty list, row-tap centers and closes, delete
removes and updates the empty state, chip drag updates altitude) - one
required a pump(duration) after the drag to flush the chip's 120ms flash
Timer, which was otherwise still pending at test teardown. All 15 tests
and flutter analyze pass. Manually verified on the Pixel_10a emulator:
opened the list with two waypoints, edited a row's altitude via chip drag
(confirmed value change, accounting for device-pixel-ratio scaling vs the
widget-test logical-pixel case), and confirmed row-tap re-centers/closes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 07:25:32 +02:00

91 lines
2.8 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],
];
}
/// Aktualisiert den Fangradius eines Wegpunkts (Wegpunktliste: Catch-Chip).
void setCatchRadius(int index, double catchRadiusM) {
state = [
for (var i = 0; i < state.length; i++)
if (i == index)
state[i].copyWith(catchRadiusM: catchRadiusM)
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],
];
}
/// Setzt die Execute-Aktion eines Wegpunkts direkt (Wegpunktliste:
/// Execute-Dropdown - im Unterschied zu [toggleAction] kein Umschalten).
void setAction(int index, WaypointAction action) {
state = [
for (var i = 0; i < state.length; i++)
if (i == index) state[i].copyWith(action: 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,
);