fix: eliminate transient dashboard viewport overflow
This commit is contained in:
parent
472b1ac994
commit
a7cce30e3d
|
|
@ -53,193 +53,223 @@ class ResponsiveDashboardLayout extends ConsumerWidget {
|
|||
DashboardLayout mode,
|
||||
RideSession session,
|
||||
) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Top status bar
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isShortPortrait = constraints.maxHeight < 620;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
// Top status bar
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
DashboardLayoutSwitcher(),
|
||||
SizedBox(width: 4),
|
||||
GPSStatusIndicator(),
|
||||
SizedBox(width: 8),
|
||||
HeadingIndicator(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerRight,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
PerformanceSummary(),
|
||||
SizedBox(width: 6),
|
||||
RideStatusChip(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Body based on mode
|
||||
if (mode == DashboardLayout.minimal) ...[
|
||||
if (!isShortPortrait) const Spacer(flex: 2) else const Spacer(flex: 1),
|
||||
const Flexible(
|
||||
flex: 6,
|
||||
child: Center(
|
||||
child: SpeedReadout(size: SpeedReadoutSize.hero),
|
||||
),
|
||||
),
|
||||
if (!isShortPortrait) const Spacer(flex: 2) else const Spacer(flex: 1),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: isShortPortrait ? 4 : 10,
|
||||
),
|
||||
decoration: _boxDecoration(context),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
DashboardLayoutSwitcher(),
|
||||
SizedBox(width: 4),
|
||||
GPSStatusIndicator(),
|
||||
SizedBox(width: 8),
|
||||
HeadingIndicator(),
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'TRIP',
|
||||
value: session.tripDistanceMiles.toStringAsFixed(2),
|
||||
unit: 'MI',
|
||||
compact: isShortPortrait,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'STATUS',
|
||||
value: session.state.name.toUpperCase(),
|
||||
compact: isShortPortrait,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerRight,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
SizedBox(height: isShortPortrait ? 6 : 16),
|
||||
RideActionButton(compact: isShortPortrait),
|
||||
] else if (mode == DashboardLayout.touring) ...[
|
||||
SizedBox(height: isShortPortrait ? 2 : 6),
|
||||
SpeedReadout(size: isShortPortrait ? SpeedReadoutSize.compact : SpeedReadoutSize.compact),
|
||||
SizedBox(height: isShortPortrait ? 4 : 8),
|
||||
const Expanded(
|
||||
flex: 5,
|
||||
child: DashboardMapPanel(),
|
||||
),
|
||||
SizedBox(height: isShortPortrait ? 4 : 10),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: isShortPortrait ? 4 : 6,
|
||||
),
|
||||
decoration: _boxDecoration(context),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'TRIP',
|
||||
value: session.tripDistanceMiles.toStringAsFixed(1),
|
||||
unit: 'MI',
|
||||
compact: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'AVG',
|
||||
value: session.averageSpeedMph.toStringAsFixed(1),
|
||||
unit: 'MPH',
|
||||
compact: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'ELEV',
|
||||
value: session.elevationGain.toStringAsFixed(0),
|
||||
unit: 'M',
|
||||
compact: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'MOVING',
|
||||
value: '${(session.movingTimeSeconds / 60).floor()}m',
|
||||
compact: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: isShortPortrait ? 4 : 10),
|
||||
RideActionButton(compact: isShortPortrait),
|
||||
] else ...[
|
||||
// Sport Cockpit Mode
|
||||
if (!isShortPortrait) const Spacer(flex: 1),
|
||||
Flexible(
|
||||
flex: isShortPortrait ? 3 : 4,
|
||||
child: const Center(
|
||||
child: SpeedReadout(size: SpeedReadoutSize.hero),
|
||||
),
|
||||
),
|
||||
if (!isShortPortrait) const Spacer(flex: 1),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
PerformanceSummary(),
|
||||
SizedBox(width: 6),
|
||||
RideStatusChip(),
|
||||
Expanded(child: GForceIndicator(compact: isShortPortrait)),
|
||||
Expanded(child: LeanAngleIndicator(compact: isShortPortrait)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Body based on mode
|
||||
if (mode == DashboardLayout.minimal) ...[
|
||||
const Spacer(flex: 2),
|
||||
const SpeedReadout(size: SpeedReadoutSize.hero),
|
||||
const Spacer(flex: 2),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
decoration: _boxDecoration(context),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'TRIP',
|
||||
value: session.tripDistanceMiles.toStringAsFixed(2),
|
||||
unit: 'MI',
|
||||
),
|
||||
),
|
||||
SizedBox(height: isShortPortrait ? 4 : 14),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: isShortPortrait ? 4 : 8,
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'STATUS',
|
||||
value: session.state.name.toUpperCase(),
|
||||
decoration: _boxDecoration(context),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'TRIP',
|
||||
value: session.tripDistanceMiles.toStringAsFixed(2),
|
||||
unit: 'MI',
|
||||
icon: Icons.pin_drop_outlined,
|
||||
compact: isShortPortrait,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const RideActionButton(),
|
||||
] else if (mode == DashboardLayout.touring) ...[
|
||||
const SizedBox(height: 6),
|
||||
const SpeedReadout(size: SpeedReadoutSize.compact),
|
||||
const SizedBox(height: 8),
|
||||
const Expanded(
|
||||
flex: 5,
|
||||
child: DashboardMapPanel(),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
decoration: _boxDecoration(context),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'TRIP',
|
||||
value: session.tripDistanceMiles.toStringAsFixed(1),
|
||||
unit: 'MI',
|
||||
compact: true,
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'TOP SPD',
|
||||
value: session.topSpeedMph.toStringAsFixed(0),
|
||||
unit: 'MPH',
|
||||
icon: Icons.speed_outlined,
|
||||
compact: isShortPortrait,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'AVG',
|
||||
value: session.averageSpeedMph.toStringAsFixed(1),
|
||||
unit: 'MPH',
|
||||
compact: true,
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'AVG SPD',
|
||||
value: session.averageSpeedMph.toStringAsFixed(1),
|
||||
unit: 'MPH',
|
||||
icon: Icons.av_timer_outlined,
|
||||
compact: isShortPortrait,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'ELEV',
|
||||
value: session.elevationGain.toStringAsFixed(0),
|
||||
unit: 'M',
|
||||
compact: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'MOVING',
|
||||
value: '${(session.movingTimeSeconds / 60).floor()}m',
|
||||
compact: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const RideActionButton(),
|
||||
] else ...[
|
||||
// Sport Cockpit Mode
|
||||
const Spacer(flex: 1),
|
||||
const SpeedReadout(size: SpeedReadoutSize.hero),
|
||||
const Spacer(flex: 1),
|
||||
const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Expanded(child: GForceIndicator()),
|
||||
Expanded(child: LeanAngleIndicator()),
|
||||
),
|
||||
SizedBox(height: isShortPortrait ? 4 : 14),
|
||||
RideActionButton(compact: isShortPortrait),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: _boxDecoration(context),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'TRIP',
|
||||
value: session.tripDistanceMiles.toStringAsFixed(2),
|
||||
unit: 'MI',
|
||||
icon: Icons.pin_drop_outlined,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'TOP SPD',
|
||||
value: session.topSpeedMph.toStringAsFixed(0),
|
||||
unit: 'MPH',
|
||||
icon: Icons.speed_outlined,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: MetricTile(
|
||||
label: 'AVG SPD',
|
||||
value: session.averageSpeedMph.toStringAsFixed(1),
|
||||
unit: 'MPH',
|
||||
icon: Icons.av_timer_outlined,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const RideActionButton(),
|
||||
],
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ void main() {
|
|||
group('Canonical Dashboard Viewport Matrix Tests', () {
|
||||
final viewports = <String, Size>{
|
||||
'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),
|
||||
'412x915 (Pixel-class Portrait)': const Size(412, 915),
|
||||
'780x360 (Short Landscape Mount)': const Size(780, 360),
|
||||
|
|
|
|||
Loading…
Reference in New Issue