Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fujidaiti committed Dec 3, 2024
1 parent 144a74a commit 9cbb634
Show file tree
Hide file tree
Showing 3 changed files with 327 additions and 1 deletion.
4 changes: 3 additions & 1 deletion lib/src/foundation/sheet_position.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ import 'sheet_status.dart';
/// proportionally to the sheet's content height.
/// - [FixedSheetAnchor], which defines the position
/// using a fixed value in pixels.
// TODO: Rename to SheetPosition.
// TODO: Rename to SheetOffset.
abstract interface class SheetAnchor {
/// {@macro FixedSheetAnchor}
// TODO: Rename to `absolute`.
const factory SheetAnchor.pixels(double pixels) = FixedSheetAnchor;

/// {@macro ProportionalSheetAnchor}
// TODO: Rename to `relative`.
const factory SheetAnchor.proportional(double size) = ProportionalSheetAnchor;

/// Resolves the position to an actual value in pixels.
Expand Down
3 changes: 3 additions & 0 deletions lib/src/foundation/sheet_position_scope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ abstract class SheetPositionScope<E extends SheetPosition>
@internal
abstract class SheetPositionScopeState<E extends SheetPosition,
W extends SheetPositionScope> extends State<W> {
@protected
E get position => _position;
late E _position;

SheetController? _controller;

SheetPositionScopeKey? get _scopeKey {
Expand Down
321 changes: 321 additions & 0 deletions lib/src/paged_sheet.dart
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>;
}
}

0 comments on commit 9cbb634

Please sign in to comment.