Commit Graph
10 Commits
Author SHA1 Message Date
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 f42d997d4c UI improvements for telemetry debugging 2026-08-02 20:21:21 +02:00
Constantin Leue 607dbb6584 autoconnect on fly mode (does not work yet) 2026-08-02 08:25:23 +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 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 1b13c45cac Add wind analysis: header pill, fetch/validate menu, speed-plot overlay
Portiert das Wind-System des HTML-Demonstrators (Doku 3.8) vollstaendig:
Open-Meteo-Winddaten, interpoliert zwischen Druckflaechen- und festen
Nabenhoehen-Stuetzpunkten auf die tatsaechliche Zielhoehe.

Domain (lib/domain/wind/wind_math.dart, reines Dart): Hoehen-
Interpolation (linear + zirkulaer fuer Windrichtung), Peilung zwischen
zwei Punkten, Kopf-/Rueckenwind-Komponente pro Flugleg - 1:1 uebernommen
aus interpolateWindAtHeight()/bearingBetween()/headTailwindComponent()
des Demonstrators, per Unit-Tests abgesichert.

Service (lib/services/wind/wind_service.dart): zwei getrennte,
fokussierte Open-Meteo-Requests statt eines kombinierten (Doku 4.10 -
ein 35-Variablen-Request verursachte im Demonstrator Modellauswahl-
Fehler). Drei Verbraucher: Kopfleisten-Pille (120 m AGL ueber
Referenzort), gebuendelter Wegpunkt-Fetch (ein Request fuer alle
Koordinaten via Open-Meteos Mehrfach-Location-Syntax) und das
Validierungspanel (rohe Messwerte je Nabenhoehe/Druckflaeche). Per
MockClient getestet (kein echter Netzwerkzugriff in Tests).

UI:
- HeaderWindPill (TopModeBar, nur ausserhalb Fly-Modus): zeigt
  Windrichtung/-geschwindigkeit, Antippen schaltet perspektivisch die
  Wind-Marker der Wegpunkte um (Zustand vorbereitet, HTML-Demonstrator:
  showPerWaypointWind).
- WaypointListPanel: "Fetch wind"/"Validate"-Buttons im Panel-Header,
  neue WIND-Spalte in der Liste.
- WindValidatePanel: neue Vollbild-Route (gleiches Muster wie
  WaypointListPanel) mit Rohdaten je Hoehenstufe inkl. Boeen (Doku 4.9:
  Boeen nur hier, nicht in der Wegpunktanzeige) und Interpolationsergebnis.
- FullValueChart (Speed-Tab): Kopf-/Rueckenwind-Diamanten pro Leg samt
  gestrichelter Nulllinie sowie gestrichelte Bodengeschwindigkeits-
  Stufenlinie.

Bug gefunden und gefixt waehrend der Emulator-Verifikation: die
Y-Achsen-Autoskalierung klemmte weiterhin auf den gueltigen Drehrad-
Wertebereich (13-25 m/s), wodurch nahe Null liegende Windkomponenten
(bei schwachem Wind der Normalfall) ausserhalb des sichtbaren Bereichs
lagen und weder Diamanten noch Nulllinie zu sehen waren. Der HTML-
Demonstrator klemmt computeSpeedChartRange() bewusst nicht auf SPD_MIN/
SPD_MAX; das Klemmen in FullValueChart._range() jetzt entsprechend
entfernt.

Waypoint-Modell um windSpeedMs/windDirFromDeg/windElevationM erweitert
(nullable, reine Planungshilfe - der Encoder liest diese Felder nie,
Doku 3.2). currentMissionProvider bekommt replaceAll() fuer den
gebuendelten Wind-Fetch, der alle Wegpunkte gleichzeitig aktualisiert.

