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
This commit is contained in:
committed by
Shi-Hao Hong
parent
ba70e0e074
commit
dd43da71fe
@@ -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,14 +14,16 @@ 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.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Builder(
|
||||
builder: (BuildContext context) {
|
||||
savedContext = context;
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
expect(find.text('BottomSheet'), findsNothing);
|
||||
@@ -45,15 +47,15 @@ void main() {
|
||||
expect(showBottomSheetThenCalled, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('Tapping outside a modal BottomSheet should dismiss it', (WidgetTester tester) async {
|
||||
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();
|
||||
}
|
||||
builder: (BuildContext context) {
|
||||
savedContext = context;
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
));
|
||||
|
||||
@@ -72,15 +74,85 @@ void main() {
|
||||
expect(find.text('BottomSheet'), findsOneWidget);
|
||||
expect(showBottomSheetThenCalled, isFalse);
|
||||
|
||||
// Tap above the bottom sheet to dismiss it
|
||||
// Tap above the bottom sheet to dismiss it.
|
||||
await tester.tapAt(const Offset(20.0, 20.0));
|
||||
await tester.pump(); // bottom sheet dismiss animation starts
|
||||
await tester.pumpAndSettle(); // Bottom sheet dismiss animation.
|
||||
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);
|
||||
});
|
||||
|
||||
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 not dismiss it when isDismissible=false', (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: false,
|
||||
).then<void>((void value) {
|
||||
showBottomSheetThenCalled = true;
|
||||
});
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('BottomSheet'), findsOneWidget);
|
||||
expect(showBottomSheetThenCalled, isFalse);
|
||||
|
||||
// Tap above the bottom sheet, attempting to dismiss it.
|
||||
await tester.tapAt(const Offset(20.0, 20.0));
|
||||
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 {
|
||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
bool showBottomSheetThenCalled = false;
|
||||
|
||||
Reference in New Issue
Block a user