Add mission/drone profile management with autosave and persistence
Vervollstaendigt Doku 3.5/3.6/7.1/7.3/7.5: ein Vollbild-Verwaltungsmenue fuer Missionen und Drohnenprofile (per Tabs umschaltbar, wie im HTML- Demonstrator #missionsPanel gemeinsam mit der Profilliste), Autosave der aktuell bearbeiteten Mission, Drift-Persistenz statt der bisherigen In-Memory-only currentMissionProvider, sowie Anzeige/Zugang ueber neue Chips in der Fusszeile (HTML-Demonstrator: #missionNameBtn/#droneNameBtn). Datenbank (services/database/): Missions-Tabelle um updatedAt ergaenzt, neue DroneProfiles- und AppSettingsTable-Tabellen (schemaVersion 2 mit onUpgrade-Migration). Drei Repositories kapseln Drift-Zugriff + JSON-(De-)Serialisierung der Wegpunktliste: MissionRepository, DroneProfileRepository, AppSettingsRepository - je mit Unit-Tests gegen eine In-Memory-Datenbank (NativeDatabase.memory()). Provider: CurrentMissionMetaNotifier haelt Name/id der aktuellen Mission und autosaved sie 800ms-debounced (identisch zum HTML-Demonstrator: scheduleAutosave()/flushPendingAutosave()) - inklusive Wiederherstellung des zuletzt bearbeiteten Autosave-Drafts beim App-Start (Doku 7.5). ActiveDroneProfileNotifier haelt das aktive Profil, seedet beim ersten Start automatisch T1 Ranger und merkt sich die Auswahl ueber AppSettingsTable neustart-fest. UI: MissionsDronesPanel (Missions-Tab: Liste mit Umbenennen/Loeschen/ Laden; Drones-Tab: Liste mit Bearbeiten/Loeschen/Auswaehlen, faellt beim Loeschen des aktiven Profils auf das naechste zurueck). DroneProfileEditor als Formular (bewusste Vereinfachung gegenueber den Swipe-Gesten-Chips des HTML-Demonstrators - bei elf Feldern ist ein normales Formular auf einem Mobilgeraet zugaenglicher). Rename per AlertDialog+TextField statt Browser-prompt(). Aktives Drohnenprofil ist jetzt tatsaechlich wirksam statt nur eine feste Anzeige: Speed-/Alt-Drehrad-Grenzen, Fangradius neuer Wegpunkte und die Flugpfad-Machbarkeitspruefung (Kurvenradius, Steig-/Sinkrate) in PlanScreen und WaypointListPanel lesen jetzt activeDroneProfileProvider statt der bisherigen statischen DefaultDroneProfile-Konstanten (die als T1-Ranger-Seed-Werte weiterleben). Beim Verifizieren zwei echte Bugs in BottomStatsBar gefunden und gefixt: ein Stack+Align-ohne-Factor blaehte sich auf unbegrenzte Groesse auf und verschob die Details-Pille aus ihrer sichtbaren Position (durch zwei gleich grosse Expanded-Bereiche ersetzt); zwei ConstrainedBox-Chips nebeneinander verursachten auf schmaleren Breiten einen RenderFlex- Overflow (durch Flexible ersetzt). Widget-Tests: AppShell/PlanScreen initialisieren beim Start jetzt die echte Datenbank - alle Tests, die AppShell pumpen, ueberschreiben appDatabaseProvider testweise mit einer In-Memory-Instanz. Ausserdem mussten mehrere Tests den neuen 800ms-Autosave-Timer abwarten (wie zuvor schon beim WaypointChip-Flash-Timer etabliert), damit nach Testende kein Timer mehr aussteht. Verifiziert: flutter analyze (0 issues), flutter test (59/59, davon 14 neue Repository-Tests), manuell auf Pixel_10a-Emulator - Wegpunkte werden automatisch als "New mission" gespeichert und erscheinen in der Liste, Umbenennen/Laden/Loeschen funktionieren, neues Drohnenprofil mit abweichender Max-Speed wird angelegt+aktiviert+in der Fusszeile angezeigt, Loeschen des aktiven Profils faellt auf das verbleibende zurueck. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3dd9c38fb1
commit
aaec7f0f72
@@ -9,8 +9,10 @@ import 'package:latlong2/latlong.dart';
|
||||
import '../../../domain/mission/default_drone_profile.dart';
|
||||
import '../../../domain/mission/route_geometry.dart';
|
||||
import '../../../domain/waypoint/flat_waypoint_list.dart';
|
||||
import '../../providers/active_drone_profile_provider.dart';
|
||||
import '../../providers/current_mission_provider.dart';
|
||||
import '../../providers/map_controller_provider.dart';
|
||||
import '../../providers/mission_meta_provider.dart';
|
||||
import '../../providers/wind_provider.dart';
|
||||
import '../../widgets/bottom_stats_bar.dart';
|
||||
import '../../widgets/halo_menu.dart';
|
||||
@@ -72,8 +74,28 @@ class _PlanScreenState extends ConsumerState<PlanScreen> {
|
||||
// Erstbefuellung der Kopfleisten-Wind-Pille (Doku 3.8) - erst nach dem
|
||||
// ersten Frame, da die Kartenkamera (Fallback-Referenzort ohne
|
||||
// Wegpunkte) vor dem initialen Layout noch nicht bereit ist.
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) => ref.read(headerWindProvider.notifier).refresh());
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
if (!mounted) return;
|
||||
ref.read(headerWindProvider.notifier).refresh();
|
||||
// Bootstrap der Persistenz (Doku 4.15/7.5): mindestens ein
|
||||
// Drohnenprofil sicherstellen/laden, danach ggf. den zuletzt
|
||||
// bearbeiteten Autosave-Draft wiederherstellen.
|
||||
await ref.read(activeDroneProfileProvider.notifier).initialize();
|
||||
await ref.read(currentMissionMetaProvider.notifier).restoreLastSession();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_curSpeed = ref.read(activeDroneProfileProvider).cruiseSpeed;
|
||||
});
|
||||
}
|
||||
});
|
||||
// Wechselt der Nutzer waehrend der Bearbeitung das aktive Drohnenprofil,
|
||||
// muss die aktuell eingestellte Soll-Geschwindigkeit in dessen
|
||||
// gueltigen Bereich geklemmt werden (sonst zeigt das Drehrad einen
|
||||
// Wert ausserhalb seiner eigenen Skala).
|
||||
ref.listenManual(activeDroneProfileProvider, (previous, next) {
|
||||
final clamped = _curSpeed.clamp(next.minSpeed, next.maxSpeed);
|
||||
if (clamped != _curSpeed) setState(() => _curSpeed = clamped);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -167,7 +189,7 @@ class _PlanScreenState extends ConsumerState<PlanScreen> {
|
||||
lon: center.longitude,
|
||||
altitudeM: _curAlt,
|
||||
speedMs: _curSpeed,
|
||||
catchRadiusM: DefaultDroneProfile.catchRadiusM,
|
||||
catchRadiusM: ref.read(activeDroneProfileProvider).catchRadius,
|
||||
),
|
||||
);
|
||||
if (waypoints.isEmpty) {
|
||||
@@ -325,12 +347,13 @@ class _PlanScreenState extends ConsumerState<PlanScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
final waypoints = ref.watch(currentMissionProvider);
|
||||
final showPerWaypointWind = ref.watch(headerWindProvider).showPerWaypoint;
|
||||
final droneProfile = ref.watch(activeDroneProfileProvider);
|
||||
|
||||
final routeGeometry = buildRouteGeometry(
|
||||
waypoints,
|
||||
minTurnRadius: DefaultDroneProfile.minTurnRadius,
|
||||
maxClimbRate: DefaultDroneProfile.maxClimbRate,
|
||||
maxDescentRate: DefaultDroneProfile.maxDescentRate,
|
||||
minTurnRadius: droneProfile.minTurnRadius,
|
||||
maxClimbRate: droneProfile.maxClimbRate,
|
||||
maxDescentRate: droneProfile.maxDescentRate,
|
||||
);
|
||||
final missionStats = _missionStats(waypoints);
|
||||
final anyBad = routeGeometry.vertexBad.any((bad) => bad) ||
|
||||
@@ -414,7 +437,7 @@ class _PlanScreenState extends ConsumerState<PlanScreen> {
|
||||
child: ValueWheel(
|
||||
value: _curAlt,
|
||||
min: DefaultDroneProfile.altMin,
|
||||
max: DefaultDroneProfile.altMax,
|
||||
max: droneProfile.maxAltitudeM,
|
||||
step: DefaultDroneProfile.altStep,
|
||||
pxPerStep: _altPxPerStep,
|
||||
majorEvery: _altMajorEvery,
|
||||
@@ -433,8 +456,8 @@ class _PlanScreenState extends ConsumerState<PlanScreen> {
|
||||
right: 14 + safeEdgeInset,
|
||||
child: ValueWheel(
|
||||
value: _curSpeed,
|
||||
min: DefaultDroneProfile.speedMin,
|
||||
max: DefaultDroneProfile.speedMax,
|
||||
min: droneProfile.minSpeed,
|
||||
max: droneProfile.maxSpeed,
|
||||
step: DefaultDroneProfile.speedStep,
|
||||
pxPerStep: _speedPxPerStep,
|
||||
majorEvery: _speedMajorEvery,
|
||||
|
||||
Reference in New Issue
Block a user