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