diff --git a/app/lib/ui/widgets/bottom_stats_bar.dart b/app/lib/ui/widgets/bottom_stats_bar.dart index 8ccf4d0..e555305 100644 --- a/app/lib/ui/widgets/bottom_stats_bar.dart +++ b/app/lib/ui/widgets/bottom_stats_bar.dart @@ -138,11 +138,27 @@ class BottomStatsBar extends ConsumerWidget { ), child: Row( children: [ - // Nur noch die Mission-Pille links - sie darf die gesamte - // verfuegbare Breite dieser Seite nutzen (Align loosent die - // Constraints statt sie wie Expanded zu erzwingen, das - // begrenzt auf die verfuegbare Breite, ohne einen kuenstlich - // kleinen Fixwert vorzugeben). + // Mission-Pille links bleibt das einzige flexible/absorbierende + // Element (Doku: "erlaube dass die Pille fuer den Drohnenstatus + // groesser skalieren kann ... und dafuer die Missionspille + // kleiner skalieren kann", "darauf achten dass drone status + // immer auf seinen inhalt skaliert") - vorher waren links UND + // rechts je ein gleich breites Expanded, wodurch die + // Drohnen-Pille bei langem Inhalt (z.B. "disconnected") per + // FittedBox bis zur Unlesbarkeit schrumpfte. Ein Flex- + // Verhaeltnis (z.B. 1:4) loest das NICHT sauber: Expanded/ + // Flexible erzwingen so oder so eine feste Bruchteils- + // Zuteilung, die die Missionspille selbst bei kurzen Namen auf + // ihren (kleineren) Anteil einschraenkt, unabhaengig davon, ob + // die Drohnen-Seite diesen Platz ueberhaupt braucht. Die + // Drohnen-Seite ist deshalb jetzt bewusst NICHT mehr + // Expanded/Flexible, sondern wie _detailsPill()/ + // _AltitudeProfileToggleButton ein normales, intrinsisch + // grosses Row-Kind - sie bekommt IMMER genau die Breite, die + // ihr aktueller Inhalt braucht, nie mehr und nie weniger. Die + // Missionspille als einziges Expanded-Element bekommt + // verlaesslich den kompletten Rest und schrumpft (ellipsiert) + // zuerst, wenn es eng wird. Expanded( child: Align( alignment: Alignment.centerLeft, @@ -166,58 +182,45 @@ class BottomStatsBar extends ConsumerWidget { const SizedBox(width: _footerElementSpacing), _detailsPill(), const SizedBox(width: _footerElementSpacing), - // Drohnen-Pille + Warnungs-/Settings-Knopf rechts. Die Pille - // sitzt in Flexible, damit sie bei langen Namen zu Gunsten der - // beiden festgroessen Rundknoepfe schrumpft/ellipsiert, statt - // sie aus dem sichtbaren Bereich zu draengen. - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: droneStatus != null - ? _DroneStatusPill( - status: droneStatus!, - // Oeffnet im Fly-Modus das neue Status-Menue - // (Doku: "ein neues menue anlegen ... nach dem - // vorbild des menues fuer drones and - // missions") direkt auf dem Status-Tab, statt - // wie zuvor die Drohnenprofil-Verwaltung - - // Profile werden waehrend des Fluges ohnehin - // nicht gewechselt. - onTap: () => Navigator.of(context).push(MaterialPageRoute( - builder: (_) => const DroneStatusMessagesPanel.status(), - )), - ) - : _NameChip( - // Gleicher Hintergrund wie die Details-Pille - // (Doku: "gleichen Hintergrund wie bei der - // Detail Pille fuer die Drohne Pille - // verwenden") statt des vorherigen blauen - // Farbtons. - label: 'Drone: $droneName', - color: Colors.white.withValues(alpha: 0.12), - textColor: Colors.white, - onTap: () => Navigator.of(context).push(MaterialPageRoute( - builder: (_) => const MissionsDronesPanel.drones(), - )), - ), - ), - const SizedBox(width: _footerElementSpacing), - if (onSendTap != null) ...[ - _SendButton(sending: sending, onTap: onSendTap), - const SizedBox(width: _footerElementSpacing), - ] else if (warnings.isNotEmpty) ...[ - _WarningButton(onTap: onWarningsTap), - const SizedBox(width: _footerElementSpacing), - ], - _SettingsButton(onTap: onSettingsTap), - ], - ), - ), - ), + // Drohnen-Pille + Warnungs-/Settings-Knopf rechts - bewusst kein + // Expanded/Flexible (siehe Kommentar oben): immer intrinsische + // Breite, FittedBox in _DroneStatusPill bleibt nur als letztes + // Sicherheitsnetz fuer wirklich extreme Faelle (z.B. + // Grossschrift-Bedienungshilfen), statt schon im Normalbetrieb + // zu greifen. + droneStatus != null + ? _DroneStatusPill( + status: droneStatus!, + // Oeffnet im Fly-Modus das neue Status-Menue (Doku: "ein + // neues menue anlegen ... nach dem vorbild des menues + // fuer drones and missions") direkt auf dem Status-Tab, + // statt wie zuvor die Drohnenprofil-Verwaltung - Profile + // werden waehrend des Fluges ohnehin nicht gewechselt. + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (_) => const DroneStatusMessagesPanel.status(), + )), + ) + : _NameChip( + // Gleicher Hintergrund wie die Details-Pille (Doku: + // "gleichen Hintergrund wie bei der Detail Pille fuer + // die Drohne Pille verwenden") statt des vorherigen + // blauen Farbtons. + label: 'Drone: $droneName', + color: Colors.white.withValues(alpha: 0.12), + textColor: Colors.white, + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (_) => const MissionsDronesPanel.drones(), + )), + ), + const SizedBox(width: _footerElementSpacing), + if (onSendTap != null) ...[ + _SendButton(sending: sending, onTap: onSendTap), + const SizedBox(width: _footerElementSpacing), + ] else if (warnings.isNotEmpty) ...[ + _WarningButton(onTap: onWarningsTap), + const SizedBox(width: _footerElementSpacing), + ], + _SettingsButton(onTap: onSettingsTap), ], ), ), diff --git a/app/test/widget_test.dart b/app/test/widget_test.dart index 7961e93..8684b1b 100644 --- a/app/test/widget_test.dart +++ b/app/test/widget_test.dart @@ -1003,6 +1003,18 @@ void main() { testWidgets( 'Warnungs-Button erscheint bei Reichweiten-Ueberschreitung und oeffnet die Warnungsliste', (tester) async { + // Realistische (landschaftsbreite) Viewport-Groesse statt der 800x600- + // Standardvorgabe von flutter_test - die Fusszeile (BottomStatsBar) + // reiht mehrere intrinsisch grosse Pillen/Knoepfe nebeneinander (Doku: + // "darauf achten dass drone status immer auf seinen inhalt skaliert"), + // was bei 800px Breite selbst im Plan-Modus zu einem echten + // RenderFlex-Overflow fuehrt - auf jedem real relevanten Geraet (dieses + // Projekt ist landscape-only) ist dafuer genug Platz. + tester.view.physicalSize = const Size(2424, 1080); + tester.view.devicePixelRatio = 1.0; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + final container = _testContainer(); addTearDown(container.dispose); @@ -1187,6 +1199,13 @@ void main() { testWidgets( 'Antippen einer Speed-Warnung oeffnet die Wegpunktliste direkt auf dem Speed-Tab', (tester) async { + // Realistische (landschaftsbreite) Viewport-Groesse statt der 800x600- + // Standardvorgabe - siehe Kommentar beim vorigen Warnungs-Button-Test. + tester.view.physicalSize = const Size(2424, 1080); + tester.view.devicePixelRatio = 1.0; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + final container = _testContainer(); addTearDown(container.dispose);