diff --git a/packages/flutter/lib/src/material/navigation_bar.dart b/packages/flutter/lib/src/material/navigation_bar.dart index b9622daa44..22f7bcbdda 100644 --- a/packages/flutter/lib/src/material/navigation_bar.dart +++ b/packages/flutter/lib/src/material/navigation_bar.dart @@ -147,41 +147,35 @@ class NavigationBar extends StatelessWidget { final NavigationDestinationLabelBehavior effectiveLabelBehavior = labelBehavior ?? navigationBarTheme.labelBehavior ?? defaults.labelBehavior!; - final double additionalBottomPadding = MediaQuery.of(context).padding.bottom; return Material( color: backgroundColor ?? navigationBarTheme.backgroundColor ?? defaults.backgroundColor!, elevation: elevation ?? navigationBarTheme.elevation ?? defaults.elevation!, - child: Padding( - padding: EdgeInsets.only(bottom: additionalBottomPadding), - child: MediaQuery.removePadding( - context: context, - removeBottom: true, - child: SizedBox( - height: effectiveHeight, - child: Row( - children: [ - for (int i = 0; i < destinations.length; i++) - Expanded( - child: _SelectableAnimatedBuilder( - duration: animationDuration ?? const Duration(milliseconds: 500), - isSelected: i == selectedIndex, - builder: (BuildContext context, Animation animation) { - return _NavigationDestinationInfo( - index: i, - totalNumberOfDestinations: destinations.length, - selectedAnimation: animation, - labelBehavior: effectiveLabelBehavior, - onTap: _handleTap(i), - child: destinations[i], - ); - }, - ), + child: SafeArea( + child: SizedBox( + height: effectiveHeight, + child: Row( + children: [ + for (int i = 0; i < destinations.length; i++) + Expanded( + child: _SelectableAnimatedBuilder( + duration: animationDuration ?? const Duration(milliseconds: 500), + isSelected: i == selectedIndex, + builder: (BuildContext context, Animation animation) { + return _NavigationDestinationInfo( + index: i, + totalNumberOfDestinations: destinations.length, + selectedAnimation: animation, + labelBehavior: effectiveLabelBehavior, + onTap: _handleTap(i), + child: destinations[i], + ); + }, ), - ], - ), + ), + ], ), ), ), diff --git a/packages/flutter/test/material/navigation_bar_test.dart b/packages/flutter/test/material/navigation_bar_test.dart index 14276a2f8b..43046c2fef 100644 --- a/packages/flutter/test/material/navigation_bar_test.dart +++ b/packages/flutter/test/material/navigation_bar_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -137,6 +138,104 @@ void main() { expect(tester.getSize(find.byType(NavigationBar)).height, expectedHeight); }); + testWidgets('NavigationBar respects the notch/system navigation bar in landscape mode', (WidgetTester tester) async { + const double safeAreaPadding = 40.0; + Widget navigationBar() { + return NavigationBar( + destinations: const [ + NavigationDestination( + icon: Icon(Icons.ac_unit), + label: 'AC', + ), + NavigationDestination( + key: Key('Center'), + icon: Icon(Icons.center_focus_strong), + label: 'Center', + ), + NavigationDestination( + icon: Icon(Icons.access_alarm), + label: 'Alarm', + ), + ], + onDestinationSelected: (int i) {}, + ); + } + + await tester.pumpWidget(_buildWidget(navigationBar())); + final double defaultWidth = tester.getSize(find.byType(NavigationBar)).width; + final Finder defaultCenterItem = find.byKey(const Key('Center')); + final Offset center = tester.getCenter(defaultCenterItem); + expect(center.dx, defaultWidth / 2); + + await tester.pumpWidget( + _buildWidget( + MediaQuery( + data: const MediaQueryData( + padding: EdgeInsets.only(left: safeAreaPadding), + ), + child: navigationBar(), + ), + ), + ); + + // The position of center item of navigation bar should indicate whether + // the safe area is sufficiently respected, when safe area is on the left side. + // e.g. Android device with system navigation bar in landscape mode. + final Finder leftPaddedCenterItem = find.byKey(const Key('Center')); + final Offset leftPaddedCenter = tester.getCenter(leftPaddedCenterItem); + expect( + leftPaddedCenter.dx, + closeTo((defaultWidth + safeAreaPadding) / 2.0, precisionErrorTolerance), + ); + + await tester.pumpWidget( + _buildWidget( + MediaQuery( + data: const MediaQueryData( + padding: EdgeInsets.only(right: safeAreaPadding) + ), + child: navigationBar(), + ), + ), + ); + + // The position of center item of navigation bar should indicate whether + // the safe area is sufficiently respected, when safe area is on the right side. + // e.g. Android device with system navigation bar in landscape mode. + final Finder rightPaddedCenterItem = find.byKey(const Key('Center')); + final Offset rightPaddedCenter = tester.getCenter(rightPaddedCenterItem); + expect( + rightPaddedCenter.dx, + closeTo((defaultWidth - safeAreaPadding) / 2, precisionErrorTolerance), + ); + + await tester.pumpWidget( + _buildWidget( + MediaQuery( + data: const MediaQueryData( + padding: EdgeInsets.fromLTRB( + safeAreaPadding, + 0, + safeAreaPadding, + safeAreaPadding + ), + ), + child: navigationBar(), + ), + ), + ); + + // The position of center item of navigation bar should indicate whether + // the safe area is sufficiently respected, when safe areas are on both sides. + // e.g. iOS device with both sides of round corner. + final Finder paddedCenterItem = find.byKey(const Key('Center')); + final Offset paddedCenter = tester.getCenter(paddedCenterItem); + expect( + paddedCenter.dx, + closeTo(defaultWidth / 2, precisionErrorTolerance), + ); + }); + testWidgets('NavigationBar uses proper defaults when no parameters are given', (WidgetTester tester) async { // Pre-M3 settings that were hand coded. await tester.pumpWidget(