Commit dd43da71 authored by Tjong Anthony's avatar Tjong Anthony Committed by Shi-Hao Hong

Add isDismissible configuration for showModalBottomSheet (#42404)

* Allow showModalBottomSheet to present bottom sheet that is not dismissible by tapping on the scrim

* Add guards, improve styling and tests for BottomSheet
parent ba70e0e0
......@@ -343,9 +343,11 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
this.elevation,
this.shape,
this.clipBehavior,
this.isDismissible = true,
@required this.isScrollControlled,
RouteSettings settings,
}) : assert(isScrollControlled != null),
assert(isDismissible != null),
super(settings: settings);
final WidgetBuilder builder;
......@@ -355,12 +357,13 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
final double elevation;
final ShapeBorder shape;
final Clip clipBehavior;
final bool isDismissible;
@override
Duration get transitionDuration => _bottomSheetDuration;
@override
bool get barrierDismissible => true;
bool get barrierDismissible => isDismissible;
@override
final String barrierLabel;
......@@ -428,6 +431,9 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
/// that a modal [BottomSheet] needs to be displayed above all other content
/// but the caller is inside another [Navigator].
///
/// The [isDismissible] parameter specifies whether the bottom sheet will be
/// dismissed when user taps on the scrim.
///
/// The optional [backgroundColor], [elevation], [shape], and [clipBehavior]
/// parameters can be passed in to customize the appearance and behavior of
/// modal bottom sheets.
......@@ -453,11 +459,13 @@ Future<T> showModalBottomSheet<T>({
Clip clipBehavior,
bool isScrollControlled = false,
bool useRootNavigator = false,
bool isDismissible = true,
}) {
assert(context != null);
assert(builder != null);
assert(isScrollControlled != null);
assert(useRootNavigator != null);
assert(isDismissible != null);
assert(debugCheckHasMediaQuery(context));
assert(debugCheckHasMaterialLocalizations(context));
......@@ -470,6 +478,7 @@ Future<T> showModalBottomSheet<T>({
elevation: elevation,
shape: shape,
clipBehavior: clipBehavior,
isDismissible: isDismissible,
));
}
......
......@@ -14,12 +14,48 @@ void main() {
testWidgets('Tapping on a modal BottomSheet should not dismiss it', (WidgetTester tester) async {
BuildContext savedContext;
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
savedContext = context;
return Container();
},
),
),
);
await tester.pump();
expect(find.text('BottomSheet'), findsNothing);
bool showBottomSheetThenCalled = false;
showModalBottomSheet<void>(
context: savedContext,
builder: (BuildContext context) => const Text('BottomSheet'),
).then<void>((void value) {
showBottomSheetThenCalled = true;
});
await tester.pumpAndSettle();
expect(find.text('BottomSheet'), findsOneWidget);
expect(showBottomSheetThenCalled, isFalse);
// Tap on the bottom sheet itself, it should not be dismissed
await tester.tap(find.text('BottomSheet'));
await tester.pumpAndSettle();
expect(find.text('BottomSheet'), findsOneWidget);
expect(showBottomSheetThenCalled, isFalse);
});
testWidgets('Tapping outside a modal BottomSheet should dismiss it by default', (WidgetTester tester) async {
BuildContext savedContext;
await tester.pumpWidget(MaterialApp(
home: Builder(
builder: (BuildContext context) {
savedContext = context;
return Container();
}
},
),
));
......@@ -38,24 +74,61 @@ void main() {
expect(find.text('BottomSheet'), findsOneWidget);
expect(showBottomSheetThenCalled, isFalse);
// Tap on the bottom sheet itself, it should not be dismissed
await tester.tap(find.text('BottomSheet'));
// Tap above the bottom sheet to dismiss it.
await tester.tapAt(const Offset(20.0, 20.0));
await tester.pumpAndSettle(); // Bottom sheet dismiss animation.
expect(showBottomSheetThenCalled, isTrue);
expect(find.text('BottomSheet'), findsNothing);
});
testWidgets('Tapping outside a modal BottomSheet should dismiss it when isDismissible=true', (WidgetTester tester) async {
BuildContext savedContext;
await tester.pumpWidget(MaterialApp(
home: Builder(
builder: (BuildContext context) {
savedContext = context;
return Container();
},
),
));
await tester.pump();
expect(find.text('BottomSheet'), findsNothing);
bool showBottomSheetThenCalled = false;
showModalBottomSheet<void>(
context: savedContext,
builder: (BuildContext context) => const Text('BottomSheet'),
isDismissible: true,
).then<void>((void value) {
showBottomSheetThenCalled = true;
});
await tester.pumpAndSettle();
expect(find.text('BottomSheet'), findsOneWidget);
expect(showBottomSheetThenCalled, isFalse);
// Tap above the bottom sheet to dismiss it.
await tester.tapAt(const Offset(20.0, 20.0));
await tester.pumpAndSettle(); // Bottom sheet dismiss animation.
expect(showBottomSheetThenCalled, isTrue);
expect(find.text('BottomSheet'), findsNothing);
});
testWidgets('Tapping outside a modal BottomSheet should dismiss it', (WidgetTester tester) async {
testWidgets('Tapping outside a modal BottomSheet should not dismiss it when isDismissible=false', (WidgetTester tester) async {
BuildContext savedContext;
await tester.pumpWidget(MaterialApp(
home: Builder(
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
savedContext = context;
return Container();
}
},
),
),
));
);
await tester.pump();
expect(find.text('BottomSheet'), findsNothing);
......@@ -64,6 +137,7 @@ void main() {
showModalBottomSheet<void>(
context: savedContext,
builder: (BuildContext context) => const Text('BottomSheet'),
isDismissible: false,
).then<void>((void value) {
showBottomSheetThenCalled = true;
});
......@@ -72,13 +146,11 @@ void main() {
expect(find.text('BottomSheet'), findsOneWidget);
expect(showBottomSheetThenCalled, isFalse);
// Tap above the bottom sheet to dismiss it
// Tap above the bottom sheet, attempting to dismiss it.
await tester.tapAt(const Offset(20.0, 20.0));
await tester.pump(); // bottom sheet dismiss animation starts
expect(showBottomSheetThenCalled, isTrue);
await tester.pump(const Duration(seconds: 1)); // animation done
await tester.pump(const Duration(seconds: 1)); // rebuild frame
expect(find.text('BottomSheet'), findsNothing);
await tester.pumpAndSettle(); // Bottom sheet should not dismiss.
expect(showBottomSheetThenCalled, isFalse);
expect(find.text('BottomSheet'), findsOneWidget);
});
testWidgets('Verify that a downwards fling dismisses a persistent BottomSheet', (WidgetTester tester) async {
......
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