|
|
|
|
|
|
using System; |
|
|
|
using uiwidgets; |
|
|
|
using Unity.UIWidget.material; |
|
|
|
using Unity.UIWidgets.painting; |
|
|
|
using UnityEngine; |
|
|
|
using Color = Unity.UIWidgets.ui.Color; |
|
|
|
public static readonly TimeSpan _kBottomSheetDuration = new TimeSpan(0, 0, 0, 0, 200); |
|
|
|
public const float _kMinFlingVelocity = 700.0f; |
|
|
|
public const float _kCloseProgressThreshold = 0.5f; |
|
|
|
|
|
|
|
public static readonly TimeSpan _bottomSheetEnterDuration = new TimeSpan(0, 0, 0, 0, 250); |
|
|
|
public static readonly TimeSpan _bottomSheetExitDuration = new TimeSpan(0, 0, 0, 0, 200); |
|
|
|
public static readonly TimeSpan _bottomSheetEnterDuration = new TimeSpan(0, 0, 0, 0, 250); |
|
|
|
public static readonly TimeSpan _bottomSheetExitDuration = new TimeSpan(0, 0, 0, 0, 200); |
|
|
|
public const double _minFlingVelocity = 700.0; |
|
|
|
public const double _closeProgressThreshold = 0.5; |
|
|
|
public const float _minFlingVelocity = 700.0f; |
|
|
|
public const float _closeProgressThreshold = 0.5f; |
|
|
|
|
|
|
|
public delegate void BottomSheetDragStartHandler(DragStartDetails details); |
|
|
|
delegate void BottomSheetDragStartHandler(DragStartDetails details); |
|
|
|
delegate void BottomSheetDragEndHandler( |
|
|
|
public delegate void BottomSheetDragEndHandler( |
|
|
|
bool isClosing = false |
|
|
|
bool? isClosing = false |
|
|
|
|
|
|
|
|
|
|
|
WidgetBuilder builder |
|
|
|
WidgetBuilder builder, |
|
|
|
Color backgroundColor, |
|
|
|
float? elevation = null, |
|
|
|
ShapeBorder shape = null, |
|
|
|
Clip? clipBehavior = null, |
|
|
|
Color barrierColor = null, |
|
|
|
bool isScrollControlled = false, |
|
|
|
bool useRootNavigator = false, |
|
|
|
bool isDismissible = true, |
|
|
|
bool enableDrag = true |
|
|
|
D.assert(WidgetsD.debugCheckHasMediaQuery(context)); |
|
|
|
return Navigator.of(context, rootNavigator: false).push<T>(new _ModalBottomSheetRoute<T>( |
|
|
|
return Navigator.of(context, rootNavigator: useRootNavigator).push(new _ModalBottomSheetRoute<T>( |
|
|
|
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel |
|
|
|
)); |
|
|
|
isScrollControlled: isScrollControlled, |
|
|
|
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, |
|
|
|
backgroundColor: backgroundColor, |
|
|
|
elevation: elevation, |
|
|
|
shape: shape, |
|
|
|
clipBehavior: clipBehavior, |
|
|
|
isDismissible: isDismissible, |
|
|
|
modalBarrierColor: barrierColor, |
|
|
|
enableDrag: enableDrag |
|
|
|
)).to<T>(); |
|
|
|
WidgetBuilder builder |
|
|
|
WidgetBuilder builder, |
|
|
|
Color backgroundColor = null, |
|
|
|
float? elevation = null, |
|
|
|
ShapeBorder shape = null, |
|
|
|
Clip? clipBehavior = null |
|
|
|
return Scaffold.of(context).showBottomSheet(builder); |
|
|
|
D.assert(debugCheckHasScaffold(context)); |
|
|
|
return Scaffold.of(context).showBottomSheet( |
|
|
|
builder |
|
|
|
//TODO: update showBottomSheet
|
|
|
|
// ,
|
|
|
|
// backgroundColor: backgroundColor,
|
|
|
|
// elevation: elevation,
|
|
|
|
// shape: shape,
|
|
|
|
// clipBehavior: clipBehavior
|
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
Key key = null, |
|
|
|
AnimationController animationController = null, |
|
|
|
bool enableDrag = true, |
|
|
|
float elevation = 0.0f, |
|
|
|
material_.BottomSheetDragStartHandler onDragStart = null, |
|
|
|
material_.BottomSheetDragEndHandler onDragEnd = null, |
|
|
|
Color backgroundColor = null, |
|
|
|
float? elevation = null, |
|
|
|
ShapeBorder shape = null, |
|
|
|
Clip? clipBehavior = null, |
|
|
|
D.assert(elevation == null || elevation >= 0.0); |
|
|
|
|
|
|
|
D.assert(elevation >= 0.0f); |
|
|
|
this.animationController = animationController; |
|
|
|
this.enableDrag = enableDrag; |
|
|
|
|
|
|
|
|
|
|
public readonly bool enableDrag; |
|
|
|
|
|
|
|
public readonly float elevation; |
|
|
|
public readonly material_.BottomSheetDragStartHandler onDragStart; |
|
|
|
|
|
|
|
public readonly material_.BottomSheetDragEndHandler onDragEnd; |
|
|
|
|
|
|
|
public readonly Color backgroundColor; |
|
|
|
|
|
|
|
public readonly float? elevation; |
|
|
|
|
|
|
|
public readonly ShapeBorder shape; |
|
|
|
|
|
|
|
public readonly Clip? clipBehavior; |
|
|
|
|
|
|
|
public override State createState() { |
|
|
|
return new _BottomSheetState(); |
|
|
|
|
|
|
return new AnimationController( |
|
|
|
duration: material_._kBottomSheetDuration, |
|
|
|
duration: material_._bottomSheetEnterDuration, |
|
|
|
reverseDuration: material_._bottomSheetExitDuration, |
|
|
|
debugLabel: "BottomSheet", |
|
|
|
vsync: vsync |
|
|
|
); |
|
|
|
|
|
|
get { return widget.animationController.status == AnimationStatus.reverse; } |
|
|
|
} |
|
|
|
|
|
|
|
void _handleDragStart(DragStartDetails details) { |
|
|
|
if (widget.onDragStart != null) { |
|
|
|
widget.onDragStart(details); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
D.assert(widget.enableDrag); |
|
|
|
if (_dismissUnderway) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
void _handleDragEnd(DragEndDetails details) { |
|
|
|
D.assert(widget.enableDrag); |
|
|
|
if (details.velocity.pixelsPerSecond.dy > material_._kMinFlingVelocity) { |
|
|
|
float flingVelocity = -details.velocity.pixelsPerSecond.dy / _childHeight.Value; |
|
|
|
if (widget.animationController.value > 0.0f) { |
|
|
|
widget.animationController.fling(velocity: flingVelocity); |
|
|
|
bool isClosing = false; |
|
|
|
if (details.velocity.pixelsPerSecond.dy > material_._minFlingVelocity) { |
|
|
|
float? flingVelocity = -details.velocity.pixelsPerSecond.dy / _childHeight; |
|
|
|
if (widget.animationController.value > 0.0) { |
|
|
|
widget.animationController.fling(velocity: flingVelocity ?? 0); |
|
|
|
if (flingVelocity < 0.0f) { |
|
|
|
widget.onClosing(); |
|
|
|
if (flingVelocity < 0.0) { |
|
|
|
isClosing = true; |
|
|
|
else if (widget.animationController.value < material_._kCloseProgressThreshold) { |
|
|
|
if (widget.animationController.value > 0.0f) { |
|
|
|
else if (widget.animationController.value < material_._closeProgressThreshold) { |
|
|
|
if (widget.animationController.value > 0.0) |
|
|
|
} |
|
|
|
isClosing = true; |
|
|
|
} |
|
|
|
else { |
|
|
|
widget.animationController.forward(); |
|
|
|
} |
|
|
|
if (widget.onDragEnd != null) { |
|
|
|
widget.onDragEnd( |
|
|
|
details, |
|
|
|
isClosing: isClosing |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
if (isClosing) { |
|
|
|
else { |
|
|
|
widget.animationController.forward(); |
|
|
|
} |
|
|
|
|
|
|
|
public bool extentChanged(DraggableScrollableNotification notification) { |
|
|
|
if (notification.extent == notification.minExtent) { |
|
|
|
widget.onClosing(); |
|
|
|
|
|
|
|
return false; |
|
|
|
BottomSheetThemeData bottomSheetTheme = Theme.of(context).bottomSheetTheme; |
|
|
|
Color color = widget.backgroundColor ?? bottomSheetTheme.backgroundColor; |
|
|
|
float elevation = widget.elevation ?? bottomSheetTheme.elevation ?? 0; |
|
|
|
ShapeBorder shape = widget.shape ?? bottomSheetTheme.shape; |
|
|
|
Clip clipBehavior = widget.clipBehavior ?? bottomSheetTheme.clipBehavior ?? Clip.none; |
|
|
|
|
|
|
|
elevation: widget.elevation, |
|
|
|
child: widget.builder(context) |
|
|
|
color: color, |
|
|
|
elevation: elevation, |
|
|
|
shape: shape, |
|
|
|
clipBehavior: clipBehavior, |
|
|
|
child: new NotificationListener<DraggableScrollableNotification>( |
|
|
|
onNotification: extentChanged, |
|
|
|
child: widget.builder(context) |
|
|
|
) |
|
|
|
onVerticalDragStart: _handleDragStart, |
|
|
|
onVerticalDragUpdate: _handleDragUpdate, |
|
|
|
onVerticalDragEnd: _handleDragEnd, |
|
|
|
child: bottomSheet |
|
|
|
|
|
|
|
|
|
|
class _ModalBottomSheetLayout : SingleChildLayoutDelegate { |
|
|
|
public _ModalBottomSheetLayout(float progress) { |
|
|
|
public _ModalBottomSheetLayout(float progress, bool isScrollControlled) { |
|
|
|
this.isScrollControlled = isScrollControlled; |
|
|
|
public readonly bool isScrollControlled; |
|
|
|
|
|
|
|
public override BoxConstraints getConstraintsForChild(BoxConstraints constraints) { |
|
|
|
return new BoxConstraints( |
|
|
|
|
|
|
maxHeight: constraints.maxHeight * 9.0f / 16.0f |
|
|
|
maxHeight: isScrollControlled |
|
|
|
? constraints.maxHeight |
|
|
|
: constraints.maxHeight * 9.0f / 16.0f |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
class _ModalBottomSheet<T> : StatefulWidget { |
|
|
|
public _ModalBottomSheet(Key key = null, _ModalBottomSheetRoute<T> route = null) : base(key: key) { |
|
|
|
public _ModalBottomSheet( |
|
|
|
Key key = null, |
|
|
|
_ModalBottomSheetRoute<T> route = null, |
|
|
|
Color backgroundColor = null, |
|
|
|
float? elevation = null, |
|
|
|
ShapeBorder shape = null, |
|
|
|
Clip? clipBehavior = null, |
|
|
|
bool isScrollControlled = false, |
|
|
|
bool enableDrag = true |
|
|
|
) : base(key: key) { |
|
|
|
this.backgroundColor = backgroundColor; |
|
|
|
this.elevation = elevation; |
|
|
|
this.shape = shape; |
|
|
|
this.clipBehavior = clipBehavior; |
|
|
|
this.isScrollControlled = isScrollControlled; |
|
|
|
this.enableDrag = enableDrag; |
|
|
|
public readonly bool isScrollControlled; |
|
|
|
public readonly Color backgroundColor; |
|
|
|
public readonly float? elevation; |
|
|
|
public readonly ShapeBorder shape; |
|
|
|
public readonly Clip? clipBehavior; |
|
|
|
public readonly bool enableDrag; |
|
|
|
|
|
|
|
public override State createState() { |
|
|
|
return new _ModalBottomSheetState<T>(); |
|
|
|
|
|
|
class _ModalBottomSheetState<T> : State<_ModalBottomSheet<T>> { |
|
|
|
ParametricCurve<float> animationCurve = material_._modalBottomSheetCurve; |
|
|
|
|
|
|
|
String _getRouteLabel(MaterialLocalizations localizations) { |
|
|
|
switch (Theme.of(context).platform) { |
|
|
|
case RuntimePlatform.IPhonePlayer: |
|
|
|
case RuntimePlatform.OSXEditor: |
|
|
|
case RuntimePlatform.OSXPlayer: |
|
|
|
return ""; |
|
|
|
case RuntimePlatform.Android: |
|
|
|
case RuntimePlatform.LinuxEditor: |
|
|
|
case RuntimePlatform.LinuxPlayer: |
|
|
|
case RuntimePlatform.WindowsEditor: |
|
|
|
case RuntimePlatform.WindowsPlayer: |
|
|
|
return localizations.dialogLabel; |
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
void handleDragStart(DragStartDetails details) { |
|
|
|
animationCurve = Curves.linear; |
|
|
|
} |
|
|
|
|
|
|
|
void handleDragEnd(DragEndDetails details, bool? isClosing = null) { |
|
|
|
animationCurve = new _BottomSheetSuspendedCurve( |
|
|
|
widget.route.animation.value, |
|
|
|
curve: material_._modalBottomSheetCurve |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
D.assert(WidgetsD.debugCheckHasMediaQuery(context)); |
|
|
|
D.assert(material_.debugCheckHasMaterialLocalizations(context)); |
|
|
|
String routeLabel = _getRouteLabel(localizations); |
|
|
|
return new GestureDetector( |
|
|
|
onTap: () => Navigator.pop<T>(context), |
|
|
|
child: new AnimatedBuilder( |
|
|
|
animation: widget.route.animation, |
|
|
|
builder: (BuildContext _context, Widget child) => { |
|
|
|
float animationValue = |
|
|
|
mediaQuery.accessibleNavigation ? 1.0f : widget.route.animation.value; |
|
|
|
return new ClipRect( |
|
|
|
child: new CustomSingleChildLayout( |
|
|
|
layoutDelegate: new _ModalBottomSheetLayout(animationValue), |
|
|
|
child: new BottomSheet( |
|
|
|
animationController: widget.route._animationController, |
|
|
|
onClosing: () => Navigator.pop<T>(_context), |
|
|
|
builder: widget.route.builder |
|
|
|
) |
|
|
|
return new AnimatedBuilder( |
|
|
|
animation: widget.route.animation, |
|
|
|
builder: (BuildContext _context, Widget child) => { |
|
|
|
float animationValue = animationCurve.transform( |
|
|
|
mediaQuery.accessibleNavigation ? 1.0f : widget.route.animation.value |
|
|
|
); |
|
|
|
return new ClipRect( |
|
|
|
child: new CustomSingleChildLayout( |
|
|
|
layoutDelegate: new _ModalBottomSheetLayout(animationValue, widget.isScrollControlled), |
|
|
|
child: new BottomSheet( |
|
|
|
animationController: widget.route._animationController, |
|
|
|
onClosing: () => { |
|
|
|
if (widget.route.isCurrent) { |
|
|
|
Navigator.pop<object>(_context); |
|
|
|
} |
|
|
|
}, |
|
|
|
builder: widget.route.builder, |
|
|
|
backgroundColor: widget.backgroundColor, |
|
|
|
elevation: widget.elevation, |
|
|
|
shape: widget.shape, |
|
|
|
clipBehavior: widget.clipBehavior, |
|
|
|
enableDrag: widget.enableDrag, |
|
|
|
onDragStart: handleDragStart, |
|
|
|
onDragEnd: handleDragEnd |
|
|
|
); |
|
|
|
} |
|
|
|
) |
|
|
|
) |
|
|
|
); |
|
|
|
} |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
WidgetBuilder builder = null, |
|
|
|
ThemeData theme = null, |
|
|
|
string barrierLabel = null, |
|
|
|
Color backgroundColor = null, |
|
|
|
float? elevation = null, |
|
|
|
ShapeBorder shape = null, |
|
|
|
Clip? clipBehavior = null, |
|
|
|
Color modalBarrierColor = null, |
|
|
|
bool isDismissible = true, |
|
|
|
bool enableDrag = true, |
|
|
|
bool? isScrollControlled = null, |
|
|
|
D.assert(isScrollControlled != null); |
|
|
|
this.backgroundColor = backgroundColor; |
|
|
|
this.elevation = elevation; |
|
|
|
this.shape = shape; |
|
|
|
this.clipBehavior = clipBehavior; |
|
|
|
this.modalBarrierColor = modalBarrierColor; |
|
|
|
this.isDismissible = isDismissible; |
|
|
|
this.enableDrag = enableDrag; |
|
|
|
public readonly bool? isScrollControlled; |
|
|
|
public readonly Color backgroundColor; |
|
|
|
public readonly float? elevation; |
|
|
|
public readonly ShapeBorder shape; |
|
|
|
public readonly Clip? clipBehavior; |
|
|
|
public readonly Color modalBarrierColor; |
|
|
|
public readonly bool isDismissible; |
|
|
|
public readonly bool enableDrag; |
|
|
|
get { return material_._kBottomSheetDuration; } |
|
|
|
get { return material_._bottomSheetEnterDuration; } |
|
|
|
} |
|
|
|
|
|
|
|
public override |
|
|
|
TimeSpan reverseTransitionDuration { |
|
|
|
get { return material_._bottomSheetExitDuration; } |
|
|
|
get { return true; } |
|
|
|
get { return isDismissible; } |
|
|
|
|
|
|
|
get { return Colors.black54; } |
|
|
|
get { return modalBarrierColor ?? Colors.black54; } |
|
|
|
} |
|
|
|
|
|
|
|
public AnimationController _animationController; |
|
|
|
|
|
|
|
|
|
|
public override Widget buildPage(BuildContext context, Animation<float> animation, |
|
|
|
Animation<float> secondaryAnimation) { |
|
|
|
BottomSheetThemeData sheetTheme = theme?.bottomSheetTheme ?? Theme.of(context).bottomSheetTheme; |
|
|
|
|
|
|
|
child: new _ModalBottomSheet<T>(route: this) |
|
|
|
child: new _ModalBottomSheet<T>( |
|
|
|
route: this, |
|
|
|
backgroundColor: backgroundColor ?? sheetTheme?.modalBackgroundColor ?? sheetTheme?.backgroundColor, |
|
|
|
elevation: elevation ?? sheetTheme?.modalElevation ?? sheetTheme?.elevation, |
|
|
|
shape: shape, |
|
|
|
clipBehavior: clipBehavior, |
|
|
|
isScrollControlled: isScrollControlled ?? false, |
|
|
|
enableDrag: enableDrag |
|
|
|
) |
|
|
|
); |
|
|
|
if (theme != null) { |
|
|
|
bottomSheet = new Theme(data: theme, child: bottomSheet); |
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
class _BottomSheetSuspendedCurve : ParametricCurve<float> { |
|
|
|
internal _BottomSheetSuspendedCurve( |
|
|
|
float startingPoint, |
|
|
|
Curve curve = null |
|
|
|
) { |
|
|
|
D.assert(curve != null); |
|
|
|
this.startingPoint = startingPoint; |
|
|
|
this.curve = curve ?? Curves.easeOutCubic; |
|
|
|
} |
|
|
|
|
|
|
|
public readonly float startingPoint; |
|
|
|
|
|
|
|
public readonly Curve curve; |
|
|
|
|
|
|
|
public override float transform(float t) { |
|
|
|
D.assert(t >= 0.0 && t <= 1.0); |
|
|
|
D.assert(startingPoint >= 0.0 && startingPoint <= 1.0); |
|
|
|
|
|
|
|
if (t < startingPoint) { |
|
|
|
return t; |
|
|
|
} |
|
|
|
|
|
|
|
if (t == 1.0) { |
|
|
|
return t; |
|
|
|
} |
|
|
|
|
|
|
|
float curveProgress = (t - startingPoint) / (1 - startingPoint); |
|
|
|
float transformed = curve.transform(curveProgress); |
|
|
|
return Mathf.Lerp(startingPoint, 1, transformed); |
|
|
|
} |
|
|
|
|
|
|
|
public override string ToString() { |
|
|
|
return $"{foundation_.describeIdentity(this)}({startingPoint}, {curve})"; |
|
|
|
} |
|
|
|
} |
|
|
|
} |