Unverified Commit 9bd058e9 authored by Bruno Leroux's avatar Bruno Leroux Committed by GitHub

Add `useSafeArea` parameter to `showModalBottomSheet` (#107140)

parent 0c40945a
......@@ -470,6 +470,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
super.settings,
this.transitionAnimationController,
this.anchorPoint,
this.useSafeArea = false,
}) : assert(isScrollControlled != null),
assert(isDismissible != null),
assert(enableDrag != null);
......@@ -487,6 +488,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
final bool enableDrag;
final AnimationController? transitionAnimationController;
final Offset? anchorPoint;
final bool useSafeArea;
@override
Duration get transitionDuration => _bottomSheetEnterDuration;
......@@ -519,12 +521,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
// By definition, the bottom sheet is aligned to the bottom of the page
// and isn't exposed to the top padding of the MediaQuery.
final Widget bottomSheet = MediaQuery.removePadding(
context: context,
removeTop: true,
child: DisplayFeatureSubScreen(
final Widget content = DisplayFeatureSubScreen(
anchorPoint: anchorPoint,
child: Builder(
builder: (BuildContext context) {
......@@ -541,8 +538,19 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
);
},
),
),
);
// If useSafeArea is true, a SafeArea is inserted.
// If useSafeArea is false, the bottom sheet is aligned to the bottom of the page
// and isn't exposed to the top padding of the MediaQuery.
final Widget bottomSheet = useSafeArea
? SafeArea(child: content)
: MediaQuery.removePadding(
context: context,
removeTop: true,
child: content,
);
return capturedThemes.wrap(bottomSheet);
}
}
......@@ -638,6 +646,9 @@ class _BottomSheetSuspendedCurve extends ParametricCurve<double> {
/// The [enableDrag] parameter specifies whether the bottom sheet can be
/// dragged up and down and dismissed by swiping downwards.
///
/// The [useSafeArea] parameter specifies whether a SafeArea is inserted. Defaults to false.
/// If false, no SafeArea is added and the top padding is consumed using MediaQuery.removePadding.
///
/// The optional [backgroundColor], [elevation], [shape], [clipBehavior],
/// [constraints] and [transitionAnimationController]
/// parameters can be passed in to customize the appearance and behavior of
......@@ -690,6 +701,7 @@ Future<T?> showModalBottomSheet<T>({
bool useRootNavigator = false,
bool isDismissible = true,
bool enableDrag = true,
bool useSafeArea = false,
RouteSettings? routeSettings,
AnimationController? transitionAnimationController,
Offset? anchorPoint,
......@@ -720,6 +732,7 @@ Future<T?> showModalBottomSheet<T>({
settings: routeSettings,
transitionAnimationController: transitionAnimationController,
anchorPoint: anchorPoint,
useSafeArea: useSafeArea,
));
}
......
......@@ -681,6 +681,69 @@ void main() {
);
});
testWidgets('modal BottomSheet can insert a SafeArea', (WidgetTester tester) async {
late BuildContext outerContext;
late BuildContext innerContext;
await tester.pumpWidget(Localizations(
locale: const Locale('en', 'US'),
delegates: const <LocalizationsDelegate<dynamic>>[
DefaultWidgetsLocalizations.delegate,
DefaultMaterialLocalizations.delegate,
],
child: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(
padding: EdgeInsets.all(50.0),
size: Size(400.0, 600.0),
),
child: Navigator(
onGenerateRoute: (_) {
return PageRouteBuilder<void>(
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
outerContext = context;
return Container();
},
);
},
),
),
),
));
// Without a SafeArea (useSafeArea is false by default)
showModalBottomSheet<void>(
context: outerContext,
builder: (BuildContext context) {
innerContext = context;
return Container();
},
);
await tester.pump();
await tester.pump(const Duration(seconds: 1));
// Top padding is consumed and there is no SafeArea
expect(MediaQuery.of(innerContext).padding.top, 0);
expect(find.byType(SafeArea), findsNothing);
// With a SafeArea
showModalBottomSheet<void>(
context: outerContext,
useSafeArea: true,
builder: (BuildContext context) {
innerContext = context;
return Container();
},
);
await tester.pump();
await tester.pump(const Duration(seconds: 1));
// Top padding is consumed and there is a SafeArea
expect(MediaQuery.of(innerContext).padding.top, 0);
expect(find.byType(SafeArea), findsOneWidget);
});
testWidgets('modal BottomSheet has semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment