Commit Graph
65 Commits
Author SHA1 Message Date
Constantin Leue c7e4e5bf63 fixed drone status menue grid view bug. fields are shown again 2026-08-05 21:11:28 +02:00
Constantin Leue 4982341203 Warning thresholds added in drone profile. traffic light indication in drone satus menue added. layout and content optimized 2026-08-05 20:15:18 +02:00
Constantin Leue f65dceb338 connection settings cleanup and mock test added. removed bluetooth implementation 2026-08-05 20:01:25 +02:00
Constantin Leue 8e4df2d0d3 drone status menue with status and critical event log implemented 2026-08-04 23:15:56 +02:00
Constantin Leue 75736793f2 optimizing footer optics by unifying icon and text size 2026-08-04 16:08:45 +02:00
Constantin Leue 1a17fe5b36 drone status pill in footer for fly mode implemented. footer layout and sizes homogenized 2026-08-04 15:47:53 +02:00
Constantin Leue f2f2953642 battery indicator optical tweak 2026-08-04 14:48:07 +02:00
Constantin Leue 98c4d7e270 battery indicator added to the footer 2026-08-04 11:11:45 +02:00
Constantin Leue 66e05f0ffe warning banner removed from footer in fly mode 2026-08-04 10:20:16 +02:00
Constantin Leue a66c199b37 live mission details in footer implemented 2026-08-04 09:48:01 +02:00
Constantin LeueandClaude Sonnet 5 35351140ae Persist the ground elevation profile with the mission
The Mini-Altitude-Profile terrain data only ever lived in TerrainNotifier's
in-memory state (one slot, keyed by route). Every app restart, or every
switch away from and back to a mission, forced a full re-fetch of all
AWS Terrarium elevation tiles for the route - the noticeably slow load
the user reported for some missions was this happening on every visit,
not just once.

Adds a nullable terrainProfileJson column to the missions table
(schema v6->v7) and a saveTerrainProfile() repository method, kept
separate from the regular waypoint upsert() so an ordinary autosave never
clobbers an already-cached profile. TerrainNotifier persists a profile
right after a successful fetch (fire-and-forget) and gains restore(),
called from CurrentMissionMetaNotifier whenever a mission is loaded/
started so a previously fetched profile is available immediately -
ensureFor() still validates its routeKey before use, so a stale restored
profile is never shown for a route that has since changed.

Also included in mission export/import (MissionExportData/
ParsedMissionImport) so sharing a mission carries its terrain cache along
instead of forcing the recipient to refetch it.

