|
|
|
|
|
|
using System; |
|
|
|
using System.Linq; |
|
|
|
using RSG; |
|
|
|
using Unity.UIWidgets.async; |
|
|
|
using Unity.UIWidgets.foundation; |
|
|
|
using Unity.UIWidgets.painting; |
|
|
|
using Unity.UIWidgets.rendering; |
|
|
|
|
|
|
|
|
|
|
namespace Unity.UIWidgets.material { |
|
|
|
static class ScaffoldUtils { |
|
|
|
public static FloatingActionButtonLocation _kDefaultFloatingActionButtonLocation = |
|
|
|
public static readonly FloatingActionButtonLocation _kDefaultFloatingActionButtonLocation = |
|
|
|
public static FloatingActionButtonAnimator _kDefaultFloatingActionButtonAnimator = |
|
|
|
public static readonly FloatingActionButtonAnimator _kDefaultFloatingActionButtonAnimator = |
|
|
|
FloatingActionButtonAnimator.scaling; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static ScaffoldState of(BuildContext context, bool nullOk = false) { |
|
|
|
D.assert(context != null); |
|
|
|
ScaffoldState result = (ScaffoldState)context.ancestorStateOfType(new TypeMatcher<ScaffoldState>()); |
|
|
|
ScaffoldState result = (ScaffoldState) context.ancestorStateOfType(new TypeMatcher<ScaffoldState>()); |
|
|
|
if (nullOk || result != null) { |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
static ValueListenable<ScaffoldGeometry> geometryOf(BuildContext context) { |
|
|
|
_ScaffoldScope scaffoldScope = (_ScaffoldScope)context.inheritFromWidgetOfExactType(typeof(_ScaffoldScope)); |
|
|
|
_ScaffoldScope scaffoldScope = |
|
|
|
(_ScaffoldScope) context.inheritFromWidgetOfExactType(typeof(_ScaffoldScope)); |
|
|
|
if (scaffoldScope == null) { |
|
|
|
throw new UIWidgetsError( |
|
|
|
"Scaffold.geometryOf() called with a context that does not contain a Scaffold.\n" + |
|
|
|
|
|
|
static bool hasDrawer(BuildContext context, bool registerForUpdates = true) { |
|
|
|
D.assert(context != null); |
|
|
|
if (registerForUpdates) { |
|
|
|
_ScaffoldScope scaffold = (_ScaffoldScope)context.inheritFromWidgetOfExactType(typeof(_ScaffoldScope)); |
|
|
|
_ScaffoldScope scaffold = (_ScaffoldScope) context.inheritFromWidgetOfExactType(typeof(_ScaffoldScope)); |
|
|
|
ScaffoldState scaffold = context.ancestorStateOfType(new TypeMatcher<ScaffoldState>()); |
|
|
|
ScaffoldState scaffold = (ScaffoldState) context.ancestorStateOfType(new TypeMatcher<ScaffoldState>()); |
|
|
|
return scaffold?.hasDrawer ?? false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
public class ScaffoldState : TickerProviderStateMixin<Scaffold> { |
|
|
|
|
|
|
|
public readonly GlobalKey<DrawerControllerState> _drawerKey = GlobalKey<DrawerControllerState>.key(); |
|
|
|
public readonly GlobalKey<DrawerControllerState> _endDrawerKey = GlobalKey<DrawerControllerState>.key(); |
|
|
|
|
|
|
|
|
|
|
if (this._drawerKey.currentState != null && this._drawerOpened) { |
|
|
|
this._drawerKey.currentState.close(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
readonly Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>> _snackBars = |
|
|
|
new Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>>(); |
|
|
|
|
|
|
|
AnimationController _snackBarController; |
|
|
|
Timer _snackBarTimer; |
|
|
|
bool _accessibleNavigation; |
|
|
|
|
|
|
|
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(SnackBar snackbar) { |
|
|
|
this._snackBarController = this._snackBarController ?? SnackBar.createAnimationController(vsync: this); |
|
|
|
this._snackBarController.addStatusListener(this._handleSnackBarStatusChange); |
|
|
|
if (this._snackBars.isEmpty()) { |
|
|
|
D.assert(this._snackBarController.isDismissed); |
|
|
|
this._snackBarController.forward(); |
|
|
|
} |
|
|
|
|
|
|
|
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> controller = |
|
|
|
new ScaffoldFeatureController<SnackBar, SnackBarClosedReason>( |
|
|
|
snackbar.withAnimation(this._snackBarController, fallbackKey: new UniqueKey()), |
|
|
|
new Promise<SnackBarClosedReason>(), |
|
|
|
() => { |
|
|
|
D.assert(this._snackBars.First() == controller); |
|
|
|
this.hideCurrentSnackBar(reason: SnackBarClosedReason.hide); |
|
|
|
}, |
|
|
|
null); |
|
|
|
|
|
|
|
this.setState(() => { this._snackBars.Enqueue(controller); }); |
|
|
|
return controller; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void _handleSnackBarStatusChange(AnimationStatus status) { |
|
|
|
switch (status) { |
|
|
|
case AnimationStatus.dismissed: { |
|
|
|
D.assert(this._snackBars.isNotEmpty()); |
|
|
|
this.setState(() => { this._snackBars.Dequeue(); }); |
|
|
|
if (this._snackBars.isNotEmpty()) { |
|
|
|
this._snackBarController.forward(); |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
case AnimationStatus.completed: { |
|
|
|
this.setState(() => { D.assert(this._snackBarTimer == null); }); |
|
|
|
break; |
|
|
|
} |
|
|
|
case AnimationStatus.forward: |
|
|
|
case AnimationStatus.reverse: { |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public void removeCurrentSnackBar(SnackBarClosedReason reason = SnackBarClosedReason.remove) { |
|
|
|
if (this._snackBars.isEmpty()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
Promise<SnackBarClosedReason> completer = this._snackBars.First()._completer; |
|
|
|
if (!completer.isCompleted) { |
|
|
|
completer.Resolve(reason); |
|
|
|
} |
|
|
|
|
|
|
|
this._snackBarTimer?.cancel(); |
|
|
|
this._snackBarTimer = null; |
|
|
|
this._snackBarController.setValue(0.0f); |
|
|
|
} |
|
|
|
|
|
|
|
public void hideCurrentSnackBar(SnackBarClosedReason reason = SnackBarClosedReason.hide) { |
|
|
|
if (this._snackBars.isEmpty() || this._snackBarController.status == AnimationStatus.dismissed) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
MediaQueryData mediaQuery = MediaQuery.of(this.context); |
|
|
|
Promise<SnackBarClosedReason> completer = this._snackBars.First()._completer; |
|
|
|
if (mediaQuery.accessibleNavigation) { |
|
|
|
this._snackBarController.setValue(0.0f); |
|
|
|
completer.Resolve(reason); |
|
|
|
} |
|
|
|
else { |
|
|
|
this._snackBarController.reverse().Then(() => { |
|
|
|
D.assert(this.mounted); |
|
|
|
if (!completer.isCompleted) { |
|
|
|
completer.Resolve(reason); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
this._snackBarTimer?.cancel(); |
|
|
|
this._snackBarTimer = null; |
|
|
|
} |
|
|
|
|
|
|
|
readonly List<_PersistentBottomSheet> _dismissedBottomSheets = new List<_PersistentBottomSheet>(); |
|
|
|
PersistentBottomSheetController<object> _currentBottomSheet; |
|
|
|
|
|
|
|
void _maybeBuildCurrentBottomSheet() { |
|
|
|
if (this.widget.bottomSheet != null) { |
|
|
|
AnimationController controller = BottomSheet.createAnimationController(this); |
|
|
|
controller.setValue(1.0f); |
|
|
|
this._currentBottomSheet = this._buildBottomSheet<object>( |
|
|
|
(BuildContext context) => this.widget.bottomSheet, |
|
|
|
controller, |
|
|
|
false); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void _closeCurrentBottomSheet() { |
|
|
|
if (this._currentBottomSheet != null) { |
|
|
|
this._currentBottomSheet.close(); |
|
|
|
D.assert(this._currentBottomSheet == null); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
PersistentBottomSheetController<T> _buildBottomSheet<T>(WidgetBuilder builder, AnimationController controller, |
|
|
|
bool isLocalHistoryEntry) { |
|
|
|
Promise<T> completer = new Promise<T>(); |
|
|
|
GlobalKey<_PersistentBottomSheetState> bottomSheetKey = GlobalKey<_PersistentBottomSheetState>.key(); |
|
|
|
_PersistentBottomSheet bottomSheet = null; |
|
|
|
|
|
|
|
void _removeCurrentBottomSheet() { |
|
|
|
D.assert(this._currentBottomSheet._widget == bottomSheet); |
|
|
|
D.assert(bottomSheetKey.currentState != null); |
|
|
|
bottomSheetKey.currentState.close(); |
|
|
|
if (controller.status != AnimationStatus.dismissed) { |
|
|
|
this._dismissedBottomSheets.Add(bottomSheet); |
|
|
|
} |
|
|
|
|
|
|
|
this.setState(() => { this._currentBottomSheet = null; }); |
|
|
|
completer.Done(); |
|
|
|
} |
|
|
|
|
|
|
|
LocalHistoryEntry entry = isLocalHistoryEntry |
|
|
|
? new LocalHistoryEntry(onRemove: _removeCurrentBottomSheet) |
|
|
|
: null; |
|
|
|
|
|
|
|
bottomSheet = new _PersistentBottomSheet( |
|
|
|
key: bottomSheetKey, |
|
|
|
animationController: controller, |
|
|
|
enableDrag: isLocalHistoryEntry, |
|
|
|
onClosing: () => { |
|
|
|
D.assert(this._currentBottomSheet._widget == bottomSheet); |
|
|
|
if (isLocalHistoryEntry) { |
|
|
|
entry.remove(); |
|
|
|
} |
|
|
|
else { |
|
|
|
_removeCurrentBottomSheet(); |
|
|
|
} |
|
|
|
}, |
|
|
|
onDismissed: () => { |
|
|
|
if (this._dismissedBottomSheets.Contains(bottomSheet)) { |
|
|
|
bottomSheet.animationController.dispose(); |
|
|
|
this.setState(() => { this._dismissedBottomSheets.Remove(bottomSheet); }); |
|
|
|
} |
|
|
|
}, |
|
|
|
builder: builder); |
|
|
|
|
|
|
|
if (isLocalHistoryEntry) { |
|
|
|
ModalRoute.of(this.context).addLocalHistoryEntry(entry); |
|
|
|
} |
|
|
|
|
|
|
|
return new PersistentBottomSheetController<T>( |
|
|
|
bottomSheet, |
|
|
|
completer, |
|
|
|
isLocalHistoryEntry ? (VoidCallback) entry.remove : _removeCurrentBottomSheet, |
|
|
|
(VoidCallback fn) => { bottomSheetKey.currentState?.setState(fn); }, |
|
|
|
isLocalHistoryEntry); |
|
|
|
} |
|
|
|
|
|
|
|
PersistentBottomSheetController<object> showBottomSheet(WidgetBuilder builder) { |
|
|
|
this._closeCurrentBottomSheet(); |
|
|
|
AnimationController controller = BottomSheet.createAnimationController(this); |
|
|
|
controller.forward(); |
|
|
|
this.setState(() => { |
|
|
|
this._currentBottomSheet = this._buildBottomSheet<object>(builder, controller, true); |
|
|
|
}); |
|
|
|
return this._currentBottomSheet; |
|
|
|
} |
|
|
|
|
|
|
|
AnimationController _floatingActionButtonMoveController; |
|
|
|
FloatingActionButtonAnimator _floatingActionButtonAnimator; |
|
|
|
FloatingActionButtonLocation _previousFloatingActionButtonLocation; |
|
|
|
FloatingActionButtonLocation _floatingActionButtonLocation; |
|
|
|
|
|
|
|
void _moveFloatingActionButton(FloatingActionButtonLocation newLocation) { |
|
|
|
FloatingActionButtonLocation previousLocation = this._floatingActionButtonLocation; |
|
|
|
float restartAnimationFrom = 0.0f; |
|
|
|
if (this._floatingActionButtonMoveController.isAnimating) { |
|
|
|
previousLocation = new _TransitionSnapshotFabLocation(this._previousFloatingActionButtonLocation, |
|
|
|
this._floatingActionButtonLocation, |
|
|
|
this._floatingActionButtonAnimator, |
|
|
|
this._floatingActionButtonMoveController.value); |
|
|
|
restartAnimationFrom = |
|
|
|
this._floatingActionButtonAnimator.getAnimationRestart(this._floatingActionButtonMoveController |
|
|
|
.value); |
|
|
|
} |
|
|
|
|
|
|
|
this.setState(() => { |
|
|
|
this._previousFloatingActionButtonLocation = previousLocation; |
|
|
|
this._floatingActionButtonLocation = newLocation; |
|
|
|
}); |
|
|
|
|
|
|
|
this._floatingActionButtonMoveController.forward(from: restartAnimationFrom); |
|
|
|
} |
|
|
|
|
|
|
|
ScrollController _primaryScrollController = new ScrollController(); |
|
|
|
|
|
|
|
void _handleStatusBarTap() { |
|
|
|
if (this._primaryScrollController.hasClients) { |
|
|
|
this._primaryScrollController.animateTo( |
|
|
|
to: 0.0f, |
|
|
|
duration: new TimeSpan(0, 0, 0, 0, 300), |
|
|
|
curve: Curves.linear); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
_ScaffoldGeometryNotifier _geometryNotifier; |
|
|
|
|
|
|
|
|
|
|
|
public override void initState() { |
|
|
|
base.initState(); |
|
|
|
this._geometryNotifier = new _ScaffoldGeometryNotifier(new ScaffoldGeometry(), this.context); |
|
|
|
this._floatingActionButtonLocation = this.widget.floatingActionButtonLocation ?? |
|
|
|
ScaffoldUtils._kDefaultFloatingActionButtonLocation; |
|
|
|
this._floatingActionButtonAnimator = this.widget.floatingActionButtonAnimator ?? |
|
|
|
ScaffoldUtils._kDefaultFloatingActionButtonAnimator; |
|
|
|
this._previousFloatingActionButtonLocation = this._floatingActionButtonLocation; |
|
|
|
this._floatingActionButtonMoveController = new AnimationController( |
|
|
|
vsync: this, |
|
|
|
lowerBound: 0.0f, |
|
|
|
upperBound: 1.0f, |
|
|
|
value: 1.0f, |
|
|
|
duration: FloatingActionButtonLocationUtils.kFloatingActionButtonSegue + |
|
|
|
FloatingActionButtonLocationUtils.kFloatingActionButtonSegue |
|
|
|
); |
|
|
|
|
|
|
|
this._maybeBuildCurrentBottomSheet(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public override void didUpdateWidget(StatefulWidget oldWidget) { |
|
|
|
Scaffold _oldWidget = (Scaffold) oldWidget; |
|
|
|
if (this.widget.floatingActionButtonAnimator != _oldWidget.floatingActionButtonAnimator) { |
|
|
|
this._floatingActionButtonAnimator = this.widget.floatingActionButtonAnimator ?? |
|
|
|
ScaffoldUtils._kDefaultFloatingActionButtonAnimator; |
|
|
|
} |
|
|
|
|
|
|
|
if (this.widget.floatingActionButtonLocation != _oldWidget.floatingActionButtonLocation) { |
|
|
|
this._moveFloatingActionButton(this.widget.floatingActionButtonLocation ?? |
|
|
|
ScaffoldUtils._kDefaultFloatingActionButtonLocation); |
|
|
|
} |
|
|
|
|
|
|
|
if (this.widget.bottomSheet != _oldWidget.bottomSheet) { |
|
|
|
D.assert(() => { |
|
|
|
if (this.widget.bottomSheet != null && this._currentBottomSheet?._isLocalHistoryEntry == true) { |
|
|
|
throw new UIWidgetsError( |
|
|
|
"Scaffold.bottomSheet cannot be specified while a bottom sheet displayed " + |
|
|
|
"with showBottomSheet() is still visible.\n Use the PersistentBottomSheetController " + |
|
|
|
"returned by showBottomSheet() to close the old bottom sheet before creating " + |
|
|
|
"a Scaffold with a (non null) bottomSheet."); |
|
|
|
} |
|
|
|
|
|
|
|
return true; |
|
|
|
}); |
|
|
|
this._closeCurrentBottomSheet(); |
|
|
|
this._maybeBuildCurrentBottomSheet(); |
|
|
|
} |
|
|
|
|
|
|
|
base.didUpdateWidget(oldWidget); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public override void didChangeDependencies() { |
|
|
|
MediaQueryData mediaQuery = MediaQuery.of(this.context); |
|
|
|
|
|
|
|
if (this._accessibleNavigation |
|
|
|
&& !mediaQuery.accessibleNavigation |
|
|
|
&& this._snackBarTimer != null) { |
|
|
|
this.hideCurrentSnackBar(reason: SnackBarClosedReason.timeout); |
|
|
|
} |
|
|
|
|
|
|
|
this._accessibleNavigation = mediaQuery.accessibleNavigation; |
|
|
|
base.didChangeDependencies(); |
|
|
|
} |
|
|
|
|
|
|
|
public override void dispose() { |
|
|
|
this._snackBarController?.dispose(); |
|
|
|
this._snackBarTimer?.cancel(); |
|
|
|
this._snackBarTimer = null; |
|
|
|
this._geometryNotifier.dispose(); |
|
|
|
foreach (_PersistentBottomSheet bottomSheet in this._dismissedBottomSheets) { |
|
|
|
bottomSheet.animationController.dispose(); |
|
|
|
} |
|
|
|
|
|
|
|
if (this._currentBottomSheet != null) { |
|
|
|
this._currentBottomSheet._widget.animationController.dispose(); |
|
|
|
} |
|
|
|
|
|
|
|
this._floatingActionButtonMoveController.dispose(); |
|
|
|
base.dispose(); |
|
|
|
} |
|
|
|
|
|
|
|
void _addIfNonNull(List<LayoutId> children, Widget child, object childId, |
|
|
|
bool removeLeftPadding, |
|
|
|
bool removeTopPadding, |
|
|
|
bool removeRightPadding, |
|
|
|
bool removeBottomPadding |
|
|
|
) { |
|
|
|
if (child != null) { |
|
|
|
children.Add( |
|
|
|
new LayoutId( |
|
|
|
id: childId, |
|
|
|
child: MediaQuery.removePadding( |
|
|
|
context: this.context, |
|
|
|
removeLeft: removeLeftPadding, |
|
|
|
removeTop: removeTopPadding, |
|
|
|
removeRight: removeRightPadding, |
|
|
|
removeBottom: removeBottomPadding, |
|
|
|
child: child |
|
|
|
) |
|
|
|
) |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void _buildEndDrawer(List<LayoutId> children) { |
|
|
|
if (this.widget.endDrawer != null) { |
|
|
|
D.assert(this.hasEndDrawer); |
|
|
|
this._addIfNonNull( |
|
|
|
children: children, |
|
|
|
new DrawerController( |
|
|
|
key: this._endDrawerKey, |
|
|
|
alignment: DrawerAlignment.end, |
|
|
|
child: this.widget.endDrawer, |
|
|
|
drawerCallback: this._endDrawerOpenedCallback |
|
|
|
), |
|
|
|
childId: _ScaffoldSlot.endDrawer, |
|
|
|
removeLeftPadding: true, |
|
|
|
removeTopPadding: false, |
|
|
|
removeRightPadding: false, |
|
|
|
removeBottomPadding: false |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void _buildDrawer(List<LayoutId> children) { |
|
|
|
if (this.widget.drawer != null) { |
|
|
|
D.assert(this.hasDrawer); |
|
|
|
this._addIfNonNull( |
|
|
|
children: children, |
|
|
|
new DrawerController( |
|
|
|
key: this._drawerKey, |
|
|
|
alignment: DrawerAlignment.start, |
|
|
|
child: this.widget.drawer, |
|
|
|
drawerCallback: this._drawerOpenedCallback |
|
|
|
), |
|
|
|
childId: _ScaffoldSlot.drawer, |
|
|
|
removeLeftPadding: false, |
|
|
|
removeTopPadding: false, |
|
|
|
removeRightPadding: true, |
|
|
|
removeBottomPadding: false |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public override Widget build(BuildContext context) { |
|
|
|
MediaQueryData mediaQuery = MediaQuery.of(context); |
|
|
|
ThemeData themeData = Theme.of(context); |
|
|
|
|
|
|
|
this._accessibleNavigation = mediaQuery.accessibleNavigation; |
|
|
|
|
|
|
|
if (this._snackBars.isNotEmpty()) { |
|
|
|
ModalRoute route = ModalRoute.of(context); |
|
|
|
if (route == null || route.isCurrent) { |
|
|
|
if (this._snackBarController.isCompleted && this._snackBarTimer == null) { |
|
|
|
SnackBar snackBar = this._snackBars.First()._widget; |
|
|
|
this._snackBarTimer = Window.instance.run(snackBar.duration, () => { |
|
|
|
D.assert(this._snackBarController.status == AnimationStatus.forward || |
|
|
|
this._snackBarController.status == AnimationStatus.completed); |
|
|
|
MediaQueryData subMediaQuery = MediaQuery.of(context); |
|
|
|
if (subMediaQuery.accessibleNavigation && snackBar.action != null) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
this.hideCurrentSnackBar(reason: SnackBarClosedReason.timeout); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
this._snackBarTimer?.cancel(); |
|
|
|
this._snackBarTimer = null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
List<LayoutId> children = new List<LayoutId>(); |
|
|
|
|
|
|
|
this._addIfNonNull( |
|
|
|
children: children, |
|
|
|
child: this.widget.body, |
|
|
|
childId: _ScaffoldSlot.body, |
|
|
|
removeLeftPadding: false, |
|
|
|
removeTopPadding: this.widget.appBar != null, |
|
|
|
removeRightPadding: false, |
|
|
|
removeBottomPadding: this.widget.bottomNavigationBar != null || |
|
|
|
this.widget.persistentFooterButtons != null |
|
|
|
); |
|
|
|
|
|
|
|
if (this.widget.appBar != null) { |
|
|
|
float topPadding = this.widget.primary ? mediaQuery.padding.top : 0.0f; |
|
|
|
float extent = this.widget.appBar.preferredSize.height + topPadding; |
|
|
|
D.assert(extent >= 0.0f && extent.isFinite()); |
|
|
|
this._addIfNonNull( |
|
|
|
children: children, |
|
|
|
new ConstrainedBox( |
|
|
|
constraints: new BoxConstraints(maxHeight: extent), |
|
|
|
child: FlexibleSpaceBar.createSettings( |
|
|
|
currentExtent: extent, |
|
|
|
child: this.widget.appBar |
|
|
|
) |
|
|
|
), |
|
|
|
childId: _ScaffoldSlot.appBar, |
|
|
|
removeLeftPadding: false, |
|
|
|
removeTopPadding: false, |
|
|
|
removeRightPadding: false, |
|
|
|
removeBottomPadding: true |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
if (this._snackBars.isNotEmpty()) { |
|
|
|
bool removeBottomPadding = this.widget.persistentFooterButtons != null || |
|
|
|
this.widget.bottomNavigationBar != null; |
|
|
|
this._addIfNonNull( |
|
|
|
children: children, |
|
|
|
child: this._snackBars.First()._widget, |
|
|
|
childId: _ScaffoldSlot.snackBar, |
|
|
|
removeLeftPadding: false, |
|
|
|
removeTopPadding: true, |
|
|
|
removeRightPadding: false, |
|
|
|
removeBottomPadding: removeBottomPadding |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
if (this.widget.persistentFooterButtons != null) { |
|
|
|
this._addIfNonNull( |
|
|
|
children: children, |
|
|
|
new Container( |
|
|
|
decoration: new BoxDecoration( |
|
|
|
border: new Border( |
|
|
|
top: Divider.createBorderSide(context, width: 1.0f) |
|
|
|
) |
|
|
|
), |
|
|
|
child: new SafeArea( |
|
|
|
child: ButtonTheme.bar( |
|
|
|
child: new SafeArea( |
|
|
|
top: false, |
|
|
|
child: new ButtonBar( |
|
|
|
children: this.widget.persistentFooterButtons |
|
|
|
) |
|
|
|
) |
|
|
|
) |
|
|
|
) |
|
|
|
), |
|
|
|
childId: _ScaffoldSlot.persistentFooter, |
|
|
|
removeLeftPadding: false, |
|
|
|
removeTopPadding: true, |
|
|
|
removeRightPadding: false, |
|
|
|
removeBottomPadding: false |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
if (this.widget.bottomNavigationBar != null) { |
|
|
|
this._addIfNonNull( |
|
|
|
children: children, |
|
|
|
child: this.widget.bottomNavigationBar, |
|
|
|
childId: _ScaffoldSlot.bottomNavigationBar, |
|
|
|
removeLeftPadding: false, |
|
|
|
removeTopPadding: true, |
|
|
|
removeRightPadding: false, |
|
|
|
removeBottomPadding: false |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
if (this._currentBottomSheet != null || this._dismissedBottomSheets.isNotEmpty()) { |
|
|
|
List<Widget> bottomSheets = new List<Widget>(); |
|
|
|
if (this._dismissedBottomSheets.isNotEmpty()) { |
|
|
|
bottomSheets.AddRange(this._dismissedBottomSheets); |
|
|
|
} |
|
|
|
|
|
|
|
if (this._currentBottomSheet != null) { |
|
|
|
bottomSheets.Add(this._currentBottomSheet._widget); |
|
|
|
} |
|
|
|
|
|
|
|
Widget stack = new Stack( |
|
|
|
children: bottomSheets, |
|
|
|
alignment: Alignment.bottomCenter |
|
|
|
); |
|
|
|
this._addIfNonNull( |
|
|
|
children: children, |
|
|
|
child: stack, |
|
|
|
childId: _ScaffoldSlot.bottomSheet, |
|
|
|
removeLeftPadding: false, |
|
|
|
removeTopPadding: true, |
|
|
|
removeRightPadding: false, |
|
|
|
removeBottomPadding: this.widget.resizeToAvoidBottomPadding |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
this._addIfNonNull( |
|
|
|
children: children, |
|
|
|
new _FloatingActionButtonTransition( |
|
|
|
child: this.widget.floatingActionButton, |
|
|
|
fabMoveAnimation: this._floatingActionButtonMoveController, |
|
|
|
fabMotionAnimator: this._floatingActionButtonAnimator, |
|
|
|
geometryNotifier: this._geometryNotifier |
|
|
|
), |
|
|
|
childId: _ScaffoldSlot.floatingActionButton, |
|
|
|
removeLeftPadding: true, |
|
|
|
removeTopPadding: true, |
|
|
|
removeRightPadding: true, |
|
|
|
removeBottomPadding: true |
|
|
|
); |
|
|
|
|
|
|
|
if (themeData.platform == RuntimePlatform.IPhonePlayer) { |
|
|
|
this._addIfNonNull( |
|
|
|
children: children, |
|
|
|
new GestureDetector( |
|
|
|
behavior: HitTestBehavior.opaque, |
|
|
|
onTap: this._handleStatusBarTap |
|
|
|
), |
|
|
|
childId: _ScaffoldSlot.statusBar, |
|
|
|
removeLeftPadding: false, |
|
|
|
removeTopPadding: true, |
|
|
|
removeRightPadding: false, |
|
|
|
removeBottomPadding: true |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
if (this._endDrawerOpened) { |
|
|
|
this._buildDrawer(children); |
|
|
|
this._buildEndDrawer(children); |
|
|
|
} |
|
|
|
else { |
|
|
|
this._buildEndDrawer(children); |
|
|
|
this._buildDrawer(children); |
|
|
|
} |
|
|
|
|
|
|
|
EdgeInsets minInsets = mediaQuery.padding.copyWith( |
|
|
|
bottom: this.widget.resizeToAvoidBottomPadding ? mediaQuery.viewInsets.bottom : 0.0f |
|
|
|
); |
|
|
|
|
|
|
|
return new _ScaffoldScope( |
|
|
|
hasDrawer: this.hasDrawer, |
|
|
|
geometryNotifier: this._geometryNotifier, |
|
|
|
child: new PrimaryScrollController( |
|
|
|
controller: this._primaryScrollController, |
|
|
|
child: new Material( |
|
|
|
color: this.widget.backgroundColor ?? themeData.scaffoldBackgroundColor, |
|
|
|
child: new AnimatedBuilder(animation: this._floatingActionButtonMoveController, |
|
|
|
builder: (BuildContext subContext, Widget child) => { |
|
|
|
return new CustomMultiChildLayout( |
|
|
|
children: new List<Widget>(children), |
|
|
|
layoutDelegate: new _ScaffoldLayout( |
|
|
|
minInsets: minInsets, |
|
|
|
currentFloatingActionButtonLocation: this._floatingActionButtonLocation, |
|
|
|
floatingActionButtonMoveAnimationProgress: this |
|
|
|
._floatingActionButtonMoveController.value, |
|
|
|
floatingActionButtonMotionAnimator: this._floatingActionButtonAnimator, |
|
|
|
geometryNotifier: this._geometryNotifier, |
|
|
|
previousFloatingActionButtonLocation: this._previousFloatingActionButtonLocation |
|
|
|
) |
|
|
|
); |
|
|
|
} |
|
|
|
) |
|
|
|
) |
|
|
|
) |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
class ScaffoldFeatureController<T, U> where T : Widget { |
|
|
|
public ScaffoldFeatureController( |
|
|
|
T _widget, |
|
|
|
Promise<U> _completer, |
|
|
|
VoidCallback close, |
|
|
|
StateSetter setState) { |
|
|
|
this._widget = _widget; |
|
|
|
this._completer = _completer; |
|
|
|
this.close = close; |
|
|
|
this.setState = setState; |
|
|
|
} |
|
|
|
|
|
|
|
public readonly T _widget; |
|
|
|
|
|
|
|
public readonly Promise<U> _completer; |
|
|
|
|
|
|
|
IPromise<U> closed { |
|
|
|
get { return this._completer; } |
|
|
|
} |
|
|
|
|
|
|
|
public readonly VoidCallback close; |
|
|
|
|
|
|
|
public readonly StateSetter setState; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class _PersistentBottomSheet : StatefulWidget { |
|
|
|
public _PersistentBottomSheet( |
|
|
|
Key key = null, |
|
|
|
AnimationController animationController = null, |
|
|
|
bool enableDrag = true, |
|
|
|
VoidCallback onClosing = null, |
|
|
|
VoidCallback onDismissed = null, |
|
|
|
WidgetBuilder builder = null |
|
|
|
) : base(key: key) { |
|
|
|
this.animationController = animationController; |
|
|
|
this.enableDrag = enableDrag; |
|
|
|
this.onClosing = onClosing; |
|
|
|
this.onDismissed = onDismissed; |
|
|
|
this.builder = builder; |
|
|
|
} |
|
|
|
|
|
|
|
public readonly AnimationController animationController; |
|
|
|
public readonly bool enableDrag; |
|
|
|
public readonly VoidCallback onClosing; |
|
|
|
public readonly VoidCallback onDismissed; |
|
|
|
public readonly WidgetBuilder builder; |
|
|
|
|
|
|
|
public override State createState() { |
|
|
|
return new _PersistentBottomSheetState(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _PersistentBottomSheetState : State<_PersistentBottomSheet> { |
|
|
|
public override void initState() { |
|
|
|
base.initState(); |
|
|
|
D.assert(this.widget.animationController.status == AnimationStatus.forward |
|
|
|
|| this.widget.animationController.status == AnimationStatus.completed); |
|
|
|
this.widget.animationController.addStatusListener(this._handleStatusChange); |
|
|
|
} |
|
|
|
|
|
|
|
public override void didUpdateWidget(StatefulWidget oldWidget) { |
|
|
|
base.didUpdateWidget(oldWidget); |
|
|
|
_PersistentBottomSheet _oldWidget = (_PersistentBottomSheet) oldWidget; |
|
|
|
D.assert(this.widget.animationController == _oldWidget.animationController); |
|
|
|
} |
|
|
|
|
|
|
|
public void close() { |
|
|
|
this.widget.animationController.reverse(); |
|
|
|
} |
|
|
|
|
|
|
|
void _handleStatusChange(AnimationStatus status) { |
|
|
|
if (status == AnimationStatus.dismissed && this.widget.onDismissed != null) { |
|
|
|
this.widget.onDismissed(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public override Widget build(BuildContext context) { |
|
|
|
return new AnimatedBuilder( |
|
|
|
animation: this.widget.animationController, |
|
|
|
builder: (BuildContext subContext, Widget child) => { |
|
|
|
return new Align( |
|
|
|
alignment: Alignment.topLeft, |
|
|
|
heightFactor: this.widget.animationController.value, |
|
|
|
child: child); |
|
|
|
}, |
|
|
|
child: new BottomSheet( |
|
|
|
animationController: this.widget.animationController, |
|
|
|
enableDrag: this.widget.enableDrag, |
|
|
|
onClosing: this.widget.onClosing, |
|
|
|
builder: this.widget.builder)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class PersistentBottomSheetController<T> : ScaffoldFeatureController<_PersistentBottomSheet, T> { |
|
|
|
public PersistentBottomSheetController( |
|
|
|
_PersistentBottomSheet widget, |
|
|
|
Promise<T> completer, |
|
|
|
VoidCallback close, |
|
|
|
StateSetter setState, |
|
|
|
bool _isLocalHistoryEntry |
|
|
|
) : base(widget, completer, close, setState) { |
|
|
|
this._isLocalHistoryEntry = _isLocalHistoryEntry; |
|
|
|
} |
|
|
|
|
|
|
|
public readonly bool _isLocalHistoryEntry; |
|
|
|
} |
|
|
|
|
|
|
|
class _ScaffoldScope : InheritedWidget { |
|
|
|
public _ScaffoldScope( |
|
|
|
bool? hasDrawer = null, |
|
|
|
|
|
|
_ScaffoldScope _oldWidget = (_ScaffoldScope) oldWidget; |
|
|
|
return this.hasDrawer != _oldWidget.hasDrawer; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |