fix: eliminate transient dashboard viewport overflow

This commit is contained in:
Developer 2026-09-05 07:21:02 -04:00
parent 472b1ac994
commit a7cce30e3d
2 changed files with 207 additions and 173 deletions

View File

@ -53,6 +53,10 @@ class ResponsiveDashboardLayout extends ConsumerWidget {
DashboardLayout mode, DashboardLayout mode,
RideSession session, RideSession session,
) { ) {
return LayoutBuilder(
builder: (context, constraints) {
final isShortPortrait = constraints.maxHeight < 620;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
@ -94,11 +98,19 @@ class ResponsiveDashboardLayout extends ConsumerWidget {
// Body based on mode // Body based on mode
if (mode == DashboardLayout.minimal) ...[ if (mode == DashboardLayout.minimal) ...[
const Spacer(flex: 2), if (!isShortPortrait) const Spacer(flex: 2) else const Spacer(flex: 1),
const SpeedReadout(size: SpeedReadoutSize.hero), const Flexible(
const Spacer(flex: 2), flex: 6,
child: Center(
child: SpeedReadout(size: SpeedReadoutSize.hero),
),
),
if (!isShortPortrait) const Spacer(flex: 2) else const Spacer(flex: 1),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: isShortPortrait ? 4 : 10,
),
decoration: _boxDecoration(context), decoration: _boxDecoration(context),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
@ -109,6 +121,7 @@ class ResponsiveDashboardLayout extends ConsumerWidget {
label: 'TRIP', label: 'TRIP',
value: session.tripDistanceMiles.toStringAsFixed(2), value: session.tripDistanceMiles.toStringAsFixed(2),
unit: 'MI', unit: 'MI',
compact: isShortPortrait,
), ),
), ),
), ),
@ -117,25 +130,29 @@ class ResponsiveDashboardLayout extends ConsumerWidget {
child: MetricTile( child: MetricTile(
label: 'STATUS', label: 'STATUS',
value: session.state.name.toUpperCase(), value: session.state.name.toUpperCase(),
compact: isShortPortrait,
), ),
), ),
), ),
], ],
), ),
), ),
const SizedBox(height: 16), SizedBox(height: isShortPortrait ? 6 : 16),
const RideActionButton(), RideActionButton(compact: isShortPortrait),
] else if (mode == DashboardLayout.touring) ...[ ] else if (mode == DashboardLayout.touring) ...[
const SizedBox(height: 6), SizedBox(height: isShortPortrait ? 2 : 6),
const SpeedReadout(size: SpeedReadoutSize.compact), SpeedReadout(size: isShortPortrait ? SpeedReadoutSize.compact : SpeedReadoutSize.compact),
const SizedBox(height: 8), SizedBox(height: isShortPortrait ? 4 : 8),
const Expanded( const Expanded(
flex: 5, flex: 5,
child: DashboardMapPanel(), child: DashboardMapPanel(),
), ),
const SizedBox(height: 10), SizedBox(height: isShortPortrait ? 4 : 10),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), padding: EdgeInsets.symmetric(
horizontal: 8,
vertical: isShortPortrait ? 4 : 6,
),
decoration: _boxDecoration(context), decoration: _boxDecoration(context),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
@ -182,23 +199,31 @@ class ResponsiveDashboardLayout extends ConsumerWidget {
], ],
), ),
), ),
const SizedBox(height: 10), SizedBox(height: isShortPortrait ? 4 : 10),
const RideActionButton(), RideActionButton(compact: isShortPortrait),
] else ...[ ] else ...[
// Sport Cockpit Mode // Sport Cockpit Mode
const Spacer(flex: 1), if (!isShortPortrait) const Spacer(flex: 1),
const SpeedReadout(size: SpeedReadoutSize.hero), Flexible(
const Spacer(flex: 1), flex: isShortPortrait ? 3 : 4,
const Row( child: const Center(
child: SpeedReadout(size: SpeedReadoutSize.hero),
),
),
if (!isShortPortrait) const Spacer(flex: 1),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
Expanded(child: GForceIndicator()), Expanded(child: GForceIndicator(compact: isShortPortrait)),
Expanded(child: LeanAngleIndicator()), Expanded(child: LeanAngleIndicator(compact: isShortPortrait)),
], ],
), ),
const SizedBox(height: 14), SizedBox(height: isShortPortrait ? 4 : 14),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: isShortPortrait ? 4 : 8,
),
decoration: _boxDecoration(context), decoration: _boxDecoration(context),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
@ -210,6 +235,7 @@ class ResponsiveDashboardLayout extends ConsumerWidget {
value: session.tripDistanceMiles.toStringAsFixed(2), value: session.tripDistanceMiles.toStringAsFixed(2),
unit: 'MI', unit: 'MI',
icon: Icons.pin_drop_outlined, icon: Icons.pin_drop_outlined,
compact: isShortPortrait,
), ),
), ),
), ),
@ -220,6 +246,7 @@ class ResponsiveDashboardLayout extends ConsumerWidget {
value: session.topSpeedMph.toStringAsFixed(0), value: session.topSpeedMph.toStringAsFixed(0),
unit: 'MPH', unit: 'MPH',
icon: Icons.speed_outlined, icon: Icons.speed_outlined,
compact: isShortPortrait,
), ),
), ),
), ),
@ -230,17 +257,20 @@ class ResponsiveDashboardLayout extends ConsumerWidget {
value: session.averageSpeedMph.toStringAsFixed(1), value: session.averageSpeedMph.toStringAsFixed(1),
unit: 'MPH', unit: 'MPH',
icon: Icons.av_timer_outlined, icon: Icons.av_timer_outlined,
compact: isShortPortrait,
), ),
), ),
), ),
], ],
), ),
), ),
const SizedBox(height: 14), SizedBox(height: isShortPortrait ? 4 : 14),
const RideActionButton(), RideActionButton(compact: isShortPortrait),
], ],
], ],
); );
},
);
} }
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------

View File

@ -76,6 +76,10 @@ void main() {
group('Canonical Dashboard Viewport Matrix Tests', () { group('Canonical Dashboard Viewport Matrix Tests', () {
final viewports = <String, Size>{ final viewports = <String, Size>{
'320x640 (Small Compact Phone)': const Size(320, 640), '320x640 (Small Compact Phone)': const Size(320, 640),
'320x520 (Constrained Small Phone)': const Size(320, 520),
'360x460 (Transient IME/Sheet Inset)': const Size(360, 460),
'412x460 (Pixel 8a Inset Viewport)': const Size(412, 460),
'390x480 (Constrained Portrait Phone)': const Size(390, 480),
'400x800 (Standard Phone Portrait)': const Size(400, 800), '400x800 (Standard Phone Portrait)': const Size(400, 800),
'412x915 (Pixel-class Portrait)': const Size(412, 915), '412x915 (Pixel-class Portrait)': const Size(412, 915),
'780x360 (Short Landscape Mount)': const Size(780, 360), '780x360 (Short Landscape Mount)': const Size(780, 360),