Sample-count/resolution stays as-is for now (still fixed 30m spacing,
10-2000 samples) - adapting the sampling density to terrain variance
(e.g. coarser sampling over flat ground) is a separate follow-up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 08:10:41 +02:00
Constantin Leue 4e94102861 host address retrieving through assigned subnet information and broadcast message to learn host address 2026-08-03 23:10:11 +02:00
Constantin Leue 4671b34f5b follow mode behaviour tuned: load mission and jump to wayoinnt cancels follow mode 2026-08-03 22:39:45 +02:00
Constantin Leue ba14af8469 follow mode button, zoom mode and abort behavior tuning 2026-08-03 22:29:22 +02:00
Constantin Leue 2f4353f3fa map follow on drone focus. rubberband pointing to next waypoint. drone icon bigger 40px 2026-08-03 22:07:11 +02:00
Constantin Leue 7b198e1755 wake lock implemented 2026-08-03 21:26:36 +02:00
Constantin Leue 5dfd28c56b fit map bug behoben fuer plan modus 2026-08-02 22:23:49 +02:00
Constantin Leue 2d7c50e030 fixed peer learning from first package (race condition) 2026-08-02 22:09:24 +02:00
Constantin Leue 0649ae4507 disabled auto connect when switching to fly mode and no disconnect when leavin fly mode, race condition fix for connectionType ( now telemetry not working anymore), UI optimizations: centered speed and alt, zoom to mission uses full screen 2026-08-02 21:45:46 +02:00
Constantin Leue 35c4129eb7 add telemetry messages: rx link quality, battery percentage, flight mode, relative altitude 2026-08-02 21:09:33 +02:00
Constantin Leue 11f894919b telemetry fix gate for telemetry stream removed 2026-08-02 20:44:20 +02:00
Constantin Leue f42d997d4c UI improvements for telemetry debugging 2026-08-02 20:21:21 +02:00
Constantin Leue 6cfb82bd77 msp over wifi implemented 2026-08-02 09:20:05 +02:00
Constantin Leue 24db38dc33 settings ui clean up 2026-08-02 08:45:24 +02:00
Constantin Leue 607dbb6584 autoconnect on fly mode (does not work yet) 2026-08-02 08:25:23 +02:00
Constantin Leue 5e9ef43f2f udp socket binding istead of process binding to allow internet over network for maps etc 2026-07-31 20:49:08 +02:00
Constantin Leue 3b3451b8a2 wifi connection established by app, process socket binding, primary os connection untouched 2026-07-31 15:25:45 +02:00
Constantin Leue 222d8743fb udp package timeout changed to 4s 2026-07-31 11:04:27 +02:00
Constantin Leue ee97a7cb9a wifi udp stream activity indicator added 2026-07-31 10:50:41 +02:00
Constantin Leue cb91436fe4 UI collapsable height profile, english settings menue 2026-07-31 09:49:28 +02:00
Constantin Leue 8cc9a51a34 WIFI connection support added 2026-07-31 08:17:12 +02:00
Constantin Leue b28b1a89bb settings menu UI adjustment 2026-07-30 22:19:47 +02:00
Constantin Leue 2ce1444b90 UI adjustments 2026-07-30 21:34:48 +02:00
Constantin Leue 078d58dbc0 MSP protocol implementation and settings menu 2026-07-30 21:18:48 +02:00
Constantin Leue 5994aee7ad bluetooth connectivity layer added 2026-07-30 19:46:52 +02:00
Constantin LeueandClaude Sonnet 5 310d92f187 Show mission map and footer in Fly mode, add area/drone zoom buttons
Fly mode previously showed just a black placeholder. It now renders the
same MissionMap (route + waypoint markers) and footer (warnings banner,
altitude profile, BottomStatsBar) as the Plan screen, minus the
reticle/wheels since flying observes the route rather than replanning it.
Waypoints can still be edited via the details list for spontaneous
in-flight changes.

Live drone position comes from a new telemetryProvider/
flightControllerLinkProvider pair (autoDispose), the first place
FlightControllerLink/TelemetryFrame get wired into the UI - currently
backed by MockFlightControllerLink until a real MSP transport exists
(Doku 4.19). A drone marker renders on the map once a telemetry frame
arrives.

The Fly-mode header keeps the fit-to-area button but replaces "zoom to
home" with "zoom to drone" (FlyMapControls) - the first waypoint isn't a
meaningful reference point anymore once airborne.

Extracted computeMissionStats and MissionFooterBar out of PlanScreen so
Plan/Fly share the exact same stats/footer logic instead of duplicating
it, and extracted NavIconButton out of MapSearchControls so both Plan's
and Fly's map controls use the same button widget.

Widget tests that now mount FlyScreen switch from the ProviderScope-based
_wrap() helper to a manual ProviderContainer with an explicit
FlightControllerLink.disconnect() call before test end - the mock's
Timer.periodic doesn't get cancelled by container disposal alone, and
flutter_test's pending-timer check runs before addTearDown callbacks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 07:45:36 +02:00
Constantin LeueandClaude Sonnet 5 d745b76403 Remove Ready-to-Fly-Gate, add fly-mode header with flight-mode dropdown
Ready-to-Fly-Gate (Doku 2.2/4.5): AppModeCubit.toFly() erzwang bisher
Upload + verifiziert + disarmed und warf sonst einen StateError - da
MissionSyncService/Upload-Flow noch nicht angebunden sind, war das Gate
dauerhaft geschlossen und der Fly-Modus damit von der UI aus gar nicht
erreichbar. toFly() wechselt jetzt unbedingt in den Fly-Modus; die
readyToFly-Logik bleibt als Getter erhalten fuer den spaeteren Upload-
Flow. Der eigentliche Schutz vor einem Missions-Upload im armed-Zustand
lebt unveraendert auf Transport-Ebene (MockFlightControllerLink.
uploadMission() wirft dort weiterhin bei armed).

Fly-Modus-Kopfzeile (Doku 3.11/7.2, HTML-Demonstrator: #flightModePill/
#flightModeDropdown): top_mode_bar.dart zeigte im Fly-Modus bisher nur
eine leere Spacer-Flaeche. Zeigt jetzt die Wind-Pille (weiterhin
relevant, Doku 7.2) und eine neue Flugmodus-Auswahl:

- ui/providers/flight_mode_provider.dart: haelt den lokal gewaehlten
  FlightMode (Doku 3.1: missionRun/guidedPoint/returnHome/hold), noch
  ohne echte FlightControllerLink-Anbindung (Doku 4.19). reset() setzt
  auf missionRun (Waypoint) zurueck.
- ui/widgets/flight_mode_pill.dart: PopupMenuButton-Pille "Mode: X" mit
  den vier Optionen Waypoint/Point and Fly/Return to Home/Manual (1:1
  aus dem HTML-Demonstrator uebernommene Labels).
- top_mode_bar.dart: Fly-Button setzt beim Eintritt in den Fly-Modus
  den Flugmodus zurueck auf Waypoint (Doku 3.11: "Standard-
  Rueckstellung ... beim Eintritt in Fly-Modus").

Layout-Bug beim Implementieren gefunden und behoben: Expanded(child:
Center(child: FlightModePill())) liess die Kopfzeile ueber den
gesamten Bildschirm expandieren (derselbe "Center/Align ohne Faktor
expandiert auf verfuegbare Constraints"-Fehler wie zuvor schon bei der
Bottom-Stats-Bar in dieser Session) - behoben durch Entfernen des
ueberfluessigen Center-Wrappers, da FlightModePill sein Zentrieren
bereits selbst per Container-alignment uebernimmt. Zusaetzlich einen
RenderFlex-Overflow bei langen Modusnamen ("Return to Home") behoben,
indem der Label-Text in Flexible mit TextOverflow.ellipsis gewrappt
wurde.

Getestet: 2 neue Widget-Tests (Fly-Button wechselt jetzt direkt in den
Fly-Modus statt eine Gate-Snackbar zu zeigen; Flugmodus-Dropdown
wechselt den Modus und setzt ihn beim erneuten Eintritt zurueck),
bestehender Gate-Test ersetzt, ein bestehender Test vereinfacht (die
Gate-Erfuellung vor toFly() ist nicht mehr noetig). Alle 118 Tests
sowie flutter analyze bestehen. Manuell auf dem Pixel_10a-Emulator
verifiziert: Fly-Button wechselt direkt um, Dropdown zeigt alle vier
Optionen, Auswahl uebernimmt das Label korrekt (auch bei langen Namen
ohne Overflow), Rueckstellung auf Waypoint bei erneutem Eintritt
funktioniert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 07:15:15 +02:00
Constantin Leue 07394f79c8 adaptive launcher icons (not working) 2026-07-30 06:43:34 +02:00
Constantin LeueandClaude Sonnet 5 2f697f1356 Point wind arrow into the wind (standard meteorological convention)
WindArrowIcon zeigte bisher windabwaerts (Fliessrichtung der Luft),
gedreht um (dirFrom + 180) Grad - 1:1 uebernommen aus windArrowSvg() im
HTML-Demonstrator. Die Standardkonvention bei Wetterfahnen und
Windbarben auf Wetterkarten ist jedoch, dass der Pfeil windaufwaerts
zeigt, also dorthin, woher der Wind kommt. Dreht jetzt direkt um
dirFromDeg statt (dirFromDeg + 180) - betrifft zentral alle Verwendungen
(Kopfleisten-Windpille, Wegpunktlisten-Zeile, Windmarker auf der Karte),
da diese alle nur dirFromDeg durchreichen.

Weicht damit bewusst vom HTML-Demonstrator ab (dort weiterhin
Fliessrichtung) - im Code-Kommentar dokumentiert.

flutter analyze und alle 117 Tests bestehen unveraendert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 06:02:35 +02:00
Constantin LeueandClaude Sonnet 5 f25c312697 Add swipe gestures to drone profile editor (vertical=big, horizontal=small step)
Ersetzt die einfachen Zahlen-TextFormFields im Drohnenprofil-Editor durch
tastaturlose Swipe-Chips (Doku 3.5, HTML-Demonstrator: SwipeValueInput) -
vertikales Ziehen aendert den Wert in grossen Schritten (stepBig),
horizontales Ziehen in kleinen Schritten (stepSmall). Die Zugrichtung wird
beim ersten Ueberschreiten einer 8px-Schwelle einmalig festgelegt und
bleibt fuer den Rest der Geste gesperrt, damit eine leicht schraege Geste
nicht zwischen den beiden Schrittweiten hin- und herspringt. Min/Max/
Schrittweiten je Feld 1:1 aus DRONE_EDITOR_GROUPS im HTML-Demonstrator
uebernommen. Zusaetzlich ein "modified"-Punkt neben dem Label, sobald ein
Wert vom Ausgangswert beim Oeffnen des Editors abweicht (HTML: .modified-
dot/.chip-modified).

lib/ui/widgets/swipe_value_input.dart: neuer, wiederverwendbarer Chip.

Gesten-Konflikt mit der scrollbaren Formular-Liste (wichtigster Teil
dieser Aenderung): Die Chips sitzen in einer vertikal scrollbaren
ListView (elf Felder in Gruppen passen nicht auf einen Bildschirm). Ein
GestureDetector.onPan* auf dem Chip verlor dabei auf einem echten Geraet
durchgehend gegen die eigene Scroll-Geste der ListView - onPanStart/
onPanUpdate feuerten nie, weder fuer vertikale noch horizontale Zuege
(mit Debug-Prints auf dem Pixel_10a-Emulator verifiziert). Behoben durch:
- SwipeValueInput nutzt jetzt rohe Pointer-Events (Listener statt
  GestureDetector.onPan*) - diese werden unabhaengig vom Gesture-Arena-
  Ausgang immer zugestellt.
- Ein neuer onDragActiveChanged-Callback informiert das Elternwidget,
  solange eine Geste aktiv ist; DroneProfileEditor sperrt darueber die
  ListView (NeverScrollableScrollPhysics) fuer die Dauer des Ziehens, so
  dass die Liste waehrenddessen nicht mitscrollt.

Getestet: 7 Widget-Tests fuer SwipeValueInput (grosse/kleine Schritte,
Richtungswechsel, Klemmen auf min/max, Rundung auf Nachkommastellen,
"modified"-Punkt, sowie ein Regressionstest fuer exakt dieses Szenario -
Chip innerhalb einer scrollbaren ListView). Alle 117 Tests sowie flutter
analyze bestehen. Manuell auf dem Pixel_10a-Emulator verifiziert:
vertikaler Zug auf "Min (stall)" aendert den Wert in 10er-Schritten ohne
die Liste zu scrollen, horizontaler Zug auf "Cruise" in 1er-Schritten -
beide mit korrekt aufleuchtendem "modified"-Indikator.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 21:11:51 +02:00
Constantin LeueandClaude Sonnet 5 c5a8c68a8e Lock map rotation, add mission warnings list with per-issue navigation
Kartendrehung (Doku 4.6): flutter_map's Zwei-Finger-Twist-Geste bewusst
deaktiviert (InteractionOptions mit InteractiveFlag.all & ~rotate in
mission_map.dart) - die Drohne fliegt nordorientiert, eine gedrehte Karte
wuerde die Peilung von Wegpunkten/Reticle nur verwirren, zumal der HTML-
Demonstrator gar keine Drehung kennt.

Warnungsliste (Doku 3.10), analog dem #warningsBtn/#warningsPanel-Flow im
HTML-Demonstrator:

- domain/mission/mission_warnings.dart: computeMissionWarnings() sammelt
  alle aktuell zutreffenden Probleme (Route-Geometrie ausserhalb der
  Drohnenfaehigkeiten, zu starker Wind, zu geringer Bodenabstand,
  Reichweite/Ausdauer ueberschritten) zu einer Liste aus {text, action}-
  Eintraegen - reine Funktion aus bereits vorhandenen Zwischenergebnissen,
  unabhaengig von Riverpod/Widgets testbar.
- domain/wind/wind_math.dart: computeLegWindBad() ergaenzt (Bodenge-
  schwindigkeit <= 1 m/s oder Windgeschwindigkeit > Drohnen-Maximum) -
  bislang gab es nur die Windkomponente/Bodengeschwindigkeit selbst, aber
  keine "zu stark"-Markierung.
- ui/widgets/warnings_panel.dart: Vollbild-Liste, jede Zeile verweist per
  Aktionslabel auf den Loesungs-Screen ("Open altitude view"/"Open speed
  view"/"Show on map").
- ui/widgets/bottom_stats_bar.dart: roter Warnungs-Button neben den
  Mission-/Drohnen-Chips, nur sichtbar solange Warnungen aktiv sind.
- ui/widgets/waypoint_list_panel.dart: `_PanelTab` zu `PanelTab` public
  gemacht plus neuer `initialTab`-Parameter, damit das Panel direkt auf
  dem Altitude- oder Speed-Tab geoeffnet werden kann statt immer auf der
  Liste zu starten.
- ui/providers/map_controller_provider.dart: fitMapToWaypoints()-Hilfs-
  funktion extrahiert und in MapSearchControls' Fit-Button (vorher eigene
  Kopie der Logik) sowie fuer die "Show on map"-Warnungsaktion
  wiederverwendet.
- ui/screens/plan/plan_screen.dart: berechnet die Warnungsliste, ersetzt
  den bisherigen Einzel-Banner (nur "Route exceeds...") durch alle
  aktuellen Warnungstexte (durch " · " getrennt, wie im HTML-Demonstrator)
  und verdrahtet Warnungs-Button/-Liste mit der passenden Navigation.

Getestet: 4 neue Unit-Tests fuer computeLegWindBad, 7 fuer
computeMissionWarnings (inkl. Grenzfaelle: veraltetes Gelaendeprofil,
kein Limit bei maxRangeM/maxEnduranceMin <= 0), 2 neue Widget-Tests
(Warnungs-Button erscheint bei Reichweiten-Ueberschreitung und oeffnet
die Liste; Antippen einer Wind-Warnung oeffnet den Speed-Tab direkt).
Alle 110 Tests sowie flutter analyze bestehen. Manuell auf dem
Pixel_10a-Emulator verifiziert: Steigraten-Ueberschreitung (Altitude auf
220m bei kurzer Distanz) macht Route/Banner/Warnungs-Button rot,
Warnungsliste zeigt den Eintrag mit "Show on map", Antippen schliesst
die Liste und zoomt die Karte korrekt auf die Bounding-Box beider
Wegpunkte.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 19:51:42 +02:00
Constantin Leue 0f16c0db12 flutter commands cheat sheet 2026-07-28 21:16:12 +02:00
Constantin Leue 0a9cc1d3c8 update with square icon 2026-07-28 21:11:06 +02:00
Constantin LeueandClaude Sonnet 5 b51c6c16d5 Add mission/drone profile sharing and importing, fix map not fitting to loaded mission
Sharing/Import (Doku 3.5/3.6), analog shareMission()/shareDrone() und den
importMissionInput/importDroneInput-Handlern im HTML-Demonstrator:

- services/sharing/mission_share_codec.dart, drone_share_codec.dart: reine
  Funktionen zum Bauen/Parsen des Export-JSON (appVersion/exportedAt-Umschlag),
  inkl. Erkennung einer abweichenden Hauptversion beim Import - vollstaendig
  unit-getestet ohne Plugin-Abhaengigkeit.
- services/database/waypoint_json_codec.dart: Waypoint-JSON-(De-)Serialisierung
  aus mission_repository.dart herausgezogen, damit Persistenz und Sharing
  exakt dasselbe Dateiformat verwenden statt es zu duplizieren.
- services/sharing/sharing_service.dart: duenne I/O-Schicht - schreibt eine
  temporaere Datei und oeffnet das native Share-Sheet (share_plus), bzw.
  liest eine vom Nutzer per Systemdialog ausgewaehlte JSON-Datei
  (file_picker). Als Klasse mit Instanzmethoden gehalten, damit sie sich in
  Tests durch einen Fake ersetzen laesst.
- ui/widgets/missions_drones_panel.dart: Share-Icon je Zeile, Import-Button
  im Toolbar beider Tabs.

Paket-Versionen bewusst gewaehlt: file_picker 11.0.2/share_plus 11.x wurden
zunaechst wegen einer win32-Konflikt-Aufloesung genutzt, kompilierten auf
diesem Projekt (AGP 9.0.1) aber nicht - file_picker < 12.0.0-beta.1 prueft
nur AGP-Version >= 9 und ueberspringt dann das Anwenden des Kotlin-Android-
Plugins, in der Annahme, AGPs eingebauter Kotlin-Support wuerde das
uebernehmen, was hier zu einem fehlenden compileReleaseKotlin-Task und
"Symbol nicht gefunden" fuehrte. file_picker >=12.0.0-beta.1 respektiert
zusaetzlich die bereits vom Flutter-Template gesetzte Gradle-Property
android.builtInKotlin=false und wendet das Plugin dann korrekt an -
file_picker auf ">=12.0.0-beta.1 <13.0.0" (share_plus zurueck auf ^13.3.0,
beide dann konsistent auf win32 ^6.x) gesetzt, um dies zu nutzen.

Zusaetzlich beim manuellen Durchtesten auf dem Pixel_10a-Emulator einen
zweiten, davon unabhaengigen Bug gefunden und behoben: CurrentMissionMeta-
Notifier.loadMission()/restoreLastSession() aktualisierten zwar Wegpunkte
und Missionsname, bewegten aber nie die Kartenkamera - eine geladene
Mission wurde dadurch mit falscher Kartenausschnitt angezeigt (Name/
Wegpunkte einer Stadt, Karte noch an der zuletzt betrachteten Stelle).
Fix: _fitMapToWaypoints() zentriert nach dem Laden auf die Bounding-Box
der Mission, analog der bereits vorhandenen _onFitPressed()-Logik in
MapSearchControls.

Getestet: 13 neue Unit-Tests fuer die Sharing-Codecs, 4 neue Widget-Tests
mit einem Fake-SharingService (kein echter Platform-Channel-Zugriff in
Tests). Alle 97 Tests sowie flutter analyze bestehen. Manuell auf dem
Pixel_10a-Emulator verifiziert: natives Share-Sheet oeffnet sich mit der
korrekten JSON-Datei, Datei-Import ueber den Systemdialog legt eine neue
Mission bzw. ein neues Drohnenprofil an, Kartensprung beim Laden einer
Mission funktioniert jetzt korrekt fuer sowohl manuelles Laden als auch
den Autosave-Restore beim App-Start.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 19:50:19 +02:00
Constantin LeueandClaude Sonnet 5 b176ad294e Add terrain/elevation data fetching (Doku 3.9)
Rundet das Altitude-Tab um ein reales Gelaendeprofil ab, damit sichtbar
wird, ob die geplante Flughoehe ausreichend Bodenabstand haelt:

- domain/mission/terrain_math.dart: reine Sampling-/Geometrie-Funktionen
  (routeKeyForTerrain fuer Caching, distanzbasiertes Sampling alle 30m,
  lineare Sollhoehen-Interpolation, terrainDangerRanges fuer Abschnitte
  mit < 10m Bodenabstand) - unabhaengig testbar ohne Netzwerk/UI.
- services/terrain/terrain_service.dart: laedt AWS-Terrarium-PNG-Kacheln
  (elevation-tiles-prod, zoom 13) und dekodiert sie ueber dart:ui
  (instantiateImageCodec/toByteData), ohne zusaetzliches Bildpaket.
  Elevation = R*256 + G + B/256 - 32768 pro Pixel; Kacheln werden pro
  Route nur einmal geladen (nach Kachel gruppierte Sample-Punkte).
- ui/providers/terrain_provider.dart: cached das geladene Profil ueber
  routeKeyForTerrain - reine Werteaenderungen (Hoehe/Speed) loesen keinen
  erneuten Kachel-Download aus, nur eine tatsaechliche Ortsverschiebung.
- ui/widgets/full_value_chart.dart: _paintDangerBands/_paintTerrain
  zeichnen rote Gefahrenbaender bzw. die braune Gelaende-Flaeche, in der
  gleichen Reihenfolge wie im HTML-Demonstrator (dangerHtml, grid,
  terrainHtml, dann Soll-Kurve obenauf).
- ui/widgets/waypoint_list_panel.dart: triggert ensureFor() beim
  Wechsel auf den Altitude-Tab sowie bei Routenaenderungen waehrend
  dieser aktiv ist (ref.listenManual auf currentMissionProvider).

Getestet: 13 neue Unit-Tests fuer die reine Geometrie/Sampling-Logik,
3 Service-Tests mit einer zur Testzeit synthetisch erzeugten PNG
(dart:ui-Encoding, keine Testasset-Datei noetig). Alle 80 Tests sowie
flutter analyze bestehen. Manuell auf dem Pixel_10a-Emulator verifiziert:
Altitude-Tab zeigt das reale Amsterdam-Gelaendeprofil (nahe Meereshoehe)
korrekt als Flaeche unter der Sollhoehen-Linie.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 18:38:23 +02:00
Constantin LeueandClaude Sonnet 5 5505bb3741 Fix wind marker slack and off-center header bar
Zwei vom Nutzer per Screenshot gemeldete Probleme:

1. Der Wind-Marker auf der Karte liess bei kurzen Werten (z.B. "1 m/s")
   weiterhin sichtbaren Leerraum rechts neben dem Text - die letzte
   Anpassung hatte die Marker-Box auf den unguenstigsten Fall
   (zweistellig, "25 m/s") fixiert bemessen, wodurch einstellige Werte
   in derselben Box zu viel Platz hatten. flutter_map's Marker verlangt
   eine feste Breite (kein intrinsisches Sizing), also wird sie jetzt
   pro Wegpunkt aus der Ziffernzahl der Geschwindigkeit berechnet
   (+11px/Ziffer, per Widget-Messung ermittelt) statt eine einzelne
   Konstante fuer alle Faelle zu verwenden.

2. Die Kopfleiste sass nicht mittig, sondern wirkte nach rechts
   verschoben. Ursache: SafeArea wendet links/rechts die jeweils
   tatsaechlichen (auf diesem Emulator unterschiedlich grossen)
   Insets an - dadurch war der fuer FractionallySizedBox verfuegbare
   Bereich selbst schon asymmetrisch zur Bildschirmmitte verschoben.
   Ersetzt durch denselben symmetrischen Randabstand (max(links,
   rechts)), der in PlanScreen fuer die Drehraeder bereits etabliert ist.

Verifiziert: flutter analyze (0 issues), flutter test (64/64), manuell
auf Pixel_10a-Emulator - Kopfleiste jetzt sichtbar mittig, mehrere
Wind-Marker mit "1 m/s" zeigen keinen Leerraum mehr.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 15:53:42 +02:00
Constantin LeueandClaude Sonnet 5 926d805156 Location search starts a new mission; shrink and center header bar
Ortssuche verschob bisher nur die Karte - der HTML-Demonstrator beginnt
bei einer Suche immer eine neue, nach dem gefundenen Ort benannte
Mission (setMissionFromPlace(), aufgerufen aus doSearch()). Vorherige
Aenderungen gehen dabei nicht verloren, da flushPendingAutosave()
(hier: CurrentMissionMetaNotifier._flushNow()) zuerst noch ausstehende
Autosaves schreibt.

Die Nominatim-Suche wurde dafuer aus MapSearchControls in einen
eigenstaendigen NominatimService extrahiert (analog WindService):
injectable http.Client, damit sich die Suche in Tests ohne echten
Netzwerkzugriff ueberschreiben laesst. CurrentMissionMetaNotifier bekam
dafuer startNewFromPlace() (startNew() intern darauf umgebaut, um
Duplikation zu vermeiden).

Kopfleiste von 86%/64% (Plan-/Fly-Modus) auf einheitlich 70%
Bildschirmbreite verkleinert - zentriert war sie durch Align(topCenter)
+ FractionallySizedBox bereits strukturell korrekt, wirkte bei der
vollen Breite aber unausgewogen.

Verifiziert: flutter analyze (0 issues), flutter test (64/64, davon 4
neue NominatimService-Tests und 1 neuer Widget-Test fuer den
Missions-Reset bei Ortssuche), manuell auf Pixel_10a-Emulator - Suche
nach "Rotterdam" setzt Fusszeile auf "Mission: Rotterdam" mit 0
Wegpunkten trotz zuvor bestehender Mission mit Wegpunkten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 15:22:36 +02:00
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
Constantin LeueandClaude Sonnet 5 3dd9c38fb1 Fit wind marker pill box tightly to its content
Der Wind-Marker auf der Karte sass in einer flutter_map Marker-Box mit
fester width/height (100x26), die dem Container tighte Constraints
vorgibt - dadurch fuellte die Pille (Hintergrund/Rand) diese Box exakt
aus, auch wenn Icon+Text (bei font11) natuerlich nur ca. 90-101x21
brauchen. Der ueberschuessige Platz erschien als sichtbar zu grosse/zu
lange Pille.

Feste Groesse jetzt an der tatsaechlich benoetigten Groesse ausgerichtet
(per Widget-Messung ermittelt: 90x21 bei einstelliger, 101x21 bei
zweistelliger m/s-Anzeige) statt geschaetzt grosszuegig bemessen -
Schriftgroesse/Icon-Groesse selbst unveraendert (11/15, wie urspruenglich
implementiert).

Verifiziert: flutter analyze (0 issues), flutter test (45/45), manuell
auf Pixel_10a-Emulator - Pille umschliesst Pfeil+Text jetzt ohne
sichtbaren Leerraum.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 14:02:45 +02:00
Constantin LeueandClaude Sonnet 5 ed333229c1 Add per-waypoint wind markers on the map
Vervollstaendigt den in der letzten Session zurueckgestellten Teil des
Wind-Features (Doku 3.8): die Kopfleisten-Pille toggelt jetzt tatsaechlich
sichtbare Wind-Marker an jedem Wegpunkt, statt nur ihren eigenen Zustand
zu halten (HTML-Demonstrator: showPerWaypointWind steuerte im Original
direkt die divIcon-Marker in redrawMapLayers()).

WindMarkerPill (neuer Widget) zeigt Richtungspfeil + Geschwindigkeit in
einer kleinen dunklen Pille. MissionMap bekommt dafuer einen eigenen
windMarkers-Parameter (flutter_map MarkerLayer, gesondert von den
bestehenden CircleMarker-Wegpunkten, da MarkerLayer echte Widgets statt
nur CustomPaint-Kreise rendert). PlanScreen baut die Liste aus
ref.watch(headerWindProvider).showPerWaypoint plus den bereits per
"Fetch wind" geladenen Wegpunkt-Winddaten.

Zwei Dinge beim Verifizieren gefunden und gefixt:
- Marker-Groesse (width/height) war zu knapp bemessen und liess die
  Pille per RenderFlex-Overflow abschneiden - vergroessert und die
  Verankerung entsprechend angepasst.
- Der neue Widget-Test placierte den Test-Wegpunkt zunaechst ausserhalb
  des sichtbaren Kartenausschnitts: anders als CircleLayer rendert
  flutter_map's MarkerLayer nur Marker innerhalb der aktuellen Viewport-
  Bounds. Wegpunkt jetzt am Kartenzentrum (MissionMap._initialCenter)
  platziert.

Verifiziert: flutter analyze (0 issues), flutter test (45/45, ein neuer
Test fuer Marker-Sichtbarkeit vor/nach Antippen der Pille), manuell auf
Pixel_10a-Emulator - drei Wegpunkte mit geladenem Wind zeigen nach
Antippen der Pille je eine Wind-Anzeige, verschwinden beim erneuten
Antippen wieder.

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