Verifiziert: flutter analyze (0 issues), flutter test (44/44, davon 14
neue Wind-Mathematik- und 6 neue WindService-Tests), manuell auf
Pixel_10a-Emulator mit echtem Open-Meteo-Netzwerkzugriff - Kopfleisten-
Pille zeigt Live-Wind, Fetch/Validate fuellen Liste bzw. oeffnen das
Validierungspanel mit echten Messwerten, Speed-Plot zeigt nach dem Fix
Diamanten/Nulllinie/Bodengeschwindigkeit korrekt skaliert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 13:32:15 +02:00
Constantin LeueandClaude Sonnet 5 13da935f99 Merge search/home/fit controls into the Plan/Fly header pill
Suche, Home- und Fit-Button sassen bisher in einer eigenen Leiste unter
der Plan/Fly-Kopfleiste - ein reiner UI-Kompromiss, weil die Karte
(inkl. MapController) in PlanScreen lebte und von der aeusseren
Kopfleiste aus nicht erreichbar war. Der HTML-Demonstrator zeigt
Suche/Home/Fit dagegen als Mittelteil derselben Leiste wie die
Modus-Buttons (#searchWrap/#homeBtn/#fitBtn in #topBarMiddle) - das war
also keine Stilfrage, sondern eine Datenfluss-Frage.

Loesung: MapController aus einem PlanScreen-privaten Feld in einen
app-lebenslangen mapControllerProvider (Riverpod) gehoben. Damit kann
die neue MapSearchControls (ersetzt TopNavBar) direkt in TopModeBar
eingebettet werden und lebt architektonisch dort, wo sie hingehoert:
bei der Kartensteuerung, nicht als Kind von PlanScreen.

Als Nebeneffekt - tatsaechlich der wichtigere Punkt - bleibt die
Kamera-Position/Zoom jetzt auch beim Wechsel Plan -> Fly -> Plan
erhalten. Verifiziert per flutter_map-Quellcode (FlutterMap haengt
einen extern uebergebenen Controller beim Neu-Mounten nicht an und
disposed ihn nicht) sowie per neuem Widget-Test, der den Cubit direkt
durch das Ready-to-Fly-Gate schickt (der Fly-Modus ist ueber die UI
aktuell nicht erreichbar, da Upload/Verify noch nicht implementiert
ist) und die Kamera vor/nach dem Wechsel vergleicht.

Zusaetzlich: _onMapEvent unterscheidet jetzt per MapEventMove.source,
ob eine Kartenbewegung programmatisch (mapController, z.B. durch Suche/
Home/Fit) oder per Geste ausgeloest wurde, und triggert den
Snap-to-Edit-Handler entsprechend nur bei Gesten bzw. sofort bei
programmatischen Moves - das ersetzt den bisherigen Ansatz, an jeder
Aufrufstelle manuell _handleMoveEnd() zu rufen, der nicht mehr skaliert
sobald diese Aufrufstellen (wie jetzt) in einem anderen Widget liegen.

Verifiziert: flutter analyze (0 issues), flutter test (22/22, inkl.
neuem Kamera-Persistenz-Test), manuell auf Pixel_10a-Emulator (Release-
Build) - Suche/Home/Fit erscheinen fusioniert in der gruenen Pille,
Kartenverschiebung bleibt nach Wechsel in den (durch das Gate weiterhin
blockierten) Fly-Modus sichtbar unveraendert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 09:37:22 +02:00
Constantin LeueandClaude Sonnet 5 fc7d014fda Add top bar for switching between Plan and Fly mode
- TopModeBar widget: persistent pill-shaped header (HTML prototype:
  #topBarWrap/#editModeBtn/#flyModeBtn) tinted in the active mode's color,
  with the inactive mode offered as a solid button in the corner to switch
  into it.
- AppShell now provides a single shared Scaffold + Stack, overlaying
  TopModeBar on top of whichever screen (Plan/Fly) is active, instead of
  each screen owning its own Scaffold.
- Wired the Fly button to AppModeCubit.toFly(), which enforces the
  Ready-to-Fly gate (doc 4.5). Since upload/verify isn't wired up yet, the
  gate is never satisfied yet, so tapping Fly now shows a SnackBar instead
  of throwing an uncaught StateError.
- Extended the widget test to cover both the bar's presence and the
  gate-rejection path (was the source of a real bug: an initial
  negative-margin Container hack for the "bleed into the corner" look
  violated Container's margin.isNonNegative assertion and crashed the
  whole tree - fixed with padding instead).

Verified with flutter analyze, flutter test, and a real run on the
Pixel_10a emulator (visually matches the prototype screenshots in design/,
Fly-tap correctly shows the gate message without crashing or switching mode).

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