Footer: drone status pill always sizes to its content, mission pill absorbs the rest
The drone status pill and the mission pill previously sat in two equally sized Expanded regions. That squeezed the drone pill's FittedBox down to illegibility whenever its content grew (e.g. "disconnected"), while the mission pill often had unused space to spare. A plain flex-ratio rebalance (e.g. mission flex:1 vs drone flex:4) turned out to be the wrong tool: Expanded/Flexible always force a fixed fractional share regardless of actual content need, so it either starved the mission pill even for short names, or capped the drone pill below what it needed. bottom_stats_bar.dart: the drone-side content (status pill or drone-name chip + send/warning/settings buttons) is now a plain, non-flex Row child, exactly like _detailsPill()/_AltitudeProfileToggleButton already were - it always gets exactly the width its current content needs, never more, never less. The mission pill remains the sole Expanded element and absorbs whatever space is left, ellipsizing first if it's tight. widget_test.dart: two tests exercising the footer at the default 800x600 test viewport started hitting a real RenderFlex overflow, since that width was never realistic for this landscape-only app's footer content in the first place (previously masked by the drone pill silently shrinking via FittedBox). Set a realistic device-sized viewport (2424x1080, matching the Pixel_10a emulator) for just those two tests. Verified on the Pixel_10a emulator: "disconnected" now renders fully legible in the drone pill, and the mission pill still reads normally alongside it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
1b92e6a452
commit
7ff88c5435
@@ -138,11 +138,27 @@ class BottomStatsBar extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
// Nur noch die Mission-Pille links - sie darf die gesamte
|
// Mission-Pille links bleibt das einzige flexible/absorbierende
|
||||||
// verfuegbare Breite dieser Seite nutzen (Align loosent die
|
// Element (Doku: "erlaube dass die Pille fuer den Drohnenstatus
|
||||||
// Constraints statt sie wie Expanded zu erzwingen, das
|
// groesser skalieren kann ... und dafuer die Missionspille
|
||||||
// begrenzt auf die verfuegbare Breite, ohne einen kuenstlich
|
// kleiner skalieren kann", "darauf achten dass drone status
|
||||||
// kleinen Fixwert vorzugeben).
|
// 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(
|
Expanded(
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
@@ -166,37 +182,29 @@ class BottomStatsBar extends ConsumerWidget {
|
|||||||
const SizedBox(width: _footerElementSpacing),
|
const SizedBox(width: _footerElementSpacing),
|
||||||
_detailsPill(),
|
_detailsPill(),
|
||||||
const SizedBox(width: _footerElementSpacing),
|
const SizedBox(width: _footerElementSpacing),
|
||||||
// Drohnen-Pille + Warnungs-/Settings-Knopf rechts. Die Pille
|
// Drohnen-Pille + Warnungs-/Settings-Knopf rechts - bewusst kein
|
||||||
// sitzt in Flexible, damit sie bei langen Namen zu Gunsten der
|
// Expanded/Flexible (siehe Kommentar oben): immer intrinsische
|
||||||
// beiden festgroessen Rundknoepfe schrumpft/ellipsiert, statt
|
// Breite, FittedBox in _DroneStatusPill bleibt nur als letztes
|
||||||
// sie aus dem sichtbaren Bereich zu draengen.
|
// Sicherheitsnetz fuer wirklich extreme Faelle (z.B.
|
||||||
Expanded(
|
// Grossschrift-Bedienungshilfen), statt schon im Normalbetrieb
|
||||||
child: Align(
|
// zu greifen.
|
||||||
alignment: Alignment.centerRight,
|
droneStatus != null
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: droneStatus != null
|
|
||||||
? _DroneStatusPill(
|
? _DroneStatusPill(
|
||||||
status: droneStatus!,
|
status: droneStatus!,
|
||||||
// Oeffnet im Fly-Modus das neue Status-Menue
|
// Oeffnet im Fly-Modus das neue Status-Menue (Doku: "ein
|
||||||
// (Doku: "ein neues menue anlegen ... nach dem
|
// neues menue anlegen ... nach dem vorbild des menues
|
||||||
// vorbild des menues fuer drones and
|
// fuer drones and missions") direkt auf dem Status-Tab,
|
||||||
// missions") direkt auf dem Status-Tab, statt
|
// statt wie zuvor die Drohnenprofil-Verwaltung - Profile
|
||||||
// wie zuvor die Drohnenprofil-Verwaltung -
|
// werden waehrend des Fluges ohnehin nicht gewechselt.
|
||||||
// Profile werden waehrend des Fluges ohnehin
|
|
||||||
// nicht gewechselt.
|
|
||||||
onTap: () => Navigator.of(context).push(MaterialPageRoute(
|
onTap: () => Navigator.of(context).push(MaterialPageRoute(
|
||||||
builder: (_) => const DroneStatusMessagesPanel.status(),
|
builder: (_) => const DroneStatusMessagesPanel.status(),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
: _NameChip(
|
: _NameChip(
|
||||||
// Gleicher Hintergrund wie die Details-Pille
|
// Gleicher Hintergrund wie die Details-Pille (Doku:
|
||||||
// (Doku: "gleichen Hintergrund wie bei der
|
// "gleichen Hintergrund wie bei der Detail Pille fuer
|
||||||
// Detail Pille fuer die Drohne Pille
|
// die Drohne Pille verwenden") statt des vorherigen
|
||||||
// verwenden") statt des vorherigen blauen
|
// blauen Farbtons.
|
||||||
// Farbtons.
|
|
||||||
label: 'Drone: $droneName',
|
label: 'Drone: $droneName',
|
||||||
color: Colors.white.withValues(alpha: 0.12),
|
color: Colors.white.withValues(alpha: 0.12),
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
@@ -204,7 +212,6 @@ class BottomStatsBar extends ConsumerWidget {
|
|||||||
builder: (_) => const MissionsDronesPanel.drones(),
|
builder: (_) => const MissionsDronesPanel.drones(),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SizedBox(width: _footerElementSpacing),
|
const SizedBox(width: _footerElementSpacing),
|
||||||
if (onSendTap != null) ...[
|
if (onSendTap != null) ...[
|
||||||
_SendButton(sending: sending, onTap: onSendTap),
|
_SendButton(sending: sending, onTap: onSendTap),
|
||||||
@@ -217,10 +224,6 @@ class BottomStatsBar extends ConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1003,6 +1003,18 @@ void main() {
|
|||||||
testWidgets(
|
testWidgets(
|
||||||
'Warnungs-Button erscheint bei Reichweiten-Ueberschreitung und oeffnet die Warnungsliste',
|
'Warnungs-Button erscheint bei Reichweiten-Ueberschreitung und oeffnet die Warnungsliste',
|
||||||
(tester) async {
|
(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();
|
final container = _testContainer();
|
||||||
addTearDown(container.dispose);
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
@@ -1187,6 +1199,13 @@ void main() {
|
|||||||
testWidgets(
|
testWidgets(
|
||||||
'Antippen einer Speed-Warnung oeffnet die Wegpunktliste direkt auf dem Speed-Tab',
|
'Antippen einer Speed-Warnung oeffnet die Wegpunktliste direkt auf dem Speed-Tab',
|
||||||
(tester) async {
|
(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();
|
final container = _testContainer();
|
||||||
addTearDown(container.dispose);
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user