Unverified Commit 274435dd authored by Gregor Weber's avatar Gregor Weber Committed by GitHub

make ModalBottomSheetRoute public (#108112)

parent 50b1960a
...@@ -167,9 +167,8 @@ class BottomSheet extends StatefulWidget { ...@@ -167,9 +167,8 @@ class BottomSheet extends StatefulWidget {
/// Defines minimum and maximum sizes for a [BottomSheet]. /// Defines minimum and maximum sizes for a [BottomSheet].
/// ///
/// Typically a bottom sheet will cover the entire width of its /// Typically a bottom sheet will cover the entire width of its
/// parent. However for large screens you may want to limit the width /// parent. Consider limiting the width by setting smaller constraints
/// to something smaller and this property provides a way to specify /// for large screens.
/// a maximum width.
/// ///
/// If null, then the ambient [ThemeData.bottomSheetTheme]'s /// If null, then the ambient [ThemeData.bottomSheetTheme]'s
/// [BottomSheetThemeData.constraints] will be used. If that /// [BottomSheetThemeData.constraints] will be used. If that
...@@ -353,7 +352,7 @@ class _ModalBottomSheetLayout extends SingleChildLayoutDelegate { ...@@ -353,7 +352,7 @@ class _ModalBottomSheetLayout extends SingleChildLayoutDelegate {
class _ModalBottomSheet<T> extends StatefulWidget { class _ModalBottomSheet<T> extends StatefulWidget {
const _ModalBottomSheet({ const _ModalBottomSheet({
super.key, super.key,
this.route, required this.route,
this.backgroundColor, this.backgroundColor,
this.elevation, this.elevation,
this.shape, this.shape,
...@@ -364,7 +363,7 @@ class _ModalBottomSheet<T> extends StatefulWidget { ...@@ -364,7 +363,7 @@ class _ModalBottomSheet<T> extends StatefulWidget {
}) : assert(isScrollControlled != null), }) : assert(isScrollControlled != null),
assert(enableDrag != null); assert(enableDrag != null);
final _ModalBottomSheetRoute<T>? route; final ModalBottomSheetRoute<T> route;
final bool isScrollControlled; final bool isScrollControlled;
final Color? backgroundColor; final Color? backgroundColor;
final double? elevation; final double? elevation;
...@@ -401,7 +400,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> { ...@@ -401,7 +400,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
void handleDragEnd(DragEndDetails details, {bool? isClosing}) { void handleDragEnd(DragEndDetails details, {bool? isClosing}) {
// Allow the bottom sheet to animate smoothly from its current position. // Allow the bottom sheet to animate smoothly from its current position.
animationCurve = _BottomSheetSuspendedCurve( animationCurve = _BottomSheetSuspendedCurve(
widget.route!.animation!.value, widget.route.animation!.value,
curve: _modalBottomSheetCurve, curve: _modalBottomSheetCurve,
); );
} }
...@@ -415,15 +414,15 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> { ...@@ -415,15 +414,15 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
final String routeLabel = _getRouteLabel(localizations); final String routeLabel = _getRouteLabel(localizations);
return AnimatedBuilder( return AnimatedBuilder(
animation: widget.route!.animation!, animation: widget.route.animation!,
child: BottomSheet( child: BottomSheet(
animationController: widget.route!._animationController, animationController: widget.route._animationController,
onClosing: () { onClosing: () {
if (widget.route!.isCurrent) { if (widget.route.isCurrent) {
Navigator.pop(context); Navigator.pop(context);
} }
}, },
builder: widget.route!.builder!, builder: widget.route.builder,
backgroundColor: widget.backgroundColor, backgroundColor: widget.backgroundColor,
elevation: widget.elevation, elevation: widget.elevation,
shape: widget.shape, shape: widget.shape,
...@@ -437,7 +436,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> { ...@@ -437,7 +436,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
// Disable the initial animation when accessible navigation is on so // Disable the initial animation when accessible navigation is on so
// that the semantics are added to the tree at the correct time. // that the semantics are added to the tree at the correct time.
final double animationValue = animationCurve.transform( final double animationValue = animationCurve.transform(
mediaQuery.accessibleNavigation ? 1.0 : widget.route!.animation!.value, mediaQuery.accessibleNavigation ? 1.0 : widget.route.animation!.value,
); );
return Semantics( return Semantics(
scopesRoute: true, scopesRoute: true,
...@@ -456,10 +455,67 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> { ...@@ -456,10 +455,67 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
} }
} }
class _ModalBottomSheetRoute<T> extends PopupRoute<T> { /// A route that represents a Material Design modal bottom sheet.
_ModalBottomSheetRoute({ ///
this.builder, /// {@template flutter.material.ModalBottomSheetRoute}
required this.capturedThemes, /// A modal bottom sheet is an alternative to a menu or a dialog and prevents
/// the user from interacting with the rest of the app.
///
/// A closely related widget is a persistent bottom sheet, which shows
/// information that supplements the primary content of the app without
/// preventing the user from interacting with the app. Persistent bottom sheets
/// can be created and displayed with the [showBottomSheet] function or the
/// [ScaffoldState.showBottomSheet] method.
///
/// The [isScrollControlled] parameter specifies whether this is a route for
/// a bottom sheet that will utilize [DraggableScrollableSheet]. Consider
/// setting this parameter to true if this bottom sheet has
/// a scrollable child, such as a [ListView] or a [GridView],
/// to have the bottom sheet be draggable.
///
/// The [isDismissible] parameter specifies whether the bottom sheet will be
/// dismissed when user taps on the scrim.
///
/// 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
/// modal bottom sheets (see the documentation for these on [BottomSheet]
/// for more details).
///
/// The [transitionAnimationController] controls the bottom sheet's entrance and
/// exit animations. It's up to the owner of the controller to call
/// [AnimationController.dispose] when the controller is no longer needed.
///
/// The optional `settings` parameter sets the [RouteSettings] of the modal bottom sheet
/// sheet. This is particularly useful in the case that a user wants to observe
/// [PopupRoute]s within a [NavigatorObserver].
/// {@endtemplate}
///
/// {@macro flutter.widgets.RawDialogRoute}
///
/// See also:
///
/// * [showModalBottomSheet], which is a way to display a ModalBottomSheetRoute.
/// * [BottomSheet], which becomes the parent of the widget returned by the
/// function passed as the `builder` argument to [showModalBottomSheet].
/// * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing
/// non-modal bottom sheets.
/// * [DraggableScrollableSheet], creates a bottom sheet that grows
/// and then becomes scrollable once it reaches its maximum size.
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
/// * <https://material.io/design/components/sheets-bottom.html#modal-bottom-sheet>
class ModalBottomSheetRoute<T> extends PopupRoute<T> {
/// A modal bottom sheet route.
ModalBottomSheetRoute({
required this.builder,
this.capturedThemes,
this.barrierLabel, this.barrierLabel,
this.backgroundColor, this.backgroundColor,
this.elevation, this.elevation,
...@@ -478,19 +534,117 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> { ...@@ -478,19 +534,117 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
assert(isDismissible != null), assert(isDismissible != null),
assert(enableDrag != null); assert(enableDrag != null);
final WidgetBuilder? builder; /// A builder for the contents of the sheet.
final CapturedThemes capturedThemes; ///
/// The bottom sheet will wrap the widget produced by this builder in a
/// [Material] widget.
final WidgetBuilder builder;
/// Stores a list of captured [InheritedTheme]s that are wrapped around the
/// bottom sheet.
///
/// Consider setting this attribute when the [ModalBottomSheetRoute]
/// is created through [Navigator.push] and its friends.
final CapturedThemes? capturedThemes;
/// Specifies whether this is a route for a bottom sheet that will utilize
/// [DraggableScrollableSheet].
///
/// Consider setting this parameter to true if this bottom sheet has
/// a scrollable child, such as a [ListView] or a [GridView],
/// to have the bottom sheet be draggable.
final bool isScrollControlled; final bool isScrollControlled;
/// The bottom sheet's background color.
///
/// Defines the bottom sheet's [Material.color].
///
/// If this property is not provided, it falls back to [Material]'s default.
final Color? backgroundColor; final Color? backgroundColor;
/// The z-coordinate at which to place this material relative to its parent.
///
/// This controls the size of the shadow below the material.
///
/// Defaults to 0, must not be negative.
final double? elevation; final double? elevation;
/// The shape of the bottom sheet.
///
/// Defines the bottom sheet's [Material.shape].
///
/// If this property is not provided, it falls back to [Material]'s default.
final ShapeBorder? shape; final ShapeBorder? shape;
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defines the bottom sheet's [Material.clipBehavior].
///
/// Use this property to enable clipping of content when the bottom sheet has
/// a custom [shape] and the content can extend past this shape. For example,
/// a bottom sheet with rounded corners and an edge-to-edge [Image] at the
/// top.
///
/// If this property is null, the [BottomSheetThemeData.clipBehavior] of
/// [ThemeData.bottomSheetTheme] is used. If that's null, the behavior defaults to [Clip.none]
/// will be [Clip.none].
final Clip? clipBehavior; final Clip? clipBehavior;
/// Defines minimum and maximum sizes for a [BottomSheet].
///
/// Typically a bottom sheet will cover the entire width of its
/// parent. Consider limiting the width by setting smaller constraints
/// for large screens.
///
/// If null, the ambient [ThemeData.bottomSheetTheme]'s
/// [BottomSheetThemeData.constraints] will be used. If that
/// is null, the bottom sheet's size will be constrained
/// by its parent (usually a [Scaffold]).
///
/// If constraints are specified (either in this property or in the
/// theme), the bottom sheet will be aligned to the bottom-center of
/// the available space. Otherwise, no alignment is applied.
final BoxConstraints? constraints; final BoxConstraints? constraints;
/// Specifies the color of the modal barrier that darkens everything below the
/// bottom sheet.
///
/// Defaults to `Colors.black54` if not provided.
final Color? modalBarrierColor; final Color? modalBarrierColor;
/// Specifies whether the bottom sheet will be dismissed
/// when user taps on the scrim.
///
/// If true, the bottom sheet will be dismissed when user taps on the scrim.
///
/// Defaults to true.
final bool isDismissible; final bool isDismissible;
/// Specifies whether the bottom sheet can be dragged up and down
/// and dismissed by swiping downwards.
///
/// If true, the bottom sheet can be dragged up and down and dismissed by
/// swiping downwards.
///
/// Defaults is true.
final bool enableDrag; final bool enableDrag;
/// The animation controller that controls the bottom sheet's entrance and
/// exit animations.
///
/// The BottomSheet widget will manipulate the position of this animation, it
/// is not just a passive observer.
final AnimationController? transitionAnimationController; final AnimationController? transitionAnimationController;
/// {@macro flutter.widgets.DisplayFeatureSubScreen.anchorPoint}
final Offset? anchorPoint; final Offset? anchorPoint;
/// 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.
///
/// Default is false.
final bool useSafeArea; final bool useSafeArea;
@override @override
...@@ -555,7 +709,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> { ...@@ -555,7 +709,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
child: content, child: content,
); );
return capturedThemes.wrap(bottomSheet); return capturedThemes?.wrap(bottomSheet) ?? bottomSheet;
} }
} }
...@@ -619,56 +773,20 @@ class _BottomSheetSuspendedCurve extends ParametricCurve<double> { ...@@ -619,56 +773,20 @@ class _BottomSheetSuspendedCurve extends ParametricCurve<double> {
/// Shows a modal Material Design bottom sheet. /// Shows a modal Material Design bottom sheet.
/// ///
/// A modal bottom sheet is an alternative to a menu or a dialog and prevents /// {@macro flutter.material.ModalBottomSheetRoute}
/// the user from interacting with the rest of the app.
/// ///
/// A closely related widget is a persistent bottom sheet, which shows /// {@macro flutter.widgets.RawDialogRoute}
/// information that supplements the primary content of the app without
/// preventing the user from interacting with the app. Persistent bottom sheets
/// can be created and displayed with the [showBottomSheet] function or the
/// [ScaffoldState.showBottomSheet] method.
/// ///
/// The `context` argument is used to look up the [Navigator] and [Theme] for /// The `context` argument is used to look up the [Navigator] and [Theme] for
/// the bottom sheet. It is only used when the method is called. Its /// the bottom sheet. It is only used when the method is called. Its
/// corresponding widget can be safely removed from the tree before the bottom /// corresponding widget can be safely removed from the tree before the bottom
/// sheet is closed. /// sheet is closed.
/// ///
/// The `isScrollControlled` parameter specifies whether this is a route for
/// a bottom sheet that will utilize [DraggableScrollableSheet]. If you wish
/// to have a bottom sheet that has a scrollable child such as a [ListView] or
/// a [GridView] and have the bottom sheet be draggable, you should set this
/// parameter to true.
///
/// The `useRootNavigator` parameter ensures that the root navigator is used to /// The `useRootNavigator` parameter ensures that the root navigator is used to
/// display the [BottomSheet] when set to `true`. This is useful in the case /// display the [BottomSheet] when set to `true`. This is useful in the case
/// 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 [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
/// modal bottom sheets (see the documentation for these on [BottomSheet]
/// for more details).
///
/// The [transitionAnimationController] controls the bottom sheet's entrance and
/// exit animations. It's up to the owner of the controller to call
/// [AnimationController.dispose] when the controller is no longer needed.
///
/// The optional `routeSettings` parameter sets the [RouteSettings] of the modal bottom sheet
/// sheet. This is particularly useful in the case that a user wants to observe
/// [PopupRoute]s within a [NavigatorObserver].
///
/// {@macro flutter.widgets.RawDialogRoute}
///
/// Returns a `Future` that resolves to the value (if any) that was passed to /// Returns a `Future` that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the modal bottom sheet was closed. /// [Navigator.pop] when the modal bottom sheet was closed.
/// ///
...@@ -687,8 +805,8 @@ class _BottomSheetSuspendedCurve extends ParametricCurve<double> { ...@@ -687,8 +805,8 @@ class _BottomSheetSuspendedCurve extends ParametricCurve<double> {
/// function passed as the `builder` argument to [showModalBottomSheet]. /// function passed as the `builder` argument to [showModalBottomSheet].
/// * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing /// * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing
/// non-modal bottom sheets. /// non-modal bottom sheets.
/// * [DraggableScrollableSheet], which allows you to create a bottom sheet /// * [DraggableScrollableSheet], creates a bottom sheet that grows
/// that grows and then becomes scrollable once it reaches its maximum size. /// and then becomes scrollable once it reaches its maximum size.
/// * [DisplayFeatureSubScreen], which documents the specifics of how /// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens. /// [DisplayFeature]s can split the screen into sub-screens.
/// * <https://material.io/design/components/sheets-bottom.html#modal-bottom-sheet> /// * <https://material.io/design/components/sheets-bottom.html#modal-bottom-sheet>
...@@ -720,7 +838,7 @@ Future<T?> showModalBottomSheet<T>({ ...@@ -720,7 +838,7 @@ Future<T?> showModalBottomSheet<T>({
assert(debugCheckHasMaterialLocalizations(context)); assert(debugCheckHasMaterialLocalizations(context));
final NavigatorState navigator = Navigator.of(context, rootNavigator: useRootNavigator); final NavigatorState navigator = Navigator.of(context, rootNavigator: useRootNavigator);
return navigator.push(_ModalBottomSheetRoute<T>( return navigator.push(ModalBottomSheetRoute<T>(
builder: builder, builder: builder,
capturedThemes: InheritedTheme.capture(from: context, to: navigator.context), capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
isScrollControlled: isScrollControlled, isScrollControlled: isScrollControlled,
...@@ -740,8 +858,8 @@ Future<T?> showModalBottomSheet<T>({ ...@@ -740,8 +858,8 @@ Future<T?> showModalBottomSheet<T>({
)); ));
} }
/// Shows a Material Design bottom sheet in the nearest [Scaffold] ancestor. If /// Shows a Material Design bottom sheet in the nearest [Scaffold] ancestor. To
/// you wish to show a persistent bottom sheet, use [Scaffold.bottomSheet]. /// show a persistent bottom sheet, use the [Scaffold.bottomSheet].
/// ///
/// Returns a controller that can be used to close and otherwise manipulate the /// Returns a controller that can be used to close and otherwise manipulate the
/// bottom sheet. /// bottom sheet.
......
...@@ -1399,6 +1399,35 @@ void main() { ...@@ -1399,6 +1399,35 @@ void main() {
expect(find.text('BottomSheet 2'), findsOneWidget); expect(find.text('BottomSheet 2'), findsOneWidget);
}); });
testWidgets('ModalBottomSheetRoute shows BottomSheet correctly', (WidgetTester tester) async {
late BuildContext savedContext;
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
savedContext = context;
return Container();
},
),
),
);
await tester.pump();
expect(find.byType(BottomSheet), findsNothing);
// Bring up bottom sheet.
final NavigatorState navigator = Navigator.of(savedContext);
navigator.push(
ModalBottomSheetRoute<void>(
isScrollControlled: false,
builder: (BuildContext context) => Container(),
),
);
await tester.pumpAndSettle();
expect(find.byType(BottomSheet), findsOneWidget);
});
group('Modal BottomSheet avoids overlapping display features', () { group('Modal BottomSheet avoids overlapping display features', () {
testWidgets('positioning using anchorPoint', (WidgetTester tester) async { testWidgets('positioning using anchorPoint', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
......
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