-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
327 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,321 @@ | ||
import 'package:flutter/foundation.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter/rendering.dart'; | ||
|
||
import 'foundation/sheet_context.dart'; | ||
import 'foundation/sheet_controller.dart'; | ||
import 'foundation/sheet_gesture_tamperer.dart'; | ||
import 'foundation/sheet_physics.dart'; | ||
import 'foundation/sheet_position.dart'; | ||
import 'foundation/sheet_position_scope.dart'; | ||
import 'foundation/sheet_viewport.dart'; | ||
import 'internal/transition_observer.dart'; | ||
import 'scrollable/scrollable_sheet_position.dart'; | ||
|
||
class PagedSheet extends StatefulWidget { | ||
const PagedSheet({ | ||
super.key, | ||
this.controller, | ||
required this.transitionObserver, | ||
required this.child, | ||
}); | ||
|
||
final SheetController? controller; | ||
|
||
final TransitionObserver transitionObserver; | ||
|
||
final Widget child; | ||
|
||
@override | ||
State<PagedSheet> createState() => _PagedSheetState(); | ||
} | ||
|
||
class _PagedSheetState extends State<PagedSheet> | ||
with TickerProviderStateMixin, SheetContextStateMixin { | ||
@override | ||
Widget build(BuildContext context) { | ||
final gestureProxy = SheetGestureProxy.maybeOf(context); | ||
final controller = | ||
widget.controller ?? SheetControllerScope.maybeOf(context); | ||
|
||
return _PagedSheetPositionScope( | ||
key: SheetViewport.of(context).positionOwnerKey, | ||
context: this, | ||
transitionObserver: widget.transitionObserver, | ||
controller: controller, | ||
gestureTamperer: gestureProxy, | ||
debugLabel: kDebugMode ? 'NavigationSheet' : null, | ||
child: widget.child, | ||
); | ||
} | ||
} | ||
|
||
class _PagedSheetPositionScope extends SheetPositionScope<_PagedSheetPosition> | ||
with TransitionAwareWidgetMixin { | ||
const _PagedSheetPositionScope({ | ||
super.key, | ||
super.controller, | ||
super.gestureTamperer, | ||
required super.context, | ||
this.debugLabel, | ||
required this.transitionObserver, | ||
required super.child, | ||
}) : super( | ||
minPosition: const SheetAnchor.pixels(0), | ||
maxPosition: const SheetAnchor.proportional(1), | ||
// TODO: Use more appropriate physics. | ||
physics: const ClampingSheetPhysics(), | ||
isPrimary: true, | ||
); | ||
|
||
/// {@macro SheetPosition.debugLabel} | ||
final String? debugLabel; | ||
|
||
@override | ||
final TransitionObserver transitionObserver; | ||
|
||
@override | ||
_PagedSheetPositionScopeState createState() { | ||
return _PagedSheetPositionScopeState(); | ||
} | ||
} | ||
|
||
class _PagedSheetPositionScopeState extends SheetPositionScopeState< | ||
_PagedSheetPosition, | ||
_PagedSheetPositionScope> with TransitionAwareStateMixin { | ||
@override | ||
bool shouldRebuildPosition(_PagedSheetPosition oldPosition) { | ||
return widget.debugLabel != oldPosition.debugLabel || | ||
super.shouldRebuildPosition(oldPosition); | ||
} | ||
|
||
@override | ||
_PagedSheetPosition buildPosition(SheetContext context) { | ||
return _PagedSheetPosition( | ||
context: context, | ||
initialPosition: const SheetAnchor.proportional(1), | ||
minPosition: widget.minPosition, | ||
maxPosition: widget.maxPosition, | ||
physics: widget.physics, | ||
gestureTamperer: widget.gestureTamperer, | ||
debugLabel: widget.debugLabel, | ||
); | ||
} | ||
|
||
@override | ||
void didChangeTransitionState(Transition? transition) { | ||
switch (transition) { | ||
case NoTransition(:final _PagedSheetRoute currentRoute): | ||
position.goIdleWithRoute(currentRoute); | ||
|
||
case ForwardTransition( | ||
:final _PagedSheetRoute originRoute, | ||
:final _PagedSheetRoute destinationRoute, | ||
:final animation, | ||
): | ||
beginActivity(TransitionSheetActivity( | ||
currentRoute: originRoute, | ||
nextRoute: destinationRoute, | ||
animation: animation, | ||
animationCurve: Curves.easeInOutCubic, | ||
)); | ||
|
||
case BackwardTransition( | ||
:final _PagedSheetRoute originRoute, | ||
:final _PagedSheetRoute destinationRoute, | ||
:final animation, | ||
): | ||
beginActivity(TransitionSheetActivity( | ||
currentRoute: originRoute, | ||
nextRoute: destinationRoute, | ||
animation: animation, | ||
animationCurve: Curves.easeInOutCubic, | ||
)); | ||
|
||
case UserGestureTransition( | ||
:final _PagedSheetRoute currentRoute, | ||
:final _PagedSheetRoute previousRoute, | ||
:final animation, | ||
): | ||
beginActivity(TransitionSheetActivity( | ||
currentRoute: currentRoute, | ||
nextRoute: previousRoute, | ||
animation: animation, | ||
animationCurve: Curves.linear, | ||
)); | ||
|
||
case _: | ||
position.goIdle(); | ||
} | ||
} | ||
} | ||
|
||
typedef _PageGeometry = ({ | ||
Size contentSize, | ||
double offset, | ||
}); | ||
|
||
class _PagedSheetPosition extends DraggableScrollableSheetPosition { | ||
_PagedSheetPosition({ | ||
required super.context, | ||
required super.initialPosition, | ||
required super.minPosition, | ||
required super.maxPosition, | ||
required super.physics, | ||
super.gestureTamperer, | ||
super.debugLabel, | ||
}); | ||
|
||
final Map<_PagedSheetRoute, _PageGeometry?> _routeGeometries = {}; | ||
|
||
_PagedSheetRoute? _currentRoute; | ||
|
||
void applyNewRouteContentSize(_PagedSheetRoute route, Size contentSize) { | ||
assert(_routeGeometries.containsKey(route)); | ||
_routeGeometries[route] = ( | ||
contentSize: contentSize, | ||
offset: _routeGeometries[route]?.offset ?? | ||
route.initialOffset.resolve(contentSize), | ||
); | ||
if (route == _currentRoute) { | ||
applyNewContentSize(contentSize); | ||
} | ||
} | ||
|
||
void goIdleWithRoute(_PagedSheetRoute route) { | ||
assert(_routeGeometries.containsKey(route)); | ||
_currentRoute = route; | ||
goIdle(); | ||
} | ||
} | ||
|
||
class _RouteContentLayoutObserver extends SingleChildRenderObjectWidget { | ||
const _RouteContentLayoutObserver({required super.child}); | ||
|
||
@override | ||
RenderObject createRenderObject(BuildContext context) { | ||
return _RenderRouteContentLayoutObserver( | ||
parentRoute: _PagedSheetRoute.of(context), | ||
controller: SheetPositionScope.of<_PagedSheetPosition>(context), | ||
); | ||
} | ||
|
||
@override | ||
void updateRenderObject( | ||
BuildContext context, | ||
_RenderRouteContentLayoutObserver renderObject, | ||
) { | ||
renderObject | ||
..parentRoute = _PagedSheetRoute.of(context) | ||
..controller = SheetPositionScope.of<_PagedSheetPosition>(context); | ||
} | ||
} | ||
|
||
class _RenderRouteContentLayoutObserver extends RenderPositionedBox { | ||
_RenderRouteContentLayoutObserver({ | ||
required _PagedSheetRoute parentRoute, | ||
required _PagedSheetPosition controller, | ||
}) : _parentRoute = parentRoute, | ||
_controller = controller, | ||
super(alignment: Alignment.topCenter); | ||
|
||
_PagedSheetPosition _controller; | ||
// ignore: avoid_setters_without_getters | ||
set controller(_PagedSheetPosition value) { | ||
if (_controller != value) { | ||
_controller = value; | ||
markNeedsLayout(); | ||
} | ||
} | ||
|
||
_PagedSheetRoute _parentRoute; | ||
// ignore: avoid_setters_without_getters | ||
set parentRoute(_PagedSheetRoute value) { | ||
if (_parentRoute != value) { | ||
_parentRoute = value; | ||
markNeedsLayout(); | ||
} | ||
} | ||
|
||
@override | ||
void performLayout() { | ||
super.performLayout(); | ||
if (child?.size case final childSize?) { | ||
_controller.applyNewRouteContentSize(_parentRoute, childSize); | ||
} | ||
} | ||
} | ||
|
||
@optionalTypeArgs | ||
abstract class _PagedSheetRoute<T> extends PageRoute<T> { | ||
_PagedSheetRoute({super.settings}); | ||
|
||
SheetAnchor get initialOffset; | ||
SheetAnchor get minOffset; | ||
SheetAnchor get maxOffset; | ||
RouteTransitionsBuilder? get transitionsBuilder; | ||
|
||
@override | ||
Color? get barrierColor => null; | ||
|
||
@override | ||
String? get barrierLabel => null; | ||
|
||
@override | ||
bool canTransitionFrom(TransitionRoute<dynamic> previousRoute) { | ||
return previousRoute is _PagedSheetRoute; | ||
} | ||
|
||
@override | ||
bool canTransitionTo(TransitionRoute<dynamic> nextRoute) { | ||
return nextRoute is _PagedSheetRoute; | ||
} | ||
|
||
Widget buildContent( | ||
BuildContext context, | ||
Animation<double> animation, | ||
Animation<double> secondaryAnimation, | ||
); | ||
|
||
@override | ||
@nonVirtual | ||
Widget buildPage( | ||
BuildContext context, | ||
Animation<double> animation, | ||
Animation<double> secondaryAnimation, | ||
) { | ||
return _RouteContentLayoutObserver( | ||
child: buildContent( | ||
context, | ||
animation, | ||
secondaryAnimation, | ||
), | ||
); | ||
} | ||
|
||
@override | ||
Widget buildTransitions( | ||
BuildContext context, | ||
Animation<double> animation, | ||
Animation<double> secondaryAnimation, | ||
Widget child, | ||
) { | ||
if (transitionsBuilder case final builder?) { | ||
return builder(context, animation, secondaryAnimation, child); | ||
} | ||
final theme = Theme.of(context).pageTransitionsTheme; | ||
return theme.buildTransitions<T>( | ||
this, | ||
context, | ||
animation, | ||
secondaryAnimation, | ||
child, | ||
); | ||
} | ||
|
||
static _PagedSheetRoute<T> of<T>(BuildContext context) { | ||
final route = ModalRoute.of(context); | ||
assert(route != null && route is _PagedSheetRoute<T>); | ||
return route! as _PagedSheetRoute<T>; | ||
} | ||
} |