diff --git a/lib/src/ui/app_shell.dart b/lib/src/ui/app_shell.dart index 7ff0f15..678862a 100644 --- a/lib/src/ui/app_shell.dart +++ b/lib/src/ui/app_shell.dart @@ -138,6 +138,11 @@ class _ShellNavBar extends StatelessWidget { @override Widget build(BuildContext context) => NavigationBar( key: const Key('shell-nav-bar'), + // Material 3's own default (80dp) reads as oversized for a 4-item bar with plain + // icon+label destinations -- direct feedback (`docs/FEEDBACK.md`) called this out. + // 64dp keeps every icon/label pair at its default size, just with less surrounding + // padding, and stays comfortably above the 48dp minimum touch target. + height: 64, selectedIndex: currentIndex, onDestinationSelected: onDestinationSelected, destinations: const [ diff --git a/lib/src/ui/record/record_screen.dart b/lib/src/ui/record/record_screen.dart index b5fff2b..3154316 100644 --- a/lib/src/ui/record/record_screen.dart +++ b/lib/src/ui/record/record_screen.dart @@ -156,11 +156,21 @@ class _RecordScreenState extends ConsumerState { } } - /// 72dp normally, 96dp mounted -- one constant height regardless of how many + /// 56dp normally, 96dp mounted -- one constant height regardless of how many /// segments the control bar has (one/two/three), so the HUD area above it is always /// the same size and a widget's saved fractional position never jumps between ride - /// states. - double _controlBarHeight(bool mountedMode) => mountedMode ? 96 : 72; + /// states. Mounted mode stays at 96dp (V3-05: not enough at speed, with gloves, + /// below that); the handheld case only needs a comfortable tap target, not a glove + /// target, and 56dp is still well above the 48dp accessibility floor. + double _controlBarHeight(bool mountedMode) => mountedMode ? 96 : 56; + + /// The HUD stat grid used to fill the entire area down to the control bar, which + /// made even a two-row default layout balloon to take up most of the screen on a + /// tall phone -- direct feedback (`docs/FEEDBACK.md`) called this out explicitly. + /// Capping it to a fixed, compact band keeps every grid cell a sane physical size + /// regardless of screen height, leaving the rest of the screen as pure map. Mounted + /// mode gets a taller band to match its own larger text scale ([mountedTextScale]). + double _hudAreaHeight(bool mountedMode) => mountedMode ? 320 : 260; @override Widget build(BuildContext context) { @@ -195,8 +205,11 @@ class _RecordScreenState extends ConsumerState { child: Stack( children: [ if (!ui.isIdle) - Positioned.fill( - bottom: _controlBarHeight(mountedMode), + Positioned( + top: 0, + left: 0, + right: 0, + height: _hudAreaHeight(mountedMode), child: HudEditOverlay( metricBuilder: (context, metric) => _HudMetricValue( metric: metric, @@ -237,6 +250,7 @@ class _RecordScreenState extends ConsumerState { height: _controlBarHeight(mountedMode), child: _ControlBar( ui: ui, + mountedMode: mountedMode, onStart: () => _guard(engine.start), onPause: () => _guard(engine.pause), onStop: () => _guard(() async => engine.stop()), @@ -379,6 +393,7 @@ class _ControlBar extends StatelessWidget { required this.onPause, required this.onStop, required this.onDiscard, + this.mountedMode = false, }); final RecordUiState ui; @@ -387,6 +402,11 @@ class _ControlBar extends StatelessWidget { final VoidCallback onStop; final VoidCallback onDiscard; + /// V3-05: mounted mode keeps full-size, glove-friendly icons. The handheld case + /// only needs a comfortable tap target, not a glove target, so its icons shrink + /// along with the bar itself (`_controlBarHeight`) -- see [_Segment.iconSize]. + final bool mountedMode; + @override Widget build(BuildContext context) { if (ui.isIdle) { @@ -397,6 +417,7 @@ class _ControlBar extends StatelessWidget { background: Theme.of(context).colorScheme.tertiaryContainer, foreground: Theme.of(context).colorScheme.onTertiaryContainer, onTap: onStart, + mountedMode: mountedMode, ); } @@ -412,6 +433,7 @@ class _ControlBar extends StatelessWidget { foreground: colors.onTertiaryContainer, onTap: onPause, trailingBorder: true, + mountedMode: mountedMode, ), ), Expanded( @@ -421,6 +443,7 @@ class _ControlBar extends StatelessWidget { background: colors.errorContainer, foreground: colors.onErrorContainer, onTap: onStop, + mountedMode: mountedMode, ), ), ], @@ -441,6 +464,7 @@ class _ControlBar extends StatelessWidget { foreground: colors.onTertiaryContainer, onTap: onStart, trailingBorder: true, + mountedMode: mountedMode, ), ), Expanded( @@ -451,6 +475,7 @@ class _ControlBar extends StatelessWidget { foreground: colors.onErrorContainer, onTap: onStop, trailingBorder: true, + mountedMode: mountedMode, ), ), Expanded( @@ -460,6 +485,7 @@ class _ControlBar extends StatelessWidget { background: colors.surfaceContainerHighest, foreground: colors.error, onTap: onDiscard, + mountedMode: mountedMode, ), ), ], @@ -476,6 +502,7 @@ class _Segment extends StatelessWidget { required this.onTap, this.label, this.trailingBorder = false, + this.mountedMode = false, }); final String keyName; @@ -485,6 +512,9 @@ class _Segment extends StatelessWidget { final VoidCallback onTap; final String? label; final bool trailingBorder; + final bool mountedMode; + + double get _iconSize => mountedMode ? 32 : 24; @override Widget build(BuildContext context) => Material( @@ -505,18 +535,18 @@ class _Segment extends StatelessWidget { child: SizedBox.expand( child: Center( child: label == null - ? Icon(icon, color: foreground, size: 32) + ? Icon(icon, color: foreground, size: _iconSize) : Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(icon, color: foreground), + Icon(icon, color: foreground, size: _iconSize), const SizedBox(width: 8), Text( label!, style: TextStyle( color: foreground, fontWeight: FontWeight.bold, - fontSize: 16, + fontSize: mountedMode ? 16 : 14, ), ), ],