"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter/lib/src/material/scaffold.dart" (13 Nov 2020, 107841 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 // Copyright 2014 The Flutter Authors. All rights reserved.
    2 // Use of this source code is governed by a BSD-style license that can be
    3 // found in the LICENSE file.
    4 
    5 // @dart = 2.8
    6 
    7 import 'dart:async';
    8 import 'dart:collection';
    9 import 'dart:math' as math;
   10 import 'dart:ui' show lerpDouble;
   11 
   12 import 'package:flutter/foundation.dart';
   13 import 'package:flutter/rendering.dart';
   14 import 'package:flutter/widgets.dart';
   15 import 'package:flutter/gestures.dart' show DragStartBehavior;
   16 
   17 import 'app_bar.dart';
   18 import 'bottom_sheet.dart';
   19 import 'button_bar.dart';
   20 import 'colors.dart';
   21 import 'curves.dart';
   22 import 'divider.dart';
   23 import 'drawer.dart';
   24 import 'flexible_space_bar.dart';
   25 import 'floating_action_button.dart';
   26 import 'floating_action_button_location.dart';
   27 import 'material.dart';
   28 import 'snack_bar.dart';
   29 import 'snack_bar_theme.dart';
   30 import 'theme.dart';
   31 import 'theme_data.dart';
   32 
   33 // Examples can assume:
   34 // TabController tabController;
   35 // void setState(VoidCallback fn) { }
   36 // String appBarTitle;
   37 // int tabCount;
   38 // TickerProvider tickerProvider;
   39 
   40 const FloatingActionButtonLocation _kDefaultFloatingActionButtonLocation = FloatingActionButtonLocation.endFloat;
   41 const FloatingActionButtonAnimator _kDefaultFloatingActionButtonAnimator = FloatingActionButtonAnimator.scaling;
   42 
   43 const Curve _standardBottomSheetCurve = standardEasing;
   44 // When the top of the BottomSheet crosses this threshold, it will start to
   45 // shrink the FAB and show a scrim.
   46 const double _kBottomSheetDominatesPercentage = 0.3;
   47 const double _kMinBottomSheetScrimOpacity = 0.1;
   48 const double _kMaxBottomSheetScrimOpacity = 0.6;
   49 
   50 enum _ScaffoldSlot {
   51   body,
   52   appBar,
   53   bodyScrim,
   54   bottomSheet,
   55   snackBar,
   56   persistentFooter,
   57   bottomNavigationBar,
   58   floatingActionButton,
   59   drawer,
   60   endDrawer,
   61   statusBar,
   62 }
   63 
   64 /// The geometry of the [Scaffold] after all its contents have been laid out
   65 /// except the [FloatingActionButton].
   66 ///
   67 /// The [Scaffold] passes this pre-layout geometry to its
   68 /// [FloatingActionButtonLocation], which produces an [Offset] that the
   69 /// [Scaffold] uses to position the [FloatingActionButton].
   70 ///
   71 /// For a description of the [Scaffold]'s geometry after it has
   72 /// finished laying out, see the [ScaffoldGeometry].
   73 @immutable
   74 class ScaffoldPrelayoutGeometry {
   75   /// Abstract const constructor. This constructor enables subclasses to provide
   76   /// const constructors so that they can be used in const expressions.
   77   const ScaffoldPrelayoutGeometry({
   78     @required this.bottomSheetSize,
   79     @required this.contentBottom,
   80     @required this.contentTop,
   81     @required this.floatingActionButtonSize,
   82     @required this.minInsets,
   83     @required this.minViewPadding,
   84     @required this.scaffoldSize,
   85     @required this.snackBarSize,
   86     @required this.textDirection,
   87   });
   88 
   89   /// The [Size] of [Scaffold.floatingActionButton].
   90   ///
   91   /// If [Scaffold.floatingActionButton] is null, this will be [Size.zero].
   92   final Size floatingActionButtonSize;
   93 
   94   /// The [Size] of the [Scaffold]'s [BottomSheet].
   95   ///
   96   /// If the [Scaffold] is not currently showing a [BottomSheet],
   97   /// this will be [Size.zero].
   98   final Size bottomSheetSize;
   99 
  100   /// The vertical distance from the Scaffold's origin to the bottom of
  101   /// [Scaffold.body].
  102   ///
  103   /// This is useful in a [FloatingActionButtonLocation] designed to
  104   /// place the [FloatingActionButton] at the bottom of the screen, while
  105   /// keeping it above the [BottomSheet], the [Scaffold.bottomNavigationBar],
  106   /// or the keyboard.
  107   ///
  108   /// The [Scaffold.body] is laid out with respect to [minInsets] already. This
  109   /// means that a [FloatingActionButtonLocation] does not need to factor in
  110   /// [EdgeInsets.bottom] of [minInsets] when aligning a [FloatingActionButton]
  111   /// to [contentBottom].
  112   final double contentBottom;
  113 
  114   /// The vertical distance from the [Scaffold]'s origin to the top of
  115   /// [Scaffold.body].
  116   ///
  117   /// This is useful in a [FloatingActionButtonLocation] designed to
  118   /// place the [FloatingActionButton] at the top of the screen, while
  119   /// keeping it below the [Scaffold.appBar].
  120   ///
  121   /// The [Scaffold.body] is laid out with respect to [minInsets] already. This
  122   /// means that a [FloatingActionButtonLocation] does not need to factor in
  123   /// [EdgeInsets.top] of [minInsets] when aligning a [FloatingActionButton] to
  124   /// [contentTop].
  125   final double contentTop;
  126 
  127   /// The minimum padding to inset the [FloatingActionButton] by for it
  128   /// to remain visible.
  129   ///
  130   /// This value is the result of calling [MediaQueryData.padding] in the
  131   /// [Scaffold]'s [BuildContext],
  132   /// and is useful for insetting the [FloatingActionButton] to avoid features like
  133   /// the system status bar or the keyboard.
  134   ///
  135   /// If [Scaffold.resizeToAvoidBottomInset] is set to false,
  136   /// [EdgeInsets.bottom] of [minInsets] will be 0.0.
  137   final EdgeInsets minInsets;
  138 
  139   /// The minimum padding to inset interactive elements to be within a safe,
  140   /// un-obscured space.
  141   ///
  142   /// This value reflects the [MediaQueryData.viewPadding] of the [Scaffold]'s
  143   /// [BuildContext] when [Scaffold.resizeToAvoidBottomInset] is false or and
  144   /// the [MediaQueryData.viewInsets] > 0.0. This helps distinguish between
  145   /// different types of obstructions on the screen, such as software keyboards
  146   /// and physical device notches.
  147   final EdgeInsets minViewPadding;
  148 
  149   /// The [Size] of the whole [Scaffold].
  150   ///
  151   /// If the [Size] of the [Scaffold]'s contents is modified by values such as
  152   /// [Scaffold.resizeToAvoidBottomInset] or the keyboard opening, then the
  153   /// [scaffoldSize] will not reflect those changes.
  154   ///
  155   /// This means that [FloatingActionButtonLocation]s designed to reposition
  156   /// the [FloatingActionButton] based on events such as the keyboard popping
  157   /// up should use [minInsets] to make sure that the [FloatingActionButton] is
  158   /// inset by enough to remain visible.
  159   ///
  160   /// See [minInsets] and [MediaQueryData.padding] for more information on the
  161   /// appropriate insets to apply.
  162   final Size scaffoldSize;
  163 
  164   /// The [Size] of the [Scaffold]'s [SnackBar].
  165   ///
  166   /// If the [Scaffold] is not showing a [SnackBar], this will be [Size.zero].
  167   final Size snackBarSize;
  168 
  169   /// The [TextDirection] of the [Scaffold]'s [BuildContext].
  170   final TextDirection textDirection;
  171 }
  172 
  173 /// A snapshot of a transition between two [FloatingActionButtonLocation]s.
  174 ///
  175 /// [ScaffoldState] uses this to seamlessly change transition animations
  176 /// when a running [FloatingActionButtonLocation] transition is interrupted by a new transition.
  177 @immutable
  178 class _TransitionSnapshotFabLocation extends FloatingActionButtonLocation {
  179 
  180   const _TransitionSnapshotFabLocation(this.begin, this.end, this.animator, this.progress);
  181 
  182   final FloatingActionButtonLocation begin;
  183   final FloatingActionButtonLocation end;
  184   final FloatingActionButtonAnimator animator;
  185   final double progress;
  186 
  187   @override
  188   Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
  189     return animator.getOffset(
  190       begin: begin.getOffset(scaffoldGeometry),
  191       end: end.getOffset(scaffoldGeometry),
  192       progress: progress,
  193     );
  194   }
  195 
  196   @override
  197   String toString() {
  198     return '${objectRuntimeType(this, '_TransitionSnapshotFabLocation')}(begin: $begin, end: $end, progress: $progress)';
  199   }
  200 }
  201 
  202 /// Geometry information for [Scaffold] components after layout is finished.
  203 ///
  204 /// To get a [ValueNotifier] for the scaffold geometry of a given
  205 /// [BuildContext], use [Scaffold.geometryOf].
  206 ///
  207 /// The ScaffoldGeometry is only available during the paint phase, because
  208 /// its value is computed during the animation and layout phases prior to painting.
  209 ///
  210 /// For an example of using the [ScaffoldGeometry], see the [BottomAppBar],
  211 /// which uses the [ScaffoldGeometry] to paint a notch around the
  212 /// [FloatingActionButton].
  213 ///
  214 /// For information about the [Scaffold]'s geometry that is used while laying
  215 /// out the [FloatingActionButton], see [ScaffoldPrelayoutGeometry].
  216 @immutable
  217 class ScaffoldGeometry {
  218   /// Create an object that describes the geometry of a [Scaffold].
  219   const ScaffoldGeometry({
  220     this.bottomNavigationBarTop,
  221     this.floatingActionButtonArea,
  222   });
  223 
  224   /// The distance from the [Scaffold]'s top edge to the top edge of the
  225   /// rectangle in which the [Scaffold.bottomNavigationBar] bar is laid out.
  226   ///
  227   /// Null if [Scaffold.bottomNavigationBar] is null.
  228   final double bottomNavigationBarTop;
  229 
  230   /// The [Scaffold.floatingActionButton]'s bounding rectangle.
  231   ///
  232   /// This is null when there is no floating action button showing.
  233   final Rect floatingActionButtonArea;
  234 
  235   ScaffoldGeometry _scaleFloatingActionButton(double scaleFactor) {
  236     if (scaleFactor == 1.0)
  237       return this;
  238 
  239     if (scaleFactor == 0.0) {
  240       return ScaffoldGeometry(
  241         bottomNavigationBarTop: bottomNavigationBarTop,
  242       );
  243     }
  244 
  245     final Rect scaledButton = Rect.lerp(
  246       floatingActionButtonArea.center & Size.zero,
  247       floatingActionButtonArea,
  248       scaleFactor,
  249     );
  250     return copyWith(floatingActionButtonArea: scaledButton);
  251   }
  252 
  253   /// Creates a copy of this [ScaffoldGeometry] but with the given fields replaced with
  254   /// the new values.
  255   ScaffoldGeometry copyWith({
  256     double bottomNavigationBarTop,
  257     Rect floatingActionButtonArea,
  258   }) {
  259     return ScaffoldGeometry(
  260       bottomNavigationBarTop: bottomNavigationBarTop ?? this.bottomNavigationBarTop,
  261       floatingActionButtonArea: floatingActionButtonArea ?? this.floatingActionButtonArea,
  262     );
  263   }
  264 }
  265 
  266 class _ScaffoldGeometryNotifier extends ChangeNotifier implements ValueListenable<ScaffoldGeometry> {
  267   _ScaffoldGeometryNotifier(this.geometry, this.context)
  268     : assert (context != null);
  269 
  270   final BuildContext context;
  271   double floatingActionButtonScale;
  272   ScaffoldGeometry geometry;
  273 
  274   @override
  275   ScaffoldGeometry get value {
  276     assert(() {
  277       final RenderObject renderObject = context.findRenderObject();
  278       if (renderObject == null || !renderObject.owner.debugDoingPaint)
  279         throw FlutterError(
  280             'Scaffold.geometryOf() must only be accessed during the paint phase.\n'
  281             'The ScaffoldGeometry is only available during the paint phase, because '
  282             'its value is computed during the animation and layout phases prior to painting.'
  283         );
  284       return true;
  285     }());
  286     return geometry._scaleFloatingActionButton(floatingActionButtonScale);
  287   }
  288 
  289   void _updateWith({
  290     double bottomNavigationBarTop,
  291     Rect floatingActionButtonArea,
  292     double floatingActionButtonScale,
  293   }) {
  294     this.floatingActionButtonScale = floatingActionButtonScale ?? this.floatingActionButtonScale;
  295     geometry = geometry.copyWith(
  296       bottomNavigationBarTop: bottomNavigationBarTop,
  297       floatingActionButtonArea: floatingActionButtonArea,
  298     );
  299     notifyListeners();
  300   }
  301 }
  302 
  303 // Used to communicate the height of the Scaffold's bottomNavigationBar and
  304 // persistentFooterButtons to the LayoutBuilder which builds the Scaffold's body.
  305 //
  306 // Scaffold expects a _BodyBoxConstraints to be passed to the _BodyBuilder
  307 // widget's LayoutBuilder, see _ScaffoldLayout.performLayout(). The BoxConstraints
  308 // methods that construct new BoxConstraints objects, like copyWith() have not
  309 // been overridden here because we expect the _BodyBoxConstraintsObject to be
  310 // passed along unmodified to the LayoutBuilder. If that changes in the future
  311 // then _BodyBuilder will assert.
  312 class _BodyBoxConstraints extends BoxConstraints {
  313   const _BodyBoxConstraints({
  314     double minWidth = 0.0,
  315     double maxWidth = double.infinity,
  316     double minHeight = 0.0,
  317     double maxHeight = double.infinity,
  318     @required this.bottomWidgetsHeight,
  319     @required this.appBarHeight,
  320   }) : assert(bottomWidgetsHeight != null),
  321        assert(bottomWidgetsHeight >= 0),
  322        assert(appBarHeight != null),
  323        assert(appBarHeight >= 0),
  324        super(minWidth: minWidth, maxWidth: maxWidth, minHeight: minHeight, maxHeight: maxHeight);
  325 
  326   final double bottomWidgetsHeight;
  327   final double appBarHeight;
  328 
  329   // RenderObject.layout() will only short-circuit its call to its performLayout
  330   // method if the new layout constraints are not == to the current constraints.
  331   // If the height of the bottom widgets has changed, even though the constraints'
  332   // min and max values have not, we still want performLayout to happen.
  333   @override
  334   bool operator ==(Object other) {
  335     if (super != other)
  336       return false;
  337     return other is _BodyBoxConstraints
  338         && other.bottomWidgetsHeight == bottomWidgetsHeight
  339         && other.appBarHeight == appBarHeight;
  340   }
  341 
  342   @override
  343   int get hashCode {
  344     return hashValues(super.hashCode, bottomWidgetsHeight, appBarHeight);
  345   }
  346 }
  347 
  348 // Used when Scaffold.extendBody is true to wrap the scaffold's body in a MediaQuery
  349 // whose padding accounts for the height of the bottomNavigationBar and/or the
  350 // persistentFooterButtons.
  351 //
  352 // The bottom widgets' height is passed along via the _BodyBoxConstraints parameter.
  353 // The constraints parameter is constructed in_ScaffoldLayout.performLayout().
  354 class _BodyBuilder extends StatelessWidget {
  355   const _BodyBuilder({
  356     Key key,
  357     @required this.extendBody,
  358     @required this.extendBodyBehindAppBar,
  359     @required this.body,
  360   }) : assert(extendBody != null),
  361        assert(extendBodyBehindAppBar != null),
  362        assert(body != null),
  363        super(key: key);
  364 
  365   final Widget body;
  366   final bool extendBody;
  367   final bool extendBodyBehindAppBar;
  368 
  369   @override
  370   Widget build(BuildContext context) {
  371     if (!extendBody && !extendBodyBehindAppBar)
  372       return body;
  373 
  374     return LayoutBuilder(
  375       builder: (BuildContext context, BoxConstraints constraints) {
  376         final _BodyBoxConstraints bodyConstraints = constraints as _BodyBoxConstraints;
  377         final MediaQueryData metrics = MediaQuery.of(context);
  378 
  379         final double bottom = extendBody
  380           ? math.max(metrics.padding.bottom, bodyConstraints.bottomWidgetsHeight)
  381           : metrics.padding.bottom;
  382 
  383         final double top = extendBodyBehindAppBar
  384           ? math.max(metrics.padding.top, bodyConstraints.appBarHeight)
  385           : metrics.padding.top;
  386 
  387         return MediaQuery(
  388           data: metrics.copyWith(
  389             padding: metrics.padding.copyWith(
  390               top: top,
  391               bottom: bottom,
  392             ),
  393           ),
  394           child: body,
  395         );
  396       },
  397     );
  398   }
  399 }
  400 
  401 class _ScaffoldLayout extends MultiChildLayoutDelegate {
  402   _ScaffoldLayout({
  403     @required this.minInsets,
  404     @required this.minViewPadding,
  405     @required this.textDirection,
  406     @required this.geometryNotifier,
  407     // for floating action button
  408     @required this.previousFloatingActionButtonLocation,
  409     @required this.currentFloatingActionButtonLocation,
  410     @required this.floatingActionButtonMoveAnimationProgress,
  411     @required this.floatingActionButtonMotionAnimator,
  412     @required this.isSnackBarFloating,
  413     @required this.snackBarWidth,
  414     @required this.extendBody,
  415     @required this.extendBodyBehindAppBar,
  416   }) : assert(minInsets != null),
  417        assert(textDirection != null),
  418        assert(geometryNotifier != null),
  419        assert(previousFloatingActionButtonLocation != null),
  420        assert(currentFloatingActionButtonLocation != null),
  421        assert(extendBody != null),
  422        assert(extendBodyBehindAppBar != null);
  423 
  424   final bool extendBody;
  425   final bool extendBodyBehindAppBar;
  426   final EdgeInsets minInsets;
  427   final EdgeInsets minViewPadding;
  428   final TextDirection textDirection;
  429   final _ScaffoldGeometryNotifier geometryNotifier;
  430 
  431   final FloatingActionButtonLocation previousFloatingActionButtonLocation;
  432   final FloatingActionButtonLocation currentFloatingActionButtonLocation;
  433   final double floatingActionButtonMoveAnimationProgress;
  434   final FloatingActionButtonAnimator floatingActionButtonMotionAnimator;
  435 
  436   final bool isSnackBarFloating;
  437   final double snackBarWidth;
  438 
  439   @override
  440   void performLayout(Size size) {
  441     final BoxConstraints looseConstraints = BoxConstraints.loose(size);
  442 
  443     // This part of the layout has the same effect as putting the app bar and
  444     // body in a column and making the body flexible. What's different is that
  445     // in this case the app bar appears _after_ the body in the stacking order,
  446     // so the app bar's shadow is drawn on top of the body.
  447 
  448     final BoxConstraints fullWidthConstraints = looseConstraints.tighten(width: size.width);
  449     final double bottom = size.height;
  450     double contentTop = 0.0;
  451     double bottomWidgetsHeight = 0.0;
  452     double appBarHeight = 0.0;
  453 
  454     if (hasChild(_ScaffoldSlot.appBar)) {
  455       appBarHeight = layoutChild(_ScaffoldSlot.appBar, fullWidthConstraints).height;
  456       contentTop = extendBodyBehindAppBar ? 0.0 : appBarHeight;
  457       positionChild(_ScaffoldSlot.appBar, Offset.zero);
  458     }
  459 
  460     double bottomNavigationBarTop;
  461     if (hasChild(_ScaffoldSlot.bottomNavigationBar)) {
  462       final double bottomNavigationBarHeight = layoutChild(_ScaffoldSlot.bottomNavigationBar, fullWidthConstraints).height;
  463       bottomWidgetsHeight += bottomNavigationBarHeight;
  464       bottomNavigationBarTop = math.max(0.0, bottom - bottomWidgetsHeight);
  465       positionChild(_ScaffoldSlot.bottomNavigationBar, Offset(0.0, bottomNavigationBarTop));
  466     }
  467 
  468     if (hasChild(_ScaffoldSlot.persistentFooter)) {
  469       final BoxConstraints footerConstraints = BoxConstraints(
  470         maxWidth: fullWidthConstraints.maxWidth,
  471         maxHeight: math.max(0.0, bottom - bottomWidgetsHeight - contentTop),
  472       );
  473       final double persistentFooterHeight = layoutChild(_ScaffoldSlot.persistentFooter, footerConstraints).height;
  474       bottomWidgetsHeight += persistentFooterHeight;
  475       positionChild(_ScaffoldSlot.persistentFooter, Offset(0.0, math.max(0.0, bottom - bottomWidgetsHeight)));
  476     }
  477 
  478     // Set the content bottom to account for the greater of the height of any
  479     // bottom-anchored material widgets or of the keyboard or other
  480     // bottom-anchored system UI.
  481     final double contentBottom = math.max(0.0, bottom - math.max(minInsets.bottom, bottomWidgetsHeight));
  482 
  483     if (hasChild(_ScaffoldSlot.body)) {
  484       double bodyMaxHeight = math.max(0.0, contentBottom - contentTop);
  485 
  486       if (extendBody) {
  487         bodyMaxHeight += bottomWidgetsHeight;
  488         bodyMaxHeight = bodyMaxHeight.clamp(0.0, looseConstraints.maxHeight - contentTop).toDouble();
  489         assert(bodyMaxHeight <= math.max(0.0, looseConstraints.maxHeight - contentTop));
  490       }
  491 
  492       final BoxConstraints bodyConstraints = _BodyBoxConstraints(
  493         maxWidth: fullWidthConstraints.maxWidth,
  494         maxHeight: bodyMaxHeight,
  495         bottomWidgetsHeight: extendBody ? bottomWidgetsHeight : 0.0,
  496         appBarHeight: appBarHeight,
  497       );
  498       layoutChild(_ScaffoldSlot.body, bodyConstraints);
  499       positionChild(_ScaffoldSlot.body, Offset(0.0, contentTop));
  500     }
  501 
  502     // The BottomSheet and the SnackBar are anchored to the bottom of the parent,
  503     // they're as wide as the parent and are given their intrinsic height. The
  504     // only difference is that SnackBar appears on the top side of the
  505     // BottomNavigationBar while the BottomSheet is stacked on top of it.
  506     //
  507     // If all three elements are present then either the center of the FAB straddles
  508     // the top edge of the BottomSheet or the bottom of the FAB is
  509     // kFloatingActionButtonMargin above the SnackBar, whichever puts the FAB
  510     // the farthest above the bottom of the parent. If only the FAB is has a
  511     // non-zero height then it's inset from the parent's right and bottom edges
  512     // by kFloatingActionButtonMargin.
  513 
  514     Size bottomSheetSize = Size.zero;
  515     Size snackBarSize = Size.zero;
  516     if (hasChild(_ScaffoldSlot.bodyScrim)) {
  517       final BoxConstraints bottomSheetScrimConstraints = BoxConstraints(
  518         maxWidth: fullWidthConstraints.maxWidth,
  519         maxHeight: contentBottom,
  520       );
  521       layoutChild(_ScaffoldSlot.bodyScrim, bottomSheetScrimConstraints);
  522       positionChild(_ScaffoldSlot.bodyScrim, Offset.zero);
  523     }
  524 
  525     // Set the size of the SnackBar early if the behavior is fixed so
  526     // the FAB can be positioned correctly.
  527     if (hasChild(_ScaffoldSlot.snackBar) && !isSnackBarFloating) {
  528       snackBarSize = layoutChild(_ScaffoldSlot.snackBar, fullWidthConstraints);
  529     }
  530 
  531     if (hasChild(_ScaffoldSlot.bottomSheet)) {
  532       final BoxConstraints bottomSheetConstraints = BoxConstraints(
  533         maxWidth: fullWidthConstraints.maxWidth,
  534         maxHeight: math.max(0.0, contentBottom - contentTop),
  535       );
  536       bottomSheetSize = layoutChild(_ScaffoldSlot.bottomSheet, bottomSheetConstraints);
  537       positionChild(_ScaffoldSlot.bottomSheet, Offset((size.width - bottomSheetSize.width) / 2.0, contentBottom - bottomSheetSize.height));
  538     }
  539 
  540     Rect floatingActionButtonRect;
  541     if (hasChild(_ScaffoldSlot.floatingActionButton)) {
  542       final Size fabSize = layoutChild(_ScaffoldSlot.floatingActionButton, looseConstraints);
  543 
  544       // To account for the FAB position being changed, we'll animate between
  545       // the old and new positions.
  546       final ScaffoldPrelayoutGeometry currentGeometry = ScaffoldPrelayoutGeometry(
  547         bottomSheetSize: bottomSheetSize,
  548         contentBottom: contentBottom,
  549         contentTop: contentTop,
  550         floatingActionButtonSize: fabSize,
  551         minInsets: minInsets,
  552         scaffoldSize: size,
  553         snackBarSize: snackBarSize,
  554         textDirection: textDirection,
  555         minViewPadding: minViewPadding,
  556       );
  557       final Offset currentFabOffset = currentFloatingActionButtonLocation.getOffset(currentGeometry);
  558       final Offset previousFabOffset = previousFloatingActionButtonLocation.getOffset(currentGeometry);
  559       final Offset fabOffset = floatingActionButtonMotionAnimator.getOffset(
  560         begin: previousFabOffset,
  561         end: currentFabOffset,
  562         progress: floatingActionButtonMoveAnimationProgress,
  563       );
  564       positionChild(_ScaffoldSlot.floatingActionButton, fabOffset);
  565       floatingActionButtonRect = fabOffset & fabSize;
  566     }
  567 
  568     if (hasChild(_ScaffoldSlot.snackBar)) {
  569       final bool hasCustomWidth = snackBarWidth != null && snackBarWidth < size.width;
  570       if (snackBarSize == Size.zero) {
  571         snackBarSize = layoutChild(
  572           _ScaffoldSlot.snackBar,
  573           hasCustomWidth ? looseConstraints : fullWidthConstraints,
  574         );
  575       }
  576 
  577       double snackBarYOffsetBase;
  578       if (floatingActionButtonRect.size != Size.zero && isSnackBarFloating) {
  579         snackBarYOffsetBase = floatingActionButtonRect.top;
  580       } else {
  581         // SnackBarBehavior.fixed applies a SafeArea automatically.
  582         // SnackBarBehavior.floating does not since the positioning is affected
  583         // if there is a FloatingActionButton (see condition above). If there is
  584         // no FAB, make sure we account for safe space when the SnackBar is
  585         // floating.
  586         final double safeYOffsetBase = size.height - minViewPadding.bottom;
  587         snackBarYOffsetBase = isSnackBarFloating
  588           ? math.min(contentBottom, safeYOffsetBase)
  589           : contentBottom;
  590       }
  591 
  592       final double xOffset = hasCustomWidth ? (size.width - snackBarWidth) / 2 : 0.0;
  593       positionChild(_ScaffoldSlot.snackBar, Offset(xOffset, snackBarYOffsetBase - snackBarSize.height));
  594     }
  595 
  596     if (hasChild(_ScaffoldSlot.statusBar)) {
  597       layoutChild(_ScaffoldSlot.statusBar, fullWidthConstraints.tighten(height: minInsets.top));
  598       positionChild(_ScaffoldSlot.statusBar, Offset.zero);
  599     }
  600 
  601     if (hasChild(_ScaffoldSlot.drawer)) {
  602       layoutChild(_ScaffoldSlot.drawer, BoxConstraints.tight(size));
  603       positionChild(_ScaffoldSlot.drawer, Offset.zero);
  604     }
  605 
  606     if (hasChild(_ScaffoldSlot.endDrawer)) {
  607       layoutChild(_ScaffoldSlot.endDrawer, BoxConstraints.tight(size));
  608       positionChild(_ScaffoldSlot.endDrawer, Offset.zero);
  609     }
  610 
  611     geometryNotifier._updateWith(
  612       bottomNavigationBarTop: bottomNavigationBarTop,
  613       floatingActionButtonArea: floatingActionButtonRect,
  614     );
  615   }
  616 
  617   @override
  618   bool shouldRelayout(_ScaffoldLayout oldDelegate) {
  619     return oldDelegate.minInsets != minInsets
  620         || oldDelegate.textDirection != textDirection
  621         || oldDelegate.floatingActionButtonMoveAnimationProgress != floatingActionButtonMoveAnimationProgress
  622         || oldDelegate.previousFloatingActionButtonLocation != previousFloatingActionButtonLocation
  623         || oldDelegate.currentFloatingActionButtonLocation != currentFloatingActionButtonLocation
  624         || oldDelegate.extendBody != extendBody
  625         || oldDelegate.extendBodyBehindAppBar != extendBodyBehindAppBar;
  626   }
  627 }
  628 
  629 /// Handler for scale and rotation animations in the [FloatingActionButton].
  630 ///
  631 /// Currently, there are two types of [FloatingActionButton] animations:
  632 ///
  633 /// * Entrance/Exit animations, which this widget triggers
  634 ///   when the [FloatingActionButton] is added, updated, or removed.
  635 /// * Motion animations, which are triggered by the [Scaffold]
  636 ///   when its [FloatingActionButtonLocation] is updated.
  637 class _FloatingActionButtonTransition extends StatefulWidget {
  638   const _FloatingActionButtonTransition({
  639     Key key,
  640     @required this.child,
  641     @required this.fabMoveAnimation,
  642     @required this.fabMotionAnimator,
  643     @required this.geometryNotifier,
  644     @required this.currentController,
  645   }) : assert(fabMoveAnimation != null),
  646        assert(fabMotionAnimator != null),
  647        assert(currentController != null),
  648        super(key: key);
  649 
  650   final Widget child;
  651   final Animation<double> fabMoveAnimation;
  652   final FloatingActionButtonAnimator fabMotionAnimator;
  653   final _ScaffoldGeometryNotifier geometryNotifier;
  654 
  655   /// Controls the current child widget.child as it exits.
  656   final AnimationController currentController;
  657 
  658   @override
  659   _FloatingActionButtonTransitionState createState() => _FloatingActionButtonTransitionState();
  660 }
  661 
  662 class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTransition> with TickerProviderStateMixin {
  663   // The animations applied to the Floating Action Button when it is entering or exiting.
  664   // Controls the previous widget.child as it exits.
  665   AnimationController _previousController;
  666   Animation<double> _previousScaleAnimation;
  667   Animation<double> _previousRotationAnimation;
  668   // The animations to run, considering the widget's fabMoveAnimation and the current/previous entrance/exit animations.
  669   Animation<double> _currentScaleAnimation;
  670   Animation<double> _extendedCurrentScaleAnimation;
  671   Animation<double> _currentRotationAnimation;
  672   Widget _previousChild;
  673 
  674   @override
  675   void initState() {
  676     super.initState();
  677 
  678     _previousController = AnimationController(
  679       duration: kFloatingActionButtonSegue,
  680       vsync: this,
  681     )..addStatusListener(_handlePreviousAnimationStatusChanged);
  682     _updateAnimations();
  683 
  684     if (widget.child != null) {
  685       // If we start out with a child, have the child appear fully visible instead
  686       // of animating in.
  687       widget.currentController.value = 1.0;
  688     } else {
  689       // If we start without a child we update the geometry object with a
  690       // floating action button scale of 0, as it is not showing on the screen.
  691       _updateGeometryScale(0.0);
  692     }
  693   }
  694 
  695   @override
  696   void dispose() {
  697     _previousController.dispose();
  698     super.dispose();
  699   }
  700 
  701   @override
  702   void didUpdateWidget(_FloatingActionButtonTransition oldWidget) {
  703     super.didUpdateWidget(oldWidget);
  704     final bool oldChildIsNull = oldWidget.child == null;
  705     final bool newChildIsNull = widget.child == null;
  706     if (oldChildIsNull == newChildIsNull && oldWidget.child?.key == widget.child?.key)
  707       return;
  708     if (oldWidget.fabMotionAnimator != widget.fabMotionAnimator || oldWidget.fabMoveAnimation != widget.fabMoveAnimation) {
  709       // Get the right scale and rotation animations to use for this widget.
  710       _updateAnimations();
  711     }
  712     if (_previousController.status == AnimationStatus.dismissed) {
  713       final double currentValue = widget.currentController.value;
  714       if (currentValue == 0.0 || oldWidget.child == null) {
  715         // The current child hasn't started its entrance animation yet. We can
  716         // just skip directly to the new child's entrance.
  717         _previousChild = null;
  718         if (widget.child != null)
  719           widget.currentController.forward();
  720       } else {
  721         // Otherwise, we need to copy the state from the current controller to
  722         // the previous controller and run an exit animation for the previous
  723         // widget before running the entrance animation for the new child.
  724         _previousChild = oldWidget.child;
  725         _previousController
  726           ..value = currentValue
  727           ..reverse();
  728         widget.currentController.value = 0.0;
  729       }
  730     }
  731   }
  732 
  733   static final Animatable<double> _entranceTurnTween = Tween<double>(
  734     begin: 1.0 - kFloatingActionButtonTurnInterval,
  735     end: 1.0,
  736   ).chain(CurveTween(curve: Curves.easeIn));
  737 
  738   void _updateAnimations() {
  739     // Get the animations for exit and entrance.
  740     final CurvedAnimation previousExitScaleAnimation = CurvedAnimation(
  741       parent: _previousController,
  742       curve: Curves.easeIn,
  743     );
  744     final Animation<double> previousExitRotationAnimation = Tween<double>(begin: 1.0, end: 1.0).animate(
  745       CurvedAnimation(
  746         parent: _previousController,
  747         curve: Curves.easeIn,
  748       ),
  749     );
  750 
  751     final CurvedAnimation currentEntranceScaleAnimation = CurvedAnimation(
  752       parent: widget.currentController,
  753       curve: Curves.easeIn,
  754     );
  755     final Animation<double> currentEntranceRotationAnimation = widget.currentController.drive(_entranceTurnTween);
  756 
  757     // Get the animations for when the FAB is moving.
  758     final Animation<double> moveScaleAnimation = widget.fabMotionAnimator.getScaleAnimation(parent: widget.fabMoveAnimation);
  759     final Animation<double> moveRotationAnimation = widget.fabMotionAnimator.getRotationAnimation(parent: widget.fabMoveAnimation);
  760 
  761     // Aggregate the animations.
  762     _previousScaleAnimation = AnimationMin<double>(moveScaleAnimation, previousExitScaleAnimation);
  763     _currentScaleAnimation = AnimationMin<double>(moveScaleAnimation, currentEntranceScaleAnimation);
  764     _extendedCurrentScaleAnimation = _currentScaleAnimation.drive(CurveTween(curve: const Interval(0.0, 0.1)));
  765 
  766     _previousRotationAnimation = TrainHoppingAnimation(previousExitRotationAnimation, moveRotationAnimation);
  767     _currentRotationAnimation = TrainHoppingAnimation(currentEntranceRotationAnimation, moveRotationAnimation);
  768 
  769     _currentScaleAnimation.addListener(_onProgressChanged);
  770     _previousScaleAnimation.addListener(_onProgressChanged);
  771   }
  772 
  773   void _handlePreviousAnimationStatusChanged(AnimationStatus status) {
  774     setState(() {
  775       if (status == AnimationStatus.dismissed) {
  776         assert(widget.currentController.status == AnimationStatus.dismissed);
  777         if (widget.child != null)
  778           widget.currentController.forward();
  779       }
  780     });
  781   }
  782 
  783   bool _isExtendedFloatingActionButton(Widget widget) {
  784     return widget is FloatingActionButton
  785         && widget.isExtended;
  786   }
  787 
  788   @override
  789   Widget build(BuildContext context) {
  790     return Stack(
  791       alignment: Alignment.centerRight,
  792       children: <Widget>[
  793         if (_previousController.status != AnimationStatus.dismissed)
  794           if (_isExtendedFloatingActionButton(_previousChild))
  795             FadeTransition(
  796               opacity: _previousScaleAnimation,
  797               child: _previousChild,
  798             )
  799           else
  800             ScaleTransition(
  801               scale: _previousScaleAnimation,
  802               child: RotationTransition(
  803                 turns: _previousRotationAnimation,
  804                 child: _previousChild,
  805               ),
  806             ),
  807         if (_isExtendedFloatingActionButton(widget.child))
  808           ScaleTransition(
  809             scale: _extendedCurrentScaleAnimation,
  810             child: FadeTransition(
  811               opacity: _currentScaleAnimation,
  812               child: widget.child,
  813             ),
  814           )
  815         else
  816           ScaleTransition(
  817             scale: _currentScaleAnimation,
  818             child: RotationTransition(
  819               turns: _currentRotationAnimation,
  820               child: widget.child,
  821             ),
  822           ),
  823       ],
  824     );
  825   }
  826 
  827   void _onProgressChanged() {
  828     _updateGeometryScale(math.max(_previousScaleAnimation.value, _currentScaleAnimation.value));
  829   }
  830 
  831   void _updateGeometryScale(double scale) {
  832     widget.geometryNotifier._updateWith(
  833       floatingActionButtonScale: scale,
  834     );
  835   }
  836 }
  837 
  838 /// Implements the basic material design visual layout structure.
  839 ///
  840 /// This class provides APIs for showing drawers, snack bars, and bottom sheets.
  841 ///
  842 /// To display a snackbar or a persistent bottom sheet, obtain the
  843 /// [ScaffoldState] for the current [BuildContext] via [Scaffold.of] and use the
  844 /// [ScaffoldState.showSnackBar] and [ScaffoldState.showBottomSheet] functions.
  845 ///
  846 /// {@tool dartpad --template=stateful_widget_material}
  847 /// This example shows a [Scaffold] with a [body] and [FloatingActionButton].
  848 /// The [body] is a [Text] placed in a [Center] in order to center the text
  849 /// within the [Scaffold]. The [FloatingActionButton] is connected to a
  850 /// callback that increments a counter.
  851 ///
  852 /// ![The Scaffold has a white background with a blue AppBar at the top. A blue FloatingActionButton is positioned at the bottom right corner of the Scaffold.](https://flutter.github.io/assets-for-api-docs/assets/material/scaffold.png)
  853 ///
  854 /// ```dart
  855 /// int _count = 0;
  856 ///
  857 /// Widget build(BuildContext context) {
  858 ///   return Scaffold(
  859 ///     appBar: AppBar(
  860 ///       title: const Text('Sample Code'),
  861 ///     ),
  862 ///     body: Center(
  863 ///       child: Text('You have pressed the button $_count times.')
  864 ///     ),
  865 ///     floatingActionButton: FloatingActionButton(
  866 ///       onPressed: () => setState(() => _count++),
  867 ///       tooltip: 'Increment Counter',
  868 ///       child: const Icon(Icons.add),
  869 ///     ),
  870 ///   );
  871 /// }
  872 /// ```
  873 /// {@end-tool}
  874 ///
  875 /// {@tool dartpad --template=stateful_widget_material}
  876 /// This example shows a [Scaffold] with a blueGrey [backgroundColor], [body]
  877 /// and [FloatingActionButton]. The [body] is a [Text] placed in a [Center] in
  878 /// order to center the text within the [Scaffold]. The [FloatingActionButton]
  879 /// is connected to a callback that increments a counter.
  880 ///
  881 /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/scaffold_background_color.png)
  882 ///
  883 /// ```dart
  884 /// int _count = 0;
  885 ///
  886 /// Widget build(BuildContext context) {
  887 ///   return Scaffold(
  888 ///     appBar: AppBar(
  889 ///       title: const Text('Sample Code'),
  890 ///     ),
  891 ///     body: Center(
  892 ///       child: Text('You have pressed the button $_count times.')
  893 ///     ),
  894 ///     backgroundColor: Colors.blueGrey.shade200,
  895 ///     floatingActionButton: FloatingActionButton(
  896 ///       onPressed: () => setState(() => _count++),
  897 ///       tooltip: 'Increment Counter',
  898 ///       child: const Icon(Icons.add),
  899 ///     ),
  900 ///   );
  901 /// }
  902 /// ```
  903 /// {@end-tool}
  904 ///
  905 /// {@tool dartpad --template=stateful_widget_material}
  906 /// This example shows a [Scaffold] with an [AppBar], a [BottomAppBar] and a
  907 /// [FloatingActionButton]. The [body] is a [Text] placed in a [Center] in order
  908 /// to center the text within the [Scaffold]. The [FloatingActionButton] is
  909 /// centered and docked within the [BottomAppBar] using
  910 /// [FloatingActionButtonLocation.centerDocked]. The [FloatingActionButton] is
  911 /// connected to a callback that increments a counter.
  912 ///
  913 /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/scaffold_bottom_app_bar.png)
  914 ///
  915 /// ```dart
  916 /// int _count = 0;
  917 ///
  918 /// Widget build(BuildContext context) {
  919 ///   return Scaffold(
  920 ///     appBar: AppBar(
  921 ///       title: Text('Sample Code'),
  922 ///     ),
  923 ///     body: Center(
  924 ///       child: Text('You have pressed the button $_count times.'),
  925 ///     ),
  926 ///     bottomNavigationBar: BottomAppBar(
  927 ///       shape: const CircularNotchedRectangle(),
  928 ///       child: Container(height: 50.0,),
  929 ///     ),
  930 ///     floatingActionButton: FloatingActionButton(
  931 ///       onPressed: () => setState(() {
  932 ///         _count++;
  933 ///       }),
  934 ///       tooltip: 'Increment Counter',
  935 ///       child: Icon(Icons.add),
  936 ///     ),
  937 ///     floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
  938 ///   );
  939 /// }
  940 /// ```
  941 /// {@end-tool}
  942 ///
  943 /// ## Scaffold layout, the keyboard, and display "notches"
  944 ///
  945 /// The scaffold will expand to fill the available space. That usually
  946 /// means that it will occupy its entire window or device screen. When
  947 /// the device's keyboard appears the Scaffold's ancestor [MediaQuery]
  948 /// widget's [MediaQueryData.viewInsets] changes and the Scaffold will
  949 /// be rebuilt. By default the scaffold's [body] is resized to make
  950 /// room for the keyboard. To prevent the resize set
  951 /// [resizeToAvoidBottomInset] to false. In either case the focused
  952 /// widget will be scrolled into view if it's within a scrollable
  953 /// container.
  954 ///
  955 /// The [MediaQueryData.padding] value defines areas that might
  956 /// not be completely visible, like the display "notch" on the iPhone
  957 /// X. The scaffold's [body] is not inset by this padding value
  958 /// although an [appBar] or [bottomNavigationBar] will typically
  959 /// cause the body to avoid the padding. The [SafeArea]
  960 /// widget can be used within the scaffold's body to avoid areas
  961 /// like display notches.
  962 ///
  963 /// ## Troubleshooting
  964 ///
  965 /// ### Nested Scaffolds
  966 ///
  967 /// The Scaffold was designed to be the single top level container for
  968 /// a [MaterialApp] and it's typically not necessary to nest
  969 /// scaffolds. For example in a tabbed UI, where the
  970 /// [bottomNavigationBar] is a [TabBar] and the body is a
  971 /// [TabBarView], you might be tempted to make each tab bar view a
  972 /// scaffold with a differently titled AppBar. It would be better to add a
  973 /// listener to the [TabController] that updates the AppBar.
  974 ///
  975 /// {@tool snippet}
  976 /// Add a listener to the app's tab controller so that the [AppBar] title of the
  977 /// app's one and only scaffold is reset each time a new tab is selected.
  978 ///
  979 /// ```dart
  980 /// TabController(vsync: tickerProvider, length: tabCount)..addListener(() {
  981 ///   if (!tabController.indexIsChanging) {
  982 ///     setState(() {
  983 ///       // Rebuild the enclosing scaffold with a new AppBar title
  984 ///       appBarTitle = 'Tab ${tabController.index}';
  985 ///     });
  986 ///   }
  987 /// })
  988 /// ```
  989 /// {@end-tool}
  990 ///
  991 /// Although there are some use cases, like a presentation app that
  992 /// shows embedded flutter content, where nested scaffolds are
  993 /// appropriate, it's best to avoid nesting scaffolds.
  994 ///
  995 /// See also:
  996 ///
  997 ///  * [AppBar], which is a horizontal bar typically shown at the top of an app
  998 ///    using the [appBar] property.
  999 ///  * [BottomAppBar], which is a horizontal bar typically shown at the bottom
 1000 ///    of an app using the [bottomNavigationBar] property.
 1001 ///  * [FloatingActionButton], which is a circular button typically shown in the
 1002 ///    bottom right corner of the app using the [floatingActionButton] property.
 1003 ///  * [Drawer], which is a vertical panel that is typically displayed to the
 1004 ///    left of the body (and often hidden on phones) using the [drawer]
 1005 ///    property.
 1006 ///  * [BottomNavigationBar], which is a horizontal array of buttons typically
 1007 ///    shown along the bottom of the app using the [bottomNavigationBar]
 1008 ///    property.
 1009 ///  * [SnackBar], which is a temporary notification typically shown near the
 1010 ///    bottom of the app using the [ScaffoldState.showSnackBar] method.
 1011 ///  * [BottomSheet], which is an overlay typically shown near the bottom of the
 1012 ///    app. A bottom sheet can either be persistent, in which case it is shown
 1013 ///    using the [ScaffoldState.showBottomSheet] method, or modal, in which case
 1014 ///    it is shown using the [showModalBottomSheet] function.
 1015 ///  * [ScaffoldState], which is the state associated with this widget.
 1016 ///  * <https://material.io/design/layout/responsive-layout-grid.html>
 1017 ///  * Cookbook: [Add a Drawer to a screen](https://flutter.dev/docs/cookbook/design/drawer)
 1018 ///  * Cookbook: [Display a snackbar](https://flutter.dev/docs/cookbook/design/snackbars)
 1019 ///  * See our
 1020 ///    [Scaffold Sample Apps](https://flutter.dev/docs/catalog/samples/Scaffold).
 1021 class Scaffold extends StatefulWidget {
 1022   /// Creates a visual scaffold for material design widgets.
 1023   const Scaffold({
 1024     Key key,
 1025     this.appBar,
 1026     this.body,
 1027     this.floatingActionButton,
 1028     this.floatingActionButtonLocation,
 1029     this.floatingActionButtonAnimator,
 1030     this.persistentFooterButtons,
 1031     this.drawer,
 1032     this.endDrawer,
 1033     this.bottomNavigationBar,
 1034     this.bottomSheet,
 1035     this.backgroundColor,
 1036     this.resizeToAvoidBottomPadding,
 1037     this.resizeToAvoidBottomInset,
 1038     this.primary = true,
 1039     this.drawerDragStartBehavior = DragStartBehavior.start,
 1040     this.extendBody = false,
 1041     this.extendBodyBehindAppBar = false,
 1042     this.drawerScrimColor,
 1043     this.drawerEdgeDragWidth,
 1044     this.drawerEnableOpenDragGesture = true,
 1045     this.endDrawerEnableOpenDragGesture = true,
 1046   }) : assert(primary != null),
 1047        assert(extendBody != null),
 1048        assert(extendBodyBehindAppBar != null),
 1049        assert(drawerDragStartBehavior != null),
 1050        super(key: key);
 1051 
 1052   /// If true, and [bottomNavigationBar] or [persistentFooterButtons]
 1053   /// is specified, then the [body] extends to the bottom of the Scaffold,
 1054   /// instead of only extending to the top of the [bottomNavigationBar]
 1055   /// or the [persistentFooterButtons].
 1056   ///
 1057   /// If true, a [MediaQuery] widget whose bottom padding matches the height
 1058   /// of the [bottomNavigationBar] will be added above the scaffold's [body].
 1059   ///
 1060   /// This property is often useful when the [bottomNavigationBar] has
 1061   /// a non-rectangular shape, like [CircularNotchedRectangle], which
 1062   /// adds a [FloatingActionButton] sized notch to the top edge of the bar.
 1063   /// In this case specifying `extendBody: true` ensures that that scaffold's
 1064   /// body will be visible through the bottom navigation bar's notch.
 1065   ///
 1066   /// See also:
 1067   ///
 1068   ///  * [extendBodyBehindAppBar], which extends the height of the body
 1069   ///    to the top of the scaffold.
 1070   final bool extendBody;
 1071 
 1072   /// If true, and an [appBar] is specified, then the height of the [body] is
 1073   /// extended to include the height of the app bar and the top of the body
 1074   /// is aligned with the top of the app bar.
 1075   ///
 1076   /// This is useful if the app bar's [AppBar.backgroundColor] is not
 1077   /// completely opaque.
 1078   ///
 1079   /// This property is false by default. It must not be null.
 1080   ///
 1081   /// See also:
 1082   ///
 1083   ///  * [extendBody], which extends the height of the body to the bottom
 1084   ///    of the scaffold.
 1085   final bool extendBodyBehindAppBar;
 1086 
 1087   /// An app bar to display at the top of the scaffold.
 1088   final PreferredSizeWidget appBar;
 1089 
 1090   /// The primary content of the scaffold.
 1091   ///
 1092   /// Displayed below the [appBar], above the bottom of the ambient
 1093   /// [MediaQuery]'s [MediaQueryData.viewInsets], and behind the
 1094   /// [floatingActionButton] and [drawer]. If [resizeToAvoidBottomInset] is
 1095   /// false then the body is not resized when the onscreen keyboard appears,
 1096   /// i.e. it is not inset by `viewInsets.bottom`.
 1097   ///
 1098   /// The widget in the body of the scaffold is positioned at the top-left of
 1099   /// the available space between the app bar and the bottom of the scaffold. To
 1100   /// center this widget instead, consider putting it in a [Center] widget and
 1101   /// having that be the body. To expand this widget instead, consider
 1102   /// putting it in a [SizedBox.expand].
 1103   ///
 1104   /// If you have a column of widgets that should normally fit on the screen,
 1105   /// but may overflow and would in such cases need to scroll, consider using a
 1106   /// [ListView] as the body of the scaffold. This is also a good choice for
 1107   /// the case where your body is a scrollable list.
 1108   final Widget body;
 1109 
 1110   /// A button displayed floating above [body], in the bottom right corner.
 1111   ///
 1112   /// Typically a [FloatingActionButton].
 1113   final Widget floatingActionButton;
 1114 
 1115   /// Responsible for determining where the [floatingActionButton] should go.
 1116   ///
 1117   /// If null, the [ScaffoldState] will use the default location, [FloatingActionButtonLocation.endFloat].
 1118   final FloatingActionButtonLocation floatingActionButtonLocation;
 1119 
 1120   /// Animator to move the [floatingActionButton] to a new [floatingActionButtonLocation].
 1121   ///
 1122   /// If null, the [ScaffoldState] will use the default animator, [FloatingActionButtonAnimator.scaling].
 1123   final FloatingActionButtonAnimator floatingActionButtonAnimator;
 1124 
 1125   /// A set of buttons that are displayed at the bottom of the scaffold.
 1126   ///
 1127   /// Typically this is a list of [TextButton] widgets. These buttons are
 1128   /// persistently visible, even if the [body] of the scaffold scrolls.
 1129   ///
 1130   /// These widgets will be wrapped in a [ButtonBar].
 1131   ///
 1132   /// The [persistentFooterButtons] are rendered above the
 1133   /// [bottomNavigationBar] but below the [body].
 1134   final List<Widget> persistentFooterButtons;
 1135 
 1136   /// A panel displayed to the side of the [body], often hidden on mobile
 1137   /// devices. Swipes in from either left-to-right ([TextDirection.ltr]) or
 1138   /// right-to-left ([TextDirection.rtl])
 1139   ///
 1140   /// Typically a [Drawer].
 1141   ///
 1142   /// To open the drawer, use the [ScaffoldState.openDrawer] function.
 1143   ///
 1144   /// To close the drawer, use [Navigator.pop].
 1145   ///
 1146   /// {@tool dartpad --template=stateful_widget_material}
 1147   /// To disable the drawer edge swipe, set the
 1148   /// [Scaffold.drawerEnableOpenDragGesture] to false. Then, use
 1149   /// [ScaffoldState.openDrawer] to open the drawer and [Navigator.pop] to close
 1150   /// it.
 1151   ///
 1152   /// ```dart
 1153   /// final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
 1154   ///
 1155   /// void _openDrawer() {
 1156   ///   _scaffoldKey.currentState.openDrawer();
 1157   /// }
 1158   ///
 1159   /// void _closeDrawer() {
 1160   ///   Navigator.of(context).pop();
 1161   /// }
 1162   ///
 1163   /// @override
 1164   /// Widget build(BuildContext context) {
 1165   ///   return Scaffold(
 1166   ///     key: _scaffoldKey,
 1167   ///     appBar: AppBar(title: const Text('Drawer Demo')),
 1168   ///     body: Center(
 1169   ///       child: ElevatedButton(
 1170   ///         onPressed: _openDrawer,
 1171   ///         child: const Text('Open Drawer'),
 1172   ///       ),
 1173   ///     ),
 1174   ///     drawer: Drawer(
 1175   ///       child: Center(
 1176   ///         child: Column(
 1177   ///           mainAxisAlignment: MainAxisAlignment.center,
 1178   ///           children: <Widget>[
 1179   ///             const Text('This is the Drawer'),
 1180   ///             ElevatedButton(
 1181   ///               onPressed: _closeDrawer,
 1182   ///               child: const Text('Close Drawer'),
 1183   ///             ),
 1184   ///           ],
 1185   ///         ),
 1186   ///       ),
 1187   ///     ),
 1188   ///     // Disable opening the drawer with a swipe gesture.
 1189   ///     drawerEnableOpenDragGesture: false,
 1190   ///   );
 1191   /// }
 1192   /// ```
 1193   /// {@end-tool}
 1194   final Widget drawer;
 1195 
 1196   /// A panel displayed to the side of the [body], often hidden on mobile
 1197   /// devices. Swipes in from right-to-left ([TextDirection.ltr]) or
 1198   /// left-to-right ([TextDirection.rtl])
 1199   ///
 1200   /// Typically a [Drawer].
 1201   ///
 1202   /// To open the drawer, use the [ScaffoldState.openEndDrawer] function.
 1203   ///
 1204   /// To close the drawer, use [Navigator.pop].
 1205   ///
 1206   /// {@tool dartpad --template=stateful_widget_material}
 1207   /// To disable the drawer edge swipe, set the
 1208   /// [Scaffold.endDrawerEnableOpenDragGesture] to false. Then, use
 1209   /// [ScaffoldState.openEndDrawer] to open the drawer and [Navigator.pop] to
 1210   /// close it.
 1211   ///
 1212   /// ```dart
 1213   /// final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
 1214   ///
 1215   /// void _openEndDrawer() {
 1216   ///   _scaffoldKey.currentState.openEndDrawer();
 1217   /// }
 1218   ///
 1219   /// void _closeEndDrawer() {
 1220   ///   Navigator.of(context).pop();
 1221   /// }
 1222   ///
 1223   /// @override
 1224   /// Widget build(BuildContext context) {
 1225   ///   return Scaffold(
 1226   ///     key: _scaffoldKey,
 1227   ///     appBar: AppBar(title: Text('Drawer Demo')),
 1228   ///     body: Center(
 1229   ///       child: ElevatedButton(
 1230   ///         onPressed: _openEndDrawer,
 1231   ///         child: Text('Open End Drawer'),
 1232   ///       ),
 1233   ///     ),
 1234   ///     endDrawer: Drawer(
 1235   ///       child: Center(
 1236   ///         child: Column(
 1237   ///           mainAxisAlignment: MainAxisAlignment.center,
 1238   ///           children: <Widget>[
 1239   ///             const Text('This is the Drawer'),
 1240   ///             ElevatedButton(
 1241   ///               onPressed: _closeEndDrawer,
 1242   ///               child: const Text('Close Drawer'),
 1243   ///             ),
 1244   ///           ],
 1245   ///         ),
 1246   ///       ),
 1247   ///     ),
 1248   ///     // Disable opening the end drawer with a swipe gesture.
 1249   ///     endDrawerEnableOpenDragGesture: false,
 1250   ///   );
 1251   /// }
 1252   /// ```
 1253   /// {@end-tool}
 1254   final Widget endDrawer;
 1255 
 1256   /// The color to use for the scrim that obscures primary content while a drawer is open.
 1257   ///
 1258   /// By default, the color is [Colors.black54]
 1259   final Color drawerScrimColor;
 1260 
 1261   /// The color of the [Material] widget that underlies the entire Scaffold.
 1262   ///
 1263   /// The theme's [ThemeData.scaffoldBackgroundColor] by default.
 1264   final Color backgroundColor;
 1265 
 1266   /// A bottom navigation bar to display at the bottom of the scaffold.
 1267   ///
 1268   /// Snack bars slide from underneath the bottom navigation bar while bottom
 1269   /// sheets are stacked on top.
 1270   ///
 1271   /// The [bottomNavigationBar] is rendered below the [persistentFooterButtons]
 1272   /// and the [body].
 1273   final Widget bottomNavigationBar;
 1274 
 1275   /// The persistent bottom sheet to display.
 1276   ///
 1277   /// A persistent bottom sheet shows information that supplements the primary
 1278   /// content of the app. A persistent bottom sheet remains visible even when
 1279   /// the user interacts with other parts of the app.
 1280   ///
 1281   /// A closely related widget is a modal bottom sheet, which is an alternative
 1282   /// to a menu or a dialog and prevents the user from interacting with the rest
 1283   /// of the app. Modal bottom sheets can be created and displayed with the
 1284   /// [showModalBottomSheet] function.
 1285   ///
 1286   /// Unlike the persistent bottom sheet displayed by [showBottomSheet]
 1287   /// this bottom sheet is not a [LocalHistoryEntry] and cannot be dismissed
 1288   /// with the scaffold appbar's back button.
 1289   ///
 1290   /// If a persistent bottom sheet created with [showBottomSheet] is already
 1291   /// visible, it must be closed before building the Scaffold with a new
 1292   /// [bottomSheet].
 1293   ///
 1294   /// The value of [bottomSheet] can be any widget at all. It's unlikely to
 1295   /// actually be a [BottomSheet], which is used by the implementations of
 1296   /// [showBottomSheet] and [showModalBottomSheet]. Typically it's a widget
 1297   /// that includes [Material].
 1298   ///
 1299   /// See also:
 1300   ///
 1301   ///  * [showBottomSheet], which displays a bottom sheet as a route that can
 1302   ///    be dismissed with the scaffold's back button.
 1303   ///  * [showModalBottomSheet], which displays a modal bottom sheet.
 1304   final Widget bottomSheet;
 1305 
 1306   /// This flag is deprecated, please use [resizeToAvoidBottomInset]
 1307   /// instead.
 1308   ///
 1309   /// Originally the name referred [MediaQueryData.padding]. Now it refers
 1310   /// [MediaQueryData.viewInsets], so using [resizeToAvoidBottomInset]
 1311   /// should be clearer to readers.
 1312   @Deprecated(
 1313     'Use resizeToAvoidBottomInset to specify if the body should resize when the keyboard appears. '
 1314     'This feature was deprecated after v1.1.9.'
 1315   )
 1316   final bool resizeToAvoidBottomPadding;
 1317 
 1318   /// If true the [body] and the scaffold's floating widgets should size
 1319   /// themselves to avoid the onscreen keyboard whose height is defined by the
 1320   /// ambient [MediaQuery]'s [MediaQueryData.viewInsets] `bottom` property.
 1321   ///
 1322   /// For example, if there is an onscreen keyboard displayed above the
 1323   /// scaffold, the body can be resized to avoid overlapping the keyboard, which
 1324   /// prevents widgets inside the body from being obscured by the keyboard.
 1325   ///
 1326   /// Defaults to true.
 1327   final bool resizeToAvoidBottomInset;
 1328 
 1329   /// Whether this scaffold is being displayed at the top of the screen.
 1330   ///
 1331   /// If true then the height of the [appBar] will be extended by the height
 1332   /// of the screen's status bar, i.e. the top padding for [MediaQuery].
 1333   ///
 1334   /// The default value of this property, like the default value of
 1335   /// [AppBar.primary], is true.
 1336   final bool primary;
 1337 
 1338   /// {@macro flutter.material.drawer.dragStartBehavior}
 1339   final DragStartBehavior drawerDragStartBehavior;
 1340 
 1341   /// The width of the area within which a horizontal swipe will open the
 1342   /// drawer.
 1343   ///
 1344   /// By default, the value used is 20.0 added to the padding edge of
 1345   /// `MediaQuery.of(context).padding` that corresponds to the surrounding
 1346   /// [TextDirection]. This ensures that the drag area for notched devices is
 1347   /// not obscured. For example, if `TextDirection.of(context)` is set to
 1348   /// [TextDirection.ltr], 20.0 will be added to
 1349   /// `MediaQuery.of(context).padding.left`.
 1350   final double drawerEdgeDragWidth;
 1351 
 1352   /// Determines if the [Scaffold.drawer] can be opened with a drag
 1353   /// gesture.
 1354   ///
 1355   /// By default, the drag gesture is enabled.
 1356   final bool drawerEnableOpenDragGesture;
 1357 
 1358   /// Determines if the [Scaffold.endDrawer] can be opened with a
 1359   /// drag gesture.
 1360   ///
 1361   /// By default, the drag gesture is enabled.
 1362   final bool endDrawerEnableOpenDragGesture;
 1363 
 1364   /// The state from the closest instance of this class that encloses the given context.
 1365   ///
 1366   /// {@tool dartpad --template=freeform}
 1367   /// Typical usage of the [Scaffold.of] function is to call it from within the
 1368   /// `build` method of a child of a [Scaffold].
 1369   ///
 1370   /// ```dart imports
 1371   /// import 'package:flutter/material.dart';
 1372   /// ```
 1373   ///
 1374   /// ```dart main
 1375   /// void main() => runApp(MyApp());
 1376   /// ```
 1377   ///
 1378   /// ```dart preamble
 1379   /// class MyApp extends StatelessWidget {
 1380   ///   // This widget is the root of your application.
 1381   ///   @override
 1382   ///   Widget build(BuildContext context) {
 1383   ///     return MaterialApp(
 1384   ///       title: 'Flutter Code Sample for Scaffold.of.',
 1385   ///       theme: ThemeData(
 1386   ///         primarySwatch: Colors.blue,
 1387   ///       ),
 1388   ///       home: Scaffold(
 1389   ///         body: MyScaffoldBody(),
 1390   ///         appBar: AppBar(title: Text('Scaffold.of Example')),
 1391   ///       ),
 1392   ///       color: Colors.white,
 1393   ///     );
 1394   ///   }
 1395   /// }
 1396   /// ```
 1397   ///
 1398   /// ```dart
 1399   /// class MyScaffoldBody extends StatelessWidget {
 1400   ///   @override
 1401   ///   Widget build(BuildContext context) {
 1402   ///     return Center(
 1403   ///       child: ElevatedButton(
 1404   ///         child: Text('SHOW A SNACKBAR'),
 1405   ///         onPressed: () {
 1406   ///           Scaffold.of(context).showSnackBar(
 1407   ///             SnackBar(
 1408   ///               content: Text('Have a snack!'),
 1409   ///             ),
 1410   ///           );
 1411   ///         },
 1412   ///       ),
 1413   ///     );
 1414   ///   }
 1415   /// }
 1416   /// ```
 1417   /// {@end-tool}
 1418   ///
 1419   /// {@tool dartpad --template=stateless_widget_material}
 1420   /// When the [Scaffold] is actually created in the same `build` function, the
 1421   /// `context` argument to the `build` function can't be used to find the
 1422   /// [Scaffold] (since it's "above" the widget being returned in the widget
 1423   /// tree). In such cases, the following technique with a [Builder] can be used
 1424   /// to provide a new scope with a [BuildContext] that is "under" the
 1425   /// [Scaffold]:
 1426   ///
 1427   /// ```dart
 1428   /// Widget build(BuildContext context) {
 1429   ///   return Scaffold(
 1430   ///     appBar: AppBar(
 1431   ///       title: Text('Demo')
 1432   ///     ),
 1433   ///     body: Builder(
 1434   ///       // Create an inner BuildContext so that the onPressed methods
 1435   ///       // can refer to the Scaffold with Scaffold.of().
 1436   ///       builder: (BuildContext context) {
 1437   ///         return Center(
 1438   ///           child: ElevatedButton(
 1439   ///             child: Text('SHOW A SNACKBAR'),
 1440   ///             onPressed: () {
 1441   ///               Scaffold.of(context).showSnackBar(SnackBar(
 1442   ///                 content: Text('Have a snack!'),
 1443   ///               ));
 1444   ///             },
 1445   ///           ),
 1446   ///         );
 1447   ///       },
 1448   ///     ),
 1449   ///   );
 1450   /// }
 1451   /// ```
 1452   /// {@end-tool}
 1453   ///
 1454   /// A more efficient solution is to split your build function into several
 1455   /// widgets. This introduces a new context from which you can obtain the
 1456   /// [Scaffold]. In this solution, you would have an outer widget that creates
 1457   /// the [Scaffold] populated by instances of your new inner widgets, and then
 1458   /// in these inner widgets you would use [Scaffold.of].
 1459   ///
 1460   /// A less elegant but more expedient solution is assign a [GlobalKey] to the
 1461   /// [Scaffold], then use the `key.currentState` property to obtain the
 1462   /// [ScaffoldState] rather than using the [Scaffold.of] function.
 1463   ///
 1464   /// If there is no [Scaffold] in scope, then this will throw an exception.
 1465   /// To return null if there is no [Scaffold], then pass `nullOk: true`.
 1466   static ScaffoldState of(BuildContext context, { bool nullOk = false }) {
 1467     assert(nullOk != null);
 1468     assert(context != null);
 1469     final ScaffoldState result = context.findAncestorStateOfType<ScaffoldState>();
 1470     if (nullOk || result != null)
 1471       return result;
 1472     throw FlutterError.fromParts(<DiagnosticsNode>[
 1473       ErrorSummary(
 1474         'Scaffold.of() called with a context that does not contain a Scaffold.'
 1475       ),
 1476       ErrorDescription(
 1477         'No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of(). '
 1478         'This usually happens when the context provided is from the same StatefulWidget as that '
 1479         'whose build function actually creates the Scaffold widget being sought.'
 1480       ),
 1481       ErrorHint(
 1482         'There are several ways to avoid this problem. The simplest is to use a Builder to get a '
 1483         'context that is "under" the Scaffold. For an example of this, please see the '
 1484         'documentation for Scaffold.of():\n'
 1485         '  https://api.flutter.dev/flutter/material/Scaffold/of.html'
 1486       ),
 1487       ErrorHint(
 1488         'A more efficient solution is to split your build function into several widgets. This '
 1489         'introduces a new context from which you can obtain the Scaffold. In this solution, '
 1490         'you would have an outer widget that creates the Scaffold populated by instances of '
 1491         'your new inner widgets, and then in these inner widgets you would use Scaffold.of().\n'
 1492         'A less elegant but more expedient solution is assign a GlobalKey to the Scaffold, '
 1493         'then use the key.currentState property to obtain the ScaffoldState rather than '
 1494         'using the Scaffold.of() function.'
 1495       ),
 1496       context.describeElement('The context used was')
 1497     ]);
 1498   }
 1499 
 1500   /// Returns a [ValueListenable] for the [ScaffoldGeometry] for the closest
 1501   /// [Scaffold] ancestor of the given context.
 1502   ///
 1503   /// The [ValueListenable.value] is only available at paint time.
 1504   ///
 1505   /// Notifications are guaranteed to be sent before the first paint pass
 1506   /// with the new geometry, but there is no guarantee whether a build or
 1507   /// layout passes are going to happen between the notification and the next
 1508   /// paint pass.
 1509   ///
 1510   /// The closest [Scaffold] ancestor for the context might change, e.g when
 1511   /// an element is moved from one scaffold to another. For [StatefulWidget]s
 1512   /// using this listenable, a change of the [Scaffold] ancestor will
 1513   /// trigger a [State.didChangeDependencies].
 1514   ///
 1515   /// A typical pattern for listening to the scaffold geometry would be to
 1516   /// call [Scaffold.geometryOf] in [State.didChangeDependencies], compare the
 1517   /// return value with the previous listenable, if it has changed, unregister
 1518   /// the listener, and register a listener to the new [ScaffoldGeometry]
 1519   /// listenable.
 1520   static ValueListenable<ScaffoldGeometry> geometryOf(BuildContext context) {
 1521     final _ScaffoldScope scaffoldScope = context.dependOnInheritedWidgetOfExactType<_ScaffoldScope>();
 1522     if (scaffoldScope == null)
 1523       throw FlutterError.fromParts(<DiagnosticsNode>[
 1524         ErrorSummary(
 1525           'Scaffold.geometryOf() called with a context that does not contain a Scaffold.'
 1526         ),
 1527         ErrorDescription(
 1528           'This usually happens when the context provided is from the same StatefulWidget as that '
 1529           'whose build function actually creates the Scaffold widget being sought.'
 1530         ),
 1531         ErrorHint(
 1532           'There are several ways to avoid this problem. The simplest is to use a Builder to get a '
 1533           'context that is "under" the Scaffold. For an example of this, please see the '
 1534           'documentation for Scaffold.of():\n'
 1535           '  https://api.flutter.dev/flutter/material/Scaffold/of.html'
 1536         ),
 1537         ErrorHint(
 1538           'A more efficient solution is to split your build function into several widgets. This '
 1539           'introduces a new context from which you can obtain the Scaffold. In this solution, '
 1540           'you would have an outer widget that creates the Scaffold populated by instances of '
 1541           'your new inner widgets, and then in these inner widgets you would use Scaffold.geometryOf().',
 1542         ),
 1543         context.describeElement('The context used was')
 1544       ]);
 1545     return scaffoldScope.geometryNotifier;
 1546   }
 1547 
 1548   /// Whether the Scaffold that most tightly encloses the given context has a
 1549   /// drawer.
 1550   ///
 1551   /// If this is being used during a build (for example to decide whether to
 1552   /// show an "open drawer" button), set the `registerForUpdates` argument to
 1553   /// true. This will then set up an [InheritedWidget] relationship with the
 1554   /// [Scaffold] so that the client widget gets rebuilt whenever the [hasDrawer]
 1555   /// value changes.
 1556   ///
 1557   /// See also:
 1558   ///
 1559   ///  * [Scaffold.of], which provides access to the [ScaffoldState] object as a
 1560   ///    whole, from which you can show snackbars, bottom sheets, and so forth.
 1561   static bool hasDrawer(BuildContext context, { bool registerForUpdates = true }) {
 1562     assert(registerForUpdates != null);
 1563     assert(context != null);
 1564     if (registerForUpdates) {
 1565       final _ScaffoldScope scaffold = context.dependOnInheritedWidgetOfExactType<_ScaffoldScope>();
 1566       return scaffold?.hasDrawer ?? false;
 1567     } else {
 1568       final ScaffoldState scaffold = context.findAncestorStateOfType<ScaffoldState>();
 1569       return scaffold?.hasDrawer ?? false;
 1570     }
 1571   }
 1572 
 1573   @override
 1574   ScaffoldState createState() => ScaffoldState();
 1575 }
 1576 
 1577 /// State for a [Scaffold].
 1578 ///
 1579 /// Can display [SnackBar]s and [BottomSheet]s. Retrieve a [ScaffoldState] from
 1580 /// the current [BuildContext] using [Scaffold.of].
 1581 class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
 1582 
 1583   // DRAWER API
 1584 
 1585   final GlobalKey<DrawerControllerState> _drawerKey = GlobalKey<DrawerControllerState>();
 1586   final GlobalKey<DrawerControllerState> _endDrawerKey = GlobalKey<DrawerControllerState>();
 1587 
 1588   /// Whether this scaffold has a non-null [Scaffold.appBar].
 1589   bool get hasAppBar => widget.appBar != null;
 1590   /// Whether this scaffold has a non-null [Scaffold.drawer].
 1591   bool get hasDrawer => widget.drawer != null;
 1592   /// Whether this scaffold has a non-null [Scaffold.endDrawer].
 1593   bool get hasEndDrawer => widget.endDrawer != null;
 1594   /// Whether this scaffold has a non-null [Scaffold.floatingActionButton].
 1595   bool get hasFloatingActionButton => widget.floatingActionButton != null;
 1596 
 1597   double _appBarMaxHeight;
 1598   /// The max height the [Scaffold.appBar] uses.
 1599   ///
 1600   /// This is based on the appBar preferred height plus the top padding.
 1601   double get appBarMaxHeight => _appBarMaxHeight;
 1602   bool _drawerOpened = false;
 1603   bool _endDrawerOpened = false;
 1604 
 1605   /// Whether the [Scaffold.drawer] is opened.
 1606   ///
 1607   /// See also:
 1608   ///
 1609   ///  * [ScaffoldState.openDrawer], which opens the [Scaffold.drawer] of a
 1610   ///    [Scaffold].
 1611   bool get isDrawerOpen => _drawerOpened;
 1612 
 1613   /// Whether the [Scaffold.endDrawer] is opened.
 1614   ///
 1615   /// See also:
 1616   ///
 1617   ///  * [ScaffoldState.openEndDrawer], which opens the [Scaffold.endDrawer] of
 1618   ///    a [Scaffold].
 1619   bool get isEndDrawerOpen => _endDrawerOpened;
 1620 
 1621   void _drawerOpenedCallback(bool isOpened) {
 1622     setState(() {
 1623       _drawerOpened = isOpened;
 1624     });
 1625   }
 1626 
 1627   void _endDrawerOpenedCallback(bool isOpened) {
 1628     setState(() {
 1629       _endDrawerOpened = isOpened;
 1630     });
 1631   }
 1632 
 1633   /// Opens the [Drawer] (if any).
 1634   ///
 1635   /// If the scaffold has a non-null [Scaffold.drawer], this function will cause
 1636   /// the drawer to begin its entrance animation.
 1637   ///
 1638   /// Normally this is not needed since the [Scaffold] automatically shows an
 1639   /// appropriate [IconButton], and handles the edge-swipe gesture, to show the
 1640   /// drawer.
 1641   ///
 1642   /// To close the drawer once it is open, use [Navigator.pop].
 1643   ///
 1644   /// See [Scaffold.of] for information about how to obtain the [ScaffoldState].
 1645   void openDrawer() {
 1646     if (_endDrawerKey.currentState != null && _endDrawerOpened)
 1647       _endDrawerKey.currentState.close();
 1648     _drawerKey.currentState?.open();
 1649   }
 1650 
 1651   /// Opens the end side [Drawer] (if any).
 1652   ///
 1653   /// If the scaffold has a non-null [Scaffold.endDrawer], this function will cause
 1654   /// the end side drawer to begin its entrance animation.
 1655   ///
 1656   /// Normally this is not needed since the [Scaffold] automatically shows an
 1657   /// appropriate [IconButton], and handles the edge-swipe gesture, to show the
 1658   /// drawer.
 1659   ///
 1660   /// To close the end side drawer once it is open, use [Navigator.pop].
 1661   ///
 1662   /// See [Scaffold.of] for information about how to obtain the [ScaffoldState].
 1663   void openEndDrawer() {
 1664     if (_drawerKey.currentState != null && _drawerOpened)
 1665       _drawerKey.currentState.close();
 1666     _endDrawerKey.currentState?.open();
 1667   }
 1668 
 1669   // SNACKBAR API
 1670 
 1671   final Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>> _snackBars = Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>>();
 1672   AnimationController _snackBarController;
 1673   Timer _snackBarTimer;
 1674   bool _accessibleNavigation;
 1675 
 1676   /// Shows a [SnackBar] at the bottom of the scaffold.
 1677   ///
 1678   /// A scaffold can show at most one snack bar at a time. If this function is
 1679   /// called while another snack bar is already visible, the given snack bar
 1680   /// will be added to a queue and displayed after the earlier snack bars have
 1681   /// closed.
 1682   ///
 1683   /// To control how long a [SnackBar] remains visible, use [SnackBar.duration].
 1684   ///
 1685   /// To remove the [SnackBar] with an exit animation, use [hideCurrentSnackBar]
 1686   /// or call [ScaffoldFeatureController.close] on the returned
 1687   /// [ScaffoldFeatureController]. To remove a [SnackBar] suddenly (without an
 1688   /// animation), use [removeCurrentSnackBar].
 1689   ///
 1690   /// See [Scaffold.of] for information about how to obtain the [ScaffoldState].
 1691   ///
 1692   /// {@tool dartpad --template=stateless_widget_scaffold_center}
 1693   ///
 1694   /// Here is an example of showing a [SnackBar] when the user presses a button.
 1695   ///
 1696   /// ```dart
 1697   ///   Widget build(BuildContext context) {
 1698   ///     return OutlinedButton(
 1699   ///       onPressed: () {
 1700   ///         Scaffold.of(context).showSnackBar(
 1701   ///           SnackBar(
 1702   ///             content: Text('A SnackBar has been shown.'),
 1703   ///           ),
 1704   ///         );
 1705   ///       },
 1706   ///       child: Text('Show SnackBar'),
 1707   ///     );
 1708   ///   }
 1709   /// ```
 1710   /// {@end-tool}
 1711   ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(SnackBar snackbar) {
 1712     _snackBarController ??= SnackBar.createAnimationController(vsync: this)
 1713       ..addStatusListener(_handleSnackBarStatusChange);
 1714     if (_snackBars.isEmpty) {
 1715       assert(_snackBarController.isDismissed);
 1716       _snackBarController.forward();
 1717     }
 1718     ScaffoldFeatureController<SnackBar, SnackBarClosedReason> controller;
 1719     controller = ScaffoldFeatureController<SnackBar, SnackBarClosedReason>._(
 1720       // We provide a fallback key so that if back-to-back snackbars happen to
 1721       // match in structure, material ink splashes and highlights don't survive
 1722       // from one to the next.
 1723       snackbar.withAnimation(_snackBarController, fallbackKey: UniqueKey()),
 1724       Completer<SnackBarClosedReason>(),
 1725       () {
 1726         assert(_snackBars.first == controller);
 1727         hideCurrentSnackBar(reason: SnackBarClosedReason.hide);
 1728       },
 1729       null, // SnackBar doesn't use a builder function so setState() wouldn't rebuild it
 1730     );
 1731     setState(() {
 1732       _snackBars.addLast(controller);
 1733     });
 1734     return controller;
 1735   }
 1736 
 1737   void _handleSnackBarStatusChange(AnimationStatus status) {
 1738     switch (status) {
 1739       case AnimationStatus.dismissed:
 1740         assert(_snackBars.isNotEmpty);
 1741         setState(() {
 1742           _snackBars.removeFirst();
 1743         });
 1744         if (_snackBars.isNotEmpty)
 1745           _snackBarController.forward();
 1746         break;
 1747       case AnimationStatus.completed:
 1748         setState(() {
 1749           assert(_snackBarTimer == null);
 1750           // build will create a new timer if necessary to dismiss the snack bar
 1751         });
 1752         break;
 1753       case AnimationStatus.forward:
 1754       case AnimationStatus.reverse:
 1755         break;
 1756     }
 1757   }
 1758 
 1759   /// Removes the current [SnackBar] (if any) immediately.
 1760   ///
 1761   /// The removed snack bar does not run its normal exit animation. If there are
 1762   /// any queued snack bars, they begin their entrance animation immediately.
 1763   void removeCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.remove }) {
 1764     assert(reason != null);
 1765     if (_snackBars.isEmpty)
 1766       return;
 1767     final Completer<SnackBarClosedReason> completer = _snackBars.first._completer;
 1768     if (!completer.isCompleted)
 1769       completer.complete(reason);
 1770     _snackBarTimer?.cancel();
 1771     _snackBarTimer = null;
 1772     _snackBarController.value = 0.0;
 1773   }
 1774 
 1775   /// Removes the current [SnackBar] by running its normal exit animation.
 1776   ///
 1777   /// The closed completer is called after the animation is complete.
 1778   void hideCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.hide }) {
 1779     assert(reason != null);
 1780     if (_snackBars.isEmpty || _snackBarController.status == AnimationStatus.dismissed)
 1781       return;
 1782     final MediaQueryData mediaQuery = MediaQuery.of(context);
 1783     final Completer<SnackBarClosedReason> completer = _snackBars.first._completer;
 1784     if (mediaQuery.accessibleNavigation) {
 1785       _snackBarController.value = 0.0;
 1786       completer.complete(reason);
 1787     } else {
 1788       _snackBarController.reverse().then<void>((void value) {
 1789         assert(mounted);
 1790         if (!completer.isCompleted)
 1791           completer.complete(reason);
 1792       });
 1793     }
 1794     _snackBarTimer?.cancel();
 1795     _snackBarTimer = null;
 1796   }
 1797 
 1798 
 1799   // PERSISTENT BOTTOM SHEET API
 1800 
 1801   // Contains bottom sheets that may still be animating out of view.
 1802   // Important if the app/user takes an action that could repeatedly show a
 1803   // bottom sheet.
 1804   final List<_StandardBottomSheet> _dismissedBottomSheets = <_StandardBottomSheet>[];
 1805   PersistentBottomSheetController<dynamic> _currentBottomSheet;
 1806 
 1807   void _maybeBuildPersistentBottomSheet() {
 1808     if (widget.bottomSheet != null && _currentBottomSheet == null) {
 1809       // The new _currentBottomSheet is not a local history entry so a "back" button
 1810       // will not be added to the Scaffold's appbar and the bottom sheet will not
 1811       // support drag or swipe to dismiss.
 1812       final AnimationController animationController = BottomSheet.createAnimationController(this)..value = 1.0;
 1813       LocalHistoryEntry _persistentSheetHistoryEntry;
 1814       bool _persistentBottomSheetExtentChanged(DraggableScrollableNotification notification) {
 1815         if (notification.extent > notification.initialExtent) {
 1816           if (_persistentSheetHistoryEntry == null) {
 1817             _persistentSheetHistoryEntry = LocalHistoryEntry(onRemove: () {
 1818               if (notification.extent > notification.initialExtent) {
 1819                 DraggableScrollableActuator.reset(notification.context);
 1820               }
 1821               showBodyScrim(false, 0.0);
 1822               _floatingActionButtonVisibilityValue = 1.0;
 1823               _persistentSheetHistoryEntry = null;
 1824             });
 1825             ModalRoute.of(context).addLocalHistoryEntry(_persistentSheetHistoryEntry);
 1826           }
 1827         } else if (_persistentSheetHistoryEntry != null) {
 1828           ModalRoute.of(context).removeLocalHistoryEntry(_persistentSheetHistoryEntry);
 1829         }
 1830         return false;
 1831       }
 1832 
 1833       _currentBottomSheet = _buildBottomSheet<void>(
 1834         (BuildContext context) {
 1835           return NotificationListener<DraggableScrollableNotification>(
 1836             onNotification: _persistentBottomSheetExtentChanged,
 1837             child: DraggableScrollableActuator(
 1838               child: widget.bottomSheet,
 1839             ),
 1840           );
 1841         },
 1842         true,
 1843         animationController: animationController,
 1844       );
 1845     }
 1846   }
 1847 
 1848   void _closeCurrentBottomSheet() {
 1849     if (_currentBottomSheet != null) {
 1850       if (!_currentBottomSheet._isLocalHistoryEntry) {
 1851         _currentBottomSheet.close();
 1852       }
 1853       assert(() {
 1854         _currentBottomSheet?._completer?.future?.whenComplete(() {
 1855           assert(_currentBottomSheet == null);
 1856         });
 1857         return true;
 1858       }());
 1859     }
 1860   }
 1861 
 1862   PersistentBottomSheetController<T> _buildBottomSheet<T>(
 1863     WidgetBuilder builder,
 1864     bool isPersistent, {
 1865     AnimationController animationController,
 1866     Color backgroundColor,
 1867     double elevation,
 1868     ShapeBorder shape,
 1869     Clip clipBehavior,
 1870   }) {
 1871     assert(() {
 1872       if (widget.bottomSheet != null && isPersistent && _currentBottomSheet != null) {
 1873         throw FlutterError(
 1874           'Scaffold.bottomSheet cannot be specified while a bottom sheet '
 1875           'displayed with showBottomSheet() is still visible.\n'
 1876           'Rebuild the Scaffold with a null bottomSheet before calling showBottomSheet().'
 1877         );
 1878       }
 1879       return true;
 1880     }());
 1881 
 1882     final Completer<T> completer = Completer<T>();
 1883     final GlobalKey<_StandardBottomSheetState> bottomSheetKey = GlobalKey<_StandardBottomSheetState>();
 1884     _StandardBottomSheet bottomSheet;
 1885 
 1886     bool removedEntry = false;
 1887     void _removeCurrentBottomSheet() {
 1888       removedEntry = true;
 1889       if (_currentBottomSheet == null) {
 1890         return;
 1891       }
 1892       assert(_currentBottomSheet._widget == bottomSheet);
 1893       assert(bottomSheetKey.currentState != null);
 1894       _showFloatingActionButton();
 1895 
 1896       void _closed(void value) {
 1897         setState(() {
 1898           _currentBottomSheet = null;
 1899         });
 1900 
 1901         if (animationController.status != AnimationStatus.dismissed) {
 1902           _dismissedBottomSheets.add(bottomSheet);
 1903         }
 1904         completer.complete();
 1905       }
 1906 
 1907       final Future<void> closing = bottomSheetKey.currentState.close();
 1908       if (closing != null) {
 1909         closing.then(_closed);
 1910       } else {
 1911         _closed(null);
 1912       }
 1913     }
 1914 
 1915     final LocalHistoryEntry entry = isPersistent
 1916       ? null
 1917       : LocalHistoryEntry(onRemove: () {
 1918           if (!removedEntry) {
 1919             _removeCurrentBottomSheet();
 1920           }
 1921         });
 1922 
 1923     bottomSheet = _StandardBottomSheet(
 1924       key: bottomSheetKey,
 1925       animationController: animationController,
 1926       enableDrag: !isPersistent,
 1927       onClosing: () {
 1928         if (_currentBottomSheet == null) {
 1929           return;
 1930         }
 1931         assert(_currentBottomSheet._widget == bottomSheet);
 1932         if (!isPersistent && !removedEntry) {
 1933           assert(entry != null);
 1934           entry.remove();
 1935           removedEntry = true;
 1936         }
 1937       },
 1938       onDismissed: () {
 1939         if (_dismissedBottomSheets.contains(bottomSheet)) {
 1940           setState(() {
 1941             _dismissedBottomSheets.remove(bottomSheet);
 1942           });
 1943         }
 1944       },
 1945       builder: builder,
 1946       isPersistent: isPersistent,
 1947       backgroundColor: backgroundColor,
 1948       elevation: elevation,
 1949       shape: shape,
 1950       clipBehavior: clipBehavior,
 1951     );
 1952 
 1953     if (!isPersistent)
 1954       ModalRoute.of(context).addLocalHistoryEntry(entry);
 1955 
 1956     return PersistentBottomSheetController<T>._(
 1957       bottomSheet,
 1958       completer,
 1959       entry != null
 1960         ? entry.remove
 1961         : _removeCurrentBottomSheet,
 1962       (VoidCallback fn) { bottomSheetKey.currentState?.setState(fn); },
 1963       !isPersistent,
 1964     );
 1965   }
 1966 
 1967   /// Shows a material design bottom sheet in the nearest [Scaffold]. To show
 1968   /// a persistent bottom sheet, use the [Scaffold.bottomSheet].
 1969   ///
 1970   /// Returns a controller that can be used to close and otherwise manipulate the
 1971   /// bottom sheet.
 1972   ///
 1973   /// To rebuild the bottom sheet (e.g. if it is stateful), call
 1974   /// [PersistentBottomSheetController.setState] on the controller returned by
 1975   /// this method.
 1976   ///
 1977   /// The new bottom sheet becomes a [LocalHistoryEntry] for the enclosing
 1978   /// [ModalRoute] and a back button is added to the app bar of the [Scaffold]
 1979   /// that closes the bottom sheet.
 1980   ///
 1981   /// To create a persistent bottom sheet that is not a [LocalHistoryEntry] and
 1982   /// does not add a back button to the enclosing Scaffold's app bar, use the
 1983   /// [Scaffold.bottomSheet] constructor parameter.
 1984   ///
 1985   /// A persistent bottom sheet shows information that supplements the primary
 1986   /// content of the app. A persistent bottom sheet remains visible even when
 1987   /// the user interacts with other parts of the app.
 1988   ///
 1989   /// A closely related widget is a modal bottom sheet, which is an alternative
 1990   /// to a menu or a dialog and prevents the user from interacting with the rest
 1991   /// of the app. Modal bottom sheets can be created and displayed with the
 1992   /// [showModalBottomSheet] function.
 1993   ///
 1994   /// {@tool dartpad --template=stateless_widget_scaffold}
 1995   ///
 1996   /// This example demonstrates how to use `showBottomSheet` to display a
 1997   /// bottom sheet when a user taps a button. It also demonstrates how to
 1998   /// close a bottom sheet using the Navigator.
 1999   ///
 2000   /// ```dart
 2001   /// Widget build(BuildContext context) {
 2002   ///   return Center(
 2003   ///     child: ElevatedButton(
 2004   ///       child: const Text('showBottomSheet'),
 2005   ///       onPressed: () {
 2006   ///         Scaffold.of(context).showBottomSheet<void>(
 2007   ///           (BuildContext context) {
 2008   ///             return Container(
 2009   ///               height: 200,
 2010   ///               color: Colors.amber,
 2011   ///               child: Center(
 2012   ///                 child: Column(
 2013   ///                   mainAxisAlignment: MainAxisAlignment.center,
 2014   ///                   mainAxisSize: MainAxisSize.min,
 2015   ///                   children: <Widget>[
 2016   ///                     const Text('BottomSheet'),
 2017   ///                     ElevatedButton(
 2018   ///                       child: const Text('Close BottomSheet'),
 2019   ///                       onPressed: () => Navigator.pop(context),
 2020   ///                     )
 2021   ///                   ],
 2022   ///                 ),
 2023   ///               ),
 2024   ///             );
 2025   ///           },
 2026   ///         );
 2027   ///       },
 2028   ///     ),
 2029   ///   );
 2030   /// }
 2031   /// ```
 2032   /// {@end-tool}
 2033   /// See also:
 2034   ///
 2035   ///  * [BottomSheet], which becomes the parent of the widget returned by the
 2036   ///    `builder`.
 2037   ///  * [showBottomSheet], which calls this method given a [BuildContext].
 2038   ///  * [showModalBottomSheet], which can be used to display a modal bottom
 2039   ///    sheet.
 2040   ///  * [Scaffold.of], for information about how to obtain the [ScaffoldState].
 2041   ///  * <https://material.io/design/components/sheets-bottom.html#standard-bottom-sheet>
 2042   PersistentBottomSheetController<T> showBottomSheet<T>(
 2043     WidgetBuilder builder, {
 2044     Color backgroundColor,
 2045     double elevation,
 2046     ShapeBorder shape,
 2047     Clip clipBehavior,
 2048   }) {
 2049     assert(() {
 2050       if (widget.bottomSheet != null) {
 2051         throw FlutterError(
 2052           'Scaffold.bottomSheet cannot be specified while a bottom sheet '
 2053           'displayed with showBottomSheet() is still visible.\n'
 2054           'Rebuild the Scaffold with a null bottomSheet before calling showBottomSheet().'
 2055         );
 2056       }
 2057       return true;
 2058     }());
 2059     assert(debugCheckHasMediaQuery(context));
 2060 
 2061     _closeCurrentBottomSheet();
 2062     final AnimationController controller = BottomSheet.createAnimationController(this)..forward();
 2063     setState(() {
 2064       _currentBottomSheet = _buildBottomSheet<T>(
 2065         builder,
 2066         false,
 2067         animationController: controller,
 2068         backgroundColor: backgroundColor,
 2069         elevation: elevation,
 2070         shape: shape,
 2071         clipBehavior: clipBehavior,
 2072       );
 2073     });
 2074     return _currentBottomSheet as PersistentBottomSheetController<T>;
 2075   }
 2076 
 2077   // Floating Action Button API
 2078   AnimationController _floatingActionButtonMoveController;
 2079   FloatingActionButtonAnimator _floatingActionButtonAnimator;
 2080   FloatingActionButtonLocation _previousFloatingActionButtonLocation;
 2081   FloatingActionButtonLocation _floatingActionButtonLocation;
 2082 
 2083   AnimationController _floatingActionButtonVisibilityController;
 2084 
 2085   /// Gets the current value of the visibility animation for the
 2086   /// [Scaffold.floatingActionButton].
 2087   double get _floatingActionButtonVisibilityValue => _floatingActionButtonVisibilityController.value;
 2088 
 2089   /// Sets the current value of the visibility animation for the
 2090   /// [Scaffold.floatingActionButton].  This value must not be null.
 2091   set _floatingActionButtonVisibilityValue(double newValue) {
 2092     assert(newValue != null);
 2093     _floatingActionButtonVisibilityController.value = newValue.clamp(
 2094       _floatingActionButtonVisibilityController.lowerBound,
 2095       _floatingActionButtonVisibilityController.upperBound,
 2096     ) as double;
 2097   }
 2098 
 2099   /// Shows the [Scaffold.floatingActionButton].
 2100   TickerFuture _showFloatingActionButton() {
 2101     return _floatingActionButtonVisibilityController.forward();
 2102   }
 2103 
 2104   // Moves the Floating Action Button to the new Floating Action Button Location.
 2105   void _moveFloatingActionButton(final FloatingActionButtonLocation newLocation) {
 2106     FloatingActionButtonLocation previousLocation = _floatingActionButtonLocation;
 2107     double restartAnimationFrom = 0.0;
 2108     // If the Floating Action Button is moving right now, we need to start from a snapshot of the current transition.
 2109     if (_floatingActionButtonMoveController.isAnimating) {
 2110       previousLocation = _TransitionSnapshotFabLocation(_previousFloatingActionButtonLocation, _floatingActionButtonLocation, _floatingActionButtonAnimator, _floatingActionButtonMoveController.value);
 2111       restartAnimationFrom = _floatingActionButtonAnimator.getAnimationRestart(_floatingActionButtonMoveController.value);
 2112     }
 2113 
 2114     setState(() {
 2115       _previousFloatingActionButtonLocation = previousLocation;
 2116       _floatingActionButtonLocation = newLocation;
 2117     });
 2118 
 2119     // Animate the motion even when the fab is null so that if the exit animation is running,
 2120     // the old fab will start the motion transition while it exits instead of jumping to the
 2121     // new position.
 2122     _floatingActionButtonMoveController.forward(from: restartAnimationFrom);
 2123   }
 2124 
 2125   // iOS FEATURES - status bar tap, back gesture
 2126 
 2127   // On iOS, tapping the status bar scrolls the app's primary scrollable to the
 2128   // top. We implement this by providing a primary scroll controller and
 2129   // scrolling it to the top when tapped.
 2130 
 2131   final ScrollController _primaryScrollController = ScrollController();
 2132 
 2133   void _handleStatusBarTap() {
 2134     if (_primaryScrollController.hasClients) {
 2135       _primaryScrollController.animateTo(
 2136         0.0,
 2137         duration: const Duration(milliseconds: 300),
 2138         curve: Curves.linear, // TODO(ianh): Use a more appropriate curve.
 2139       );
 2140     }
 2141   }
 2142 
 2143   // INTERNALS
 2144 
 2145   _ScaffoldGeometryNotifier _geometryNotifier;
 2146 
 2147   // Backwards compatibility for deprecated resizeToAvoidBottomPadding property
 2148   bool get _resizeToAvoidBottomInset {
 2149     return widget.resizeToAvoidBottomInset ?? widget.resizeToAvoidBottomPadding ?? true;
 2150   }
 2151 
 2152   @override
 2153   void initState() {
 2154     super.initState();
 2155     _geometryNotifier = _ScaffoldGeometryNotifier(const ScaffoldGeometry(), context);
 2156     _floatingActionButtonLocation = widget.floatingActionButtonLocation ?? _kDefaultFloatingActionButtonLocation;
 2157     _floatingActionButtonAnimator = widget.floatingActionButtonAnimator ?? _kDefaultFloatingActionButtonAnimator;
 2158     _previousFloatingActionButtonLocation = _floatingActionButtonLocation;
 2159     _floatingActionButtonMoveController = AnimationController(
 2160       vsync: this,
 2161       lowerBound: 0.0,
 2162       upperBound: 1.0,
 2163       value: 1.0,
 2164       duration: kFloatingActionButtonSegue * 2,
 2165     );
 2166 
 2167     _floatingActionButtonVisibilityController = AnimationController(
 2168       duration: kFloatingActionButtonSegue,
 2169       vsync: this,
 2170     );
 2171   }
 2172 
 2173   @override
 2174   void didUpdateWidget(Scaffold oldWidget) {
 2175     // Update the Floating Action Button Animator, and then schedule the Floating Action Button for repositioning.
 2176     if (widget.floatingActionButtonAnimator != oldWidget.floatingActionButtonAnimator) {
 2177       _floatingActionButtonAnimator = widget.floatingActionButtonAnimator ?? _kDefaultFloatingActionButtonAnimator;
 2178     }
 2179     if (widget.floatingActionButtonLocation != oldWidget.floatingActionButtonLocation) {
 2180       _moveFloatingActionButton(widget.floatingActionButtonLocation ?? _kDefaultFloatingActionButtonLocation);
 2181     }
 2182     if (widget.bottomSheet != oldWidget.bottomSheet) {
 2183       assert(() {
 2184         if (widget.bottomSheet != null && _currentBottomSheet?._isLocalHistoryEntry == true) {
 2185           throw FlutterError.fromParts(<DiagnosticsNode>[
 2186             ErrorSummary(
 2187               'Scaffold.bottomSheet cannot be specified while a bottom sheet displayed '
 2188               'with showBottomSheet() is still visible.'
 2189             ),
 2190             ErrorHint(
 2191               'Use the PersistentBottomSheetController '
 2192               'returned by showBottomSheet() to close the old bottom sheet before creating '
 2193               'a Scaffold with a (non null) bottomSheet.'
 2194             ),
 2195           ]);
 2196         }
 2197         return true;
 2198       }());
 2199       _closeCurrentBottomSheet();
 2200       _maybeBuildPersistentBottomSheet();
 2201     }
 2202     super.didUpdateWidget(oldWidget);
 2203   }
 2204 
 2205   @override
 2206   void didChangeDependencies() {
 2207     final MediaQueryData mediaQuery = MediaQuery.of(context);
 2208     // If we transition from accessible navigation to non-accessible navigation
 2209     // and there is a SnackBar that would have timed out that has already
 2210     // completed its timer, dismiss that SnackBar. If the timer hasn't finished
 2211     // yet, let it timeout as normal.
 2212     if (_accessibleNavigation == true
 2213       && !mediaQuery.accessibleNavigation
 2214       && _snackBarTimer != null
 2215       && !_snackBarTimer.isActive) {
 2216       hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
 2217     }
 2218     _accessibleNavigation = mediaQuery.accessibleNavigation;
 2219     _maybeBuildPersistentBottomSheet();
 2220     super.didChangeDependencies();
 2221   }
 2222 
 2223   @override
 2224   void dispose() {
 2225     _snackBarController?.dispose();
 2226     _snackBarTimer?.cancel();
 2227     _snackBarTimer = null;
 2228     _geometryNotifier.dispose();
 2229     for (final _StandardBottomSheet bottomSheet in _dismissedBottomSheets) {
 2230       bottomSheet.animationController?.dispose();
 2231     }
 2232     if (_currentBottomSheet != null) {
 2233       _currentBottomSheet._widget.animationController?.dispose();
 2234     }
 2235     _floatingActionButtonMoveController.dispose();
 2236     _floatingActionButtonVisibilityController.dispose();
 2237     super.dispose();
 2238   }
 2239 
 2240   void _addIfNonNull(
 2241     List<LayoutId> children,
 2242     Widget child,
 2243     Object childId, {
 2244     @required bool removeLeftPadding,
 2245     @required bool removeTopPadding,
 2246     @required bool removeRightPadding,
 2247     @required bool removeBottomPadding,
 2248     bool removeBottomInset = false,
 2249     bool maintainBottomViewPadding = false,
 2250   }) {
 2251     MediaQueryData data = MediaQuery.of(context).removePadding(
 2252       removeLeft: removeLeftPadding,
 2253       removeTop: removeTopPadding,
 2254       removeRight: removeRightPadding,
 2255       removeBottom: removeBottomPadding,
 2256     );
 2257     if (removeBottomInset)
 2258       data = data.removeViewInsets(removeBottom: true);
 2259 
 2260     if (maintainBottomViewPadding && data.viewInsets.bottom != 0.0) {
 2261       data = data.copyWith(
 2262         padding: data.padding.copyWith(bottom: data.viewPadding.bottom)
 2263       );
 2264     }
 2265 
 2266     if (child != null) {
 2267       children.add(
 2268         LayoutId(
 2269           id: childId,
 2270           child: MediaQuery(data: data, child: child),
 2271         ),
 2272       );
 2273     }
 2274   }
 2275 
 2276   void _buildEndDrawer(List<LayoutId> children, TextDirection textDirection) {
 2277     if (widget.endDrawer != null) {
 2278       assert(hasEndDrawer);
 2279       _addIfNonNull(
 2280         children,
 2281         DrawerController(
 2282           key: _endDrawerKey,
 2283           alignment: DrawerAlignment.end,
 2284           child: widget.endDrawer,
 2285           drawerCallback: _endDrawerOpenedCallback,
 2286           dragStartBehavior: widget.drawerDragStartBehavior,
 2287           scrimColor: widget.drawerScrimColor,
 2288           edgeDragWidth: widget.drawerEdgeDragWidth,
 2289           enableOpenDragGesture: widget.endDrawerEnableOpenDragGesture,
 2290         ),
 2291         _ScaffoldSlot.endDrawer,
 2292         // remove the side padding from the side we're not touching
 2293         removeLeftPadding: textDirection == TextDirection.ltr,
 2294         removeTopPadding: false,
 2295         removeRightPadding: textDirection == TextDirection.rtl,
 2296         removeBottomPadding: false,
 2297       );
 2298     }
 2299   }
 2300 
 2301   void _buildDrawer(List<LayoutId> children, TextDirection textDirection) {
 2302     if (widget.drawer != null) {
 2303       assert(hasDrawer);
 2304       _addIfNonNull(
 2305         children,
 2306         DrawerController(
 2307           key: _drawerKey,
 2308           alignment: DrawerAlignment.start,
 2309           child: widget.drawer,
 2310           drawerCallback: _drawerOpenedCallback,
 2311           dragStartBehavior: widget.drawerDragStartBehavior,
 2312           scrimColor: widget.drawerScrimColor,
 2313           edgeDragWidth: widget.drawerEdgeDragWidth,
 2314           enableOpenDragGesture: widget.drawerEnableOpenDragGesture,
 2315         ),
 2316         _ScaffoldSlot.drawer,
 2317         // remove the side padding from the side we're not touching
 2318         removeLeftPadding: textDirection == TextDirection.rtl,
 2319         removeTopPadding: false,
 2320         removeRightPadding: textDirection == TextDirection.ltr,
 2321         removeBottomPadding: false,
 2322       );
 2323     }
 2324   }
 2325 
 2326   bool _showBodyScrim = false;
 2327   Color _bodyScrimColor = Colors.black;
 2328 
 2329   /// Whether to show a [ModalBarrier] over the body of the scaffold.
 2330   ///
 2331   /// The `value` parameter must not be null.
 2332   void showBodyScrim(bool value, double opacity) {
 2333     assert(value != null);
 2334     if (_showBodyScrim == value && _bodyScrimColor.opacity == opacity) {
 2335       return;
 2336     }
 2337     setState(() {
 2338       _showBodyScrim = value;
 2339       _bodyScrimColor = Colors.black.withOpacity(opacity);
 2340     });
 2341   }
 2342 
 2343   @override
 2344   Widget build(BuildContext context) {
 2345     assert(debugCheckHasMediaQuery(context));
 2346     assert(debugCheckHasDirectionality(context));
 2347     final MediaQueryData mediaQuery = MediaQuery.of(context);
 2348     final ThemeData themeData = Theme.of(context);
 2349     final TextDirection textDirection = Directionality.of(context);
 2350     _accessibleNavigation = mediaQuery.accessibleNavigation;
 2351 
 2352     if (_snackBars.isNotEmpty) {
 2353       final ModalRoute<dynamic> route = ModalRoute.of(context);
 2354       if (route == null || route.isCurrent) {
 2355         if (_snackBarController.isCompleted && _snackBarTimer == null) {
 2356           final SnackBar snackBar = _snackBars.first._widget;
 2357           _snackBarTimer = Timer(snackBar.duration, () {
 2358             assert(_snackBarController.status == AnimationStatus.forward ||
 2359                    _snackBarController.status == AnimationStatus.completed);
 2360             // Look up MediaQuery again in case the setting changed.
 2361             final MediaQueryData mediaQuery = MediaQuery.of(context);
 2362             if (mediaQuery.accessibleNavigation && snackBar.action != null)
 2363               return;
 2364             hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
 2365           });
 2366         }
 2367       } else {
 2368         _snackBarTimer?.cancel();
 2369         _snackBarTimer = null;
 2370       }
 2371     }
 2372 
 2373     final List<LayoutId> children = <LayoutId>[];
 2374     _addIfNonNull(
 2375       children,
 2376       widget.body == null ? null : _BodyBuilder(
 2377         extendBody: widget.extendBody,
 2378         extendBodyBehindAppBar: widget.extendBodyBehindAppBar,
 2379         body: widget.body,
 2380       ),
 2381       _ScaffoldSlot.body,
 2382       removeLeftPadding: false,
 2383       removeTopPadding: widget.appBar != null,
 2384       removeRightPadding: false,
 2385       removeBottomPadding: widget.bottomNavigationBar != null || widget.persistentFooterButtons != null,
 2386       removeBottomInset: _resizeToAvoidBottomInset,
 2387     );
 2388     if (_showBodyScrim) {
 2389       _addIfNonNull(
 2390         children,
 2391         ModalBarrier(
 2392           dismissible: false,
 2393           color: _bodyScrimColor,
 2394         ),
 2395         _ScaffoldSlot.bodyScrim,
 2396         removeLeftPadding: true,
 2397         removeTopPadding: true,
 2398         removeRightPadding: true,
 2399         removeBottomPadding: true,
 2400       );
 2401     }
 2402 
 2403     if (widget.appBar != null) {
 2404       final double topPadding = widget.primary ? mediaQuery.padding.top : 0.0;
 2405       _appBarMaxHeight = widget.appBar.preferredSize.height + topPadding;
 2406       assert(_appBarMaxHeight >= 0.0 && _appBarMaxHeight.isFinite);
 2407       _addIfNonNull(
 2408         children,
 2409         ConstrainedBox(
 2410           constraints: BoxConstraints(maxHeight: _appBarMaxHeight),
 2411           child: FlexibleSpaceBar.createSettings(
 2412             currentExtent: _appBarMaxHeight,
 2413             child: widget.appBar,
 2414           ),
 2415         ),
 2416         _ScaffoldSlot.appBar,
 2417         removeLeftPadding: false,
 2418         removeTopPadding: false,
 2419         removeRightPadding: false,
 2420         removeBottomPadding: true,
 2421       );
 2422     }
 2423 
 2424     bool isSnackBarFloating = false;
 2425     double snackBarWidth;
 2426     if (_snackBars.isNotEmpty) {
 2427       final SnackBarBehavior snackBarBehavior = _snackBars.first._widget.behavior
 2428         ?? themeData.snackBarTheme.behavior
 2429         ?? SnackBarBehavior.fixed;
 2430       isSnackBarFloating = snackBarBehavior == SnackBarBehavior.floating;
 2431       snackBarWidth = _snackBars.first._widget.width;
 2432 
 2433       _addIfNonNull(
 2434         children,
 2435         _snackBars.first._widget,
 2436         _ScaffoldSlot.snackBar,
 2437         removeLeftPadding: false,
 2438         removeTopPadding: true,
 2439         removeRightPadding: false,
 2440         removeBottomPadding: widget.bottomNavigationBar != null || widget.persistentFooterButtons != null,
 2441         maintainBottomViewPadding: !_resizeToAvoidBottomInset,
 2442       );
 2443     }
 2444 
 2445     if (widget.persistentFooterButtons != null) {
 2446       _addIfNonNull(
 2447         children,
 2448         Container(
 2449           decoration: BoxDecoration(
 2450             border: Border(
 2451               top: Divider.createBorderSide(context, width: 1.0),
 2452             ),
 2453           ),
 2454           child: SafeArea(
 2455             top: false,
 2456             child: ButtonBar(
 2457               children: widget.persistentFooterButtons,
 2458             ),
 2459           ),
 2460         ),
 2461         _ScaffoldSlot.persistentFooter,
 2462         removeLeftPadding: false,
 2463         removeTopPadding: true,
 2464         removeRightPadding: false,
 2465         removeBottomPadding: false,
 2466         maintainBottomViewPadding: !_resizeToAvoidBottomInset,
 2467       );
 2468     }
 2469 
 2470     if (widget.bottomNavigationBar != null) {
 2471       _addIfNonNull(
 2472         children,
 2473         widget.bottomNavigationBar,
 2474         _ScaffoldSlot.bottomNavigationBar,
 2475         removeLeftPadding: false,
 2476         removeTopPadding: true,
 2477         removeRightPadding: false,
 2478         removeBottomPadding: false,
 2479         maintainBottomViewPadding: !_resizeToAvoidBottomInset,
 2480       );
 2481     }
 2482 
 2483     if (_currentBottomSheet != null || _dismissedBottomSheets.isNotEmpty) {
 2484       final Widget stack = Stack(
 2485         alignment: Alignment.bottomCenter,
 2486         children: <Widget>[
 2487           ..._dismissedBottomSheets,
 2488           if (_currentBottomSheet != null) _currentBottomSheet._widget,
 2489         ],
 2490       );
 2491       _addIfNonNull(
 2492         children,
 2493         stack,
 2494         _ScaffoldSlot.bottomSheet,
 2495         removeLeftPadding: false,
 2496         removeTopPadding: true,
 2497         removeRightPadding: false,
 2498         removeBottomPadding: _resizeToAvoidBottomInset,
 2499       );
 2500     }
 2501 
 2502     _addIfNonNull(
 2503       children,
 2504       _FloatingActionButtonTransition(
 2505         child: widget.floatingActionButton,
 2506         fabMoveAnimation: _floatingActionButtonMoveController,
 2507         fabMotionAnimator: _floatingActionButtonAnimator,
 2508         geometryNotifier: _geometryNotifier,
 2509         currentController: _floatingActionButtonVisibilityController,
 2510       ),
 2511       _ScaffoldSlot.floatingActionButton,
 2512       removeLeftPadding: true,
 2513       removeTopPadding: true,
 2514       removeRightPadding: true,
 2515       removeBottomPadding: true,
 2516     );
 2517 
 2518     switch (themeData.platform) {
 2519       case TargetPlatform.iOS:
 2520       case TargetPlatform.macOS:
 2521         _addIfNonNull(
 2522           children,
 2523           GestureDetector(
 2524             behavior: HitTestBehavior.opaque,
 2525             onTap: _handleStatusBarTap,
 2526             // iOS accessibility automatically adds scroll-to-top to the clock in the status bar
 2527             excludeFromSemantics: true,
 2528           ),
 2529           _ScaffoldSlot.statusBar,
 2530           removeLeftPadding: false,
 2531           removeTopPadding: true,
 2532           removeRightPadding: false,
 2533           removeBottomPadding: true,
 2534         );
 2535         break;
 2536       case TargetPlatform.android:
 2537       case TargetPlatform.fuchsia:
 2538       case TargetPlatform.linux:
 2539       case TargetPlatform.windows:
 2540         break;
 2541     }
 2542 
 2543     if (_endDrawerOpened) {
 2544       _buildDrawer(children, textDirection);
 2545       _buildEndDrawer(children, textDirection);
 2546     } else {
 2547       _buildEndDrawer(children, textDirection);
 2548       _buildDrawer(children, textDirection);
 2549     }
 2550 
 2551     // The minimum insets for contents of the Scaffold to keep visible.
 2552     final EdgeInsets minInsets = mediaQuery.padding.copyWith(
 2553       bottom: _resizeToAvoidBottomInset ? mediaQuery.viewInsets.bottom : 0.0,
 2554     );
 2555 
 2556     // The minimum viewPadding for interactive elements positioned by the
 2557     // Scaffold to keep within safe interactive areas.
 2558     final EdgeInsets minViewPadding = mediaQuery.viewPadding.copyWith(
 2559       bottom: _resizeToAvoidBottomInset &&  mediaQuery.viewInsets.bottom != 0.0 ? 0.0 : null,
 2560     );
 2561 
 2562     // extendBody locked when keyboard is open
 2563     final bool _extendBody = minInsets.bottom <= 0 && widget.extendBody;
 2564 
 2565     return _ScaffoldScope(
 2566       hasDrawer: hasDrawer,
 2567       geometryNotifier: _geometryNotifier,
 2568       child: PrimaryScrollController(
 2569         controller: _primaryScrollController,
 2570         child: Material(
 2571           color: widget.backgroundColor ?? themeData.scaffoldBackgroundColor,
 2572           child: AnimatedBuilder(animation: _floatingActionButtonMoveController, builder: (BuildContext context, Widget child) {
 2573             return CustomMultiChildLayout(
 2574               children: children,
 2575               delegate: _ScaffoldLayout(
 2576                 extendBody: _extendBody,
 2577                 extendBodyBehindAppBar: widget.extendBodyBehindAppBar,
 2578                 minInsets: minInsets,
 2579                 minViewPadding: minViewPadding,
 2580                 currentFloatingActionButtonLocation: _floatingActionButtonLocation,
 2581                 floatingActionButtonMoveAnimationProgress: _floatingActionButtonMoveController.value,
 2582                 floatingActionButtonMotionAnimator: _floatingActionButtonAnimator,
 2583                 geometryNotifier: _geometryNotifier,
 2584                 previousFloatingActionButtonLocation: _previousFloatingActionButtonLocation,
 2585                 textDirection: textDirection,
 2586                 isSnackBarFloating: isSnackBarFloating,
 2587                 snackBarWidth: snackBarWidth,
 2588               ),
 2589             );
 2590           }),
 2591         ),
 2592       ),
 2593     );
 2594   }
 2595 }
 2596 
 2597 /// An interface for controlling a feature of a [Scaffold].
 2598 ///
 2599 /// Commonly obtained from [ScaffoldState.showSnackBar] or [ScaffoldState.showBottomSheet].
 2600 class ScaffoldFeatureController<T extends Widget, U> {
 2601   const ScaffoldFeatureController._(this._widget, this._completer, this.close, this.setState);
 2602   final T _widget;
 2603   final Completer<U> _completer;
 2604 
 2605   /// Completes when the feature controlled by this object is no longer visible.
 2606   Future<U> get closed => _completer.future;
 2607 
 2608   /// Remove the feature (e.g., bottom sheet or snack bar) from the scaffold.
 2609   final VoidCallback close;
 2610 
 2611   /// Mark the feature (e.g., bottom sheet or snack bar) as needing to rebuild.
 2612   final StateSetter setState;
 2613 }
 2614 
 2615 // TODO(guidezpl): Look into making this public. A copy of this class is in
 2616 //  bottom_sheet.dart, for now, https://github.com/flutter/flutter/issues/51627
 2617 /// A curve that progresses linearly until a specified [startingPoint], at which
 2618 /// point [curve] will begin. Unlike [Interval], [curve] will not start at zero,
 2619 /// but will use [startingPoint] as the Y position.
 2620 ///
 2621 /// For example, if [startingPoint] is set to `0.5`, and [curve] is set to
 2622 /// [Curves.easeOut], then the bottom-left quarter of the curve will be a
 2623 /// straight line, and the top-right quarter will contain the entire contents of
 2624 /// [Curves.easeOut].
 2625 ///
 2626 /// This is useful in situations where a widget must track the user's finger
 2627 /// (which requires a linear animation), and afterwards can be flung using a
 2628 /// curve specified with the [curve] argument, after the finger is released. In
 2629 /// such a case, the value of [startingPoint] would be the progress of the
 2630 /// animation at the time when the finger was released.
 2631 ///
 2632 /// The [startingPoint] and [curve] arguments must not be null.
 2633 class _BottomSheetSuspendedCurve extends ParametricCurve<double> {
 2634   /// Creates a suspended curve.
 2635   const _BottomSheetSuspendedCurve(
 2636       this.startingPoint, {
 2637         this.curve = Curves.easeOutCubic,
 2638       }) : assert(startingPoint != null),
 2639         assert(curve != null);
 2640 
 2641   /// The progress value at which [curve] should begin.
 2642   ///
 2643   /// This defaults to [Curves.easeOutCubic].
 2644   final double startingPoint;
 2645 
 2646   /// The curve to use when [startingPoint] is reached.
 2647   final Curve curve;
 2648 
 2649   @override
 2650   double transform(double t) {
 2651     assert(t >= 0.0 && t <= 1.0);
 2652     assert(startingPoint >= 0.0 && startingPoint <= 1.0);
 2653 
 2654     if (t < startingPoint) {
 2655       return t;
 2656     }
 2657 
 2658     if (t == 1.0) {
 2659       return t;
 2660     }
 2661 
 2662     final double curveProgress = (t - startingPoint) / (1 - startingPoint);
 2663     final double transformed = curve.transform(curveProgress);
 2664     return lerpDouble(startingPoint, 1, transformed);
 2665   }
 2666 
 2667   @override
 2668   String toString() {
 2669     return '${describeIdentity(this)}($startingPoint, $curve)';
 2670   }
 2671 }
 2672 
 2673 class _StandardBottomSheet extends StatefulWidget {
 2674   const _StandardBottomSheet({
 2675     Key key,
 2676     this.animationController,
 2677     this.enableDrag = true,
 2678     this.onClosing,
 2679     this.onDismissed,
 2680     this.builder,
 2681     this.isPersistent = false,
 2682     this.backgroundColor,
 2683     this.elevation,
 2684     this.shape,
 2685     this.clipBehavior,
 2686   }) : super(key: key);
 2687 
 2688   final AnimationController animationController; // we control it, but it must be disposed by whoever created it.
 2689   final bool enableDrag;
 2690   final VoidCallback onClosing;
 2691   final VoidCallback onDismissed;
 2692   final WidgetBuilder builder;
 2693   final bool isPersistent;
 2694   final Color backgroundColor;
 2695   final double elevation;
 2696   final ShapeBorder shape;
 2697   final Clip clipBehavior;
 2698 
 2699   @override
 2700   _StandardBottomSheetState createState() => _StandardBottomSheetState();
 2701 }
 2702 
 2703 class _StandardBottomSheetState extends State<_StandardBottomSheet> {
 2704   ParametricCurve<double> animationCurve = _standardBottomSheetCurve;
 2705 
 2706   @override
 2707   void initState() {
 2708     super.initState();
 2709     assert(widget.animationController != null);
 2710     assert(widget.animationController.status == AnimationStatus.forward
 2711         || widget.animationController.status == AnimationStatus.completed);
 2712     widget.animationController.addStatusListener(_handleStatusChange);
 2713   }
 2714 
 2715   @override
 2716   void didUpdateWidget(_StandardBottomSheet oldWidget) {
 2717     super.didUpdateWidget(oldWidget);
 2718     assert(widget.animationController == oldWidget.animationController);
 2719   }
 2720 
 2721   Future<void> close() {
 2722     assert(widget.animationController != null);
 2723     widget.animationController.reverse();
 2724     if (widget.onClosing != null) {
 2725       widget.onClosing();
 2726     }
 2727     return null;
 2728   }
 2729 
 2730   void _handleDragStart(DragStartDetails details) {
 2731     // Allow the bottom sheet to track the user's finger accurately.
 2732     animationCurve = Curves.linear;
 2733   }
 2734 
 2735   void _handleDragEnd(DragEndDetails details, { bool isClosing }) {
 2736     // Allow the bottom sheet to animate smoothly from its current position.
 2737     animationCurve = _BottomSheetSuspendedCurve(
 2738       widget.animationController.value,
 2739       curve: _standardBottomSheetCurve,
 2740     );
 2741   }
 2742 
 2743   void _handleStatusChange(AnimationStatus status) {
 2744     if (status == AnimationStatus.dismissed && widget.onDismissed != null) {
 2745       widget.onDismissed();
 2746     }
 2747   }
 2748 
 2749   bool extentChanged(DraggableScrollableNotification notification) {
 2750     final double extentRemaining = 1.0 - notification.extent;
 2751     final ScaffoldState scaffold = Scaffold.of(context);
 2752     if (extentRemaining < _kBottomSheetDominatesPercentage) {
 2753       scaffold._floatingActionButtonVisibilityValue = extentRemaining * _kBottomSheetDominatesPercentage * 10;
 2754       scaffold.showBodyScrim(true,  math.max(
 2755         _kMinBottomSheetScrimOpacity,
 2756         _kMaxBottomSheetScrimOpacity - scaffold._floatingActionButtonVisibilityValue,
 2757       ));
 2758     } else {
 2759       scaffold._floatingActionButtonVisibilityValue = 1.0;
 2760       scaffold.showBodyScrim(false, 0.0);
 2761     }
 2762     // If the Scaffold.bottomSheet != null, we're a persistent bottom sheet.
 2763     if (notification.extent == notification.minExtent && scaffold.widget.bottomSheet == null) {
 2764       close();
 2765     }
 2766     return false;
 2767   }
 2768 
 2769   Widget _wrapBottomSheet(Widget bottomSheet) {
 2770     return Semantics(
 2771       container: true,
 2772       onDismiss: close,
 2773       child:  NotificationListener<DraggableScrollableNotification>(
 2774         onNotification: extentChanged,
 2775         child: bottomSheet,
 2776       ),
 2777     );
 2778   }
 2779 
 2780   @override
 2781   Widget build(BuildContext context) {
 2782     if (widget.animationController != null) {
 2783       return AnimatedBuilder(
 2784         animation: widget.animationController,
 2785         builder: (BuildContext context, Widget child) {
 2786           return Align(
 2787             alignment: AlignmentDirectional.topStart,
 2788             heightFactor: animationCurve.transform(widget.animationController.value),
 2789             child: child,
 2790           );
 2791         },
 2792         child: _wrapBottomSheet(
 2793           BottomSheet(
 2794             animationController: widget.animationController,
 2795             enableDrag: widget.enableDrag,
 2796             onDragStart: _handleDragStart,
 2797             onDragEnd: _handleDragEnd,
 2798             onClosing: widget.onClosing,
 2799             builder: widget.builder,
 2800             backgroundColor: widget.backgroundColor,
 2801             elevation: widget.elevation,
 2802             shape: widget.shape,
 2803             clipBehavior: widget.clipBehavior,
 2804           ),
 2805         ),
 2806       );
 2807     }
 2808 
 2809     return _wrapBottomSheet(
 2810       BottomSheet(
 2811         onClosing: widget.onClosing,
 2812         builder: widget.builder,
 2813         backgroundColor: widget.backgroundColor,
 2814       ),
 2815     );
 2816   }
 2817 
 2818 }
 2819 
 2820 /// A [ScaffoldFeatureController] for standard bottom sheets.
 2821 ///
 2822 /// This is the type of objects returned by [ScaffoldState.showBottomSheet].
 2823 ///
 2824 /// This controller is used to display both standard and persistent bottom
 2825 /// sheets. A bottom sheet is only persistent if it is set as the
 2826 /// [Scaffold.bottomSheet].
 2827 class PersistentBottomSheetController<T> extends ScaffoldFeatureController<_StandardBottomSheet, T> {
 2828   const PersistentBottomSheetController._(
 2829     _StandardBottomSheet widget,
 2830     Completer<T> completer,
 2831     VoidCallback close,
 2832     StateSetter setState,
 2833     this._isLocalHistoryEntry,
 2834   ) : super._(widget, completer, close, setState);
 2835 
 2836   final bool _isLocalHistoryEntry;
 2837 }
 2838 
 2839 class _ScaffoldScope extends InheritedWidget {
 2840   const _ScaffoldScope({
 2841     Key key,
 2842     @required this.hasDrawer,
 2843     @required this.geometryNotifier,
 2844     @required Widget child,
 2845   }) : assert(hasDrawer != null),
 2846        super(key: key, child: child);
 2847 
 2848   final bool hasDrawer;
 2849   final _ScaffoldGeometryNotifier geometryNotifier;
 2850 
 2851   @override
 2852   bool updateShouldNotify(_ScaffoldScope oldWidget) {
 2853     return hasDrawer != oldWidget.hasDrawer;
 2854   }
 2855 }