Files
dmc/app/lib/domain/mission/drone_profile.dart
T
Constantin LeueandClaude Sonnet 5 aaec7f0f72 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>
2026-07-28 14:59:09 +02:00

97 lines
3.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// Ein Drohnen-Profil mit Flugleistungsdaten (Architektur-Doku 3.5/7.3):
/// FC-Typ ist bewusst NICHT Teil dieses MVP-Profils (nur iNAV/MSP im
/// Scope, siehe CLAUDE.md) - anders als im HTML-Demonstrator, der bereits
/// auf MAVLink vorbereitet.
class DroneProfile {
const DroneProfile({
this.id,
required this.name,
required this.minSpeed,
required this.cruiseSpeed,
required this.maxSpeed,
required this.minTurnRadius,
required this.loiterRadius,
required this.catchRadius,
required this.maxClimbRate,
required this.maxDescentRate,
required this.maxRangeM,
required this.maxEnduranceMin,
required this.maxAltitudeM,
});
/// Null fuer ein noch nicht gespeichertes (neu angelegtes) Profil.
final int? id;
final String name;
final double minSpeed;
final double cruiseSpeed;
final double maxSpeed;
final double minTurnRadius;
final double loiterRadius;
final double catchRadius;
final double maxClimbRate;
final double maxDescentRate;
final double maxRangeM;
final double maxEnduranceMin;
final double maxAltitudeM;
/// T1 Ranger (Heewing) - Standardprofil (Doku 3.5), analog DEFAULT_DRONE
/// im HTML-Demonstrator.
static const _t1RangerName = 'T1 Ranger';
factory DroneProfile.t1Ranger() => const DroneProfile(
name: _t1RangerName,
minSpeed: 13,
cruiseSpeed: 15,
maxSpeed: 25,
minTurnRadius: 35,
loiterRadius: 100,
catchRadius: 60,
maxClimbRate: 5,
maxDescentRate: 6,
maxRangeM: 15000,
maxEnduranceMin: 45,
maxAltitudeM: 2000,
);
/// Einzeilige Zusammenfassung fuer die Profilliste (Doku 3.5), analog
/// droneSummary() im HTML-Demonstrator.
String get summary {
final rangeKm = (maxRangeM / 100).round() / 10;
return '$minSpeed–$cruiseSpeed–$maxSpeed m/s · R${minTurnRadius.round()}m · '
'↑${maxClimbRate.round()}/↓${maxDescentRate.round()} m/s · ${rangeKm}km · '
'${maxEnduranceMin.round()}min · ceil ${maxAltitudeM.round()}m';
}
DroneProfile copyWith({
int? id,
String? name,
double? minSpeed,
double? cruiseSpeed,
double? maxSpeed,
double? minTurnRadius,
double? loiterRadius,
double? catchRadius,
double? maxClimbRate,
double? maxDescentRate,
double? maxRangeM,
double? maxEnduranceMin,
double? maxAltitudeM,
}) {
return DroneProfile(
id: id ?? this.id,
name: name ?? this.name,
minSpeed: minSpeed ?? this.minSpeed,
cruiseSpeed: cruiseSpeed ?? this.cruiseSpeed,
maxSpeed: maxSpeed ?? this.maxSpeed,
minTurnRadius: minTurnRadius ?? this.minTurnRadius,
loiterRadius: loiterRadius ?? this.loiterRadius,
catchRadius: catchRadius ?? this.catchRadius,
maxClimbRate: maxClimbRate ?? this.maxClimbRate,
maxDescentRate: maxDescentRate ?? this.maxDescentRate,
maxRangeM: maxRangeM ?? this.maxRangeM,
maxEnduranceMin: maxEnduranceMin ?? this.maxEnduranceMin,
maxAltitudeM: maxAltitudeM ?? this.maxAltitudeM,
);
}
}