diff --git a/package/lib/smooth_sheets.dart b/package/lib/smooth_sheets.dart index bad454cb..2d523d62 100644 --- a/package/lib/smooth_sheets.dart +++ b/package/lib/smooth_sheets.dart @@ -19,5 +19,7 @@ export 'src/modal/modal_sheet.dart'; export 'src/navigation/navigation_route.dart'; export 'src/navigation/navigation_routes.dart'; export 'src/navigation/navigation_sheet.dart'; -export 'src/scrollable/scrollable_sheet.dart'; -export 'src/scrollable/scrollable_sheet_extent.dart'; +export 'src/scrollable/scrollable_sheet.dart' + hide PrimarySheetContentScrollController; +export 'src/scrollable/scrollable_sheet_extent.dart' + hide SheetContentScrollController; diff --git a/package/lib/src/internal/into.dart b/package/lib/src/internal/into.dart deleted file mode 100644 index f4f7f3b5..00000000 --- a/package/lib/src/internal/into.dart +++ /dev/null @@ -1,3 +0,0 @@ -extension Into on T { - U? intoOrNull() => this is U ? this as U : null; -} diff --git a/package/lib/src/scrollable/content_scroll_position.dart b/package/lib/src/scrollable/content_scroll_position.dart deleted file mode 100644 index 8bbf40a8..00000000 --- a/package/lib/src/scrollable/content_scroll_position.dart +++ /dev/null @@ -1,209 +0,0 @@ -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:meta/meta.dart'; -import 'package:smooth_sheets/src/scrollable/scrollable_sheet_extent.dart'; - -@internal -sealed class DelegationResult { - const DelegationResult(); - const factory DelegationResult.handled(T value) = Handled; - const factory DelegationResult.notHandled() = NotHandled; -} - -@internal -class Handled extends DelegationResult { - const Handled(this.value); - final T value; -} - -@internal -class NotHandled extends DelegationResult { - const NotHandled(); -} - -@internal -mixin SheetContentScrollPositionDelegate { - void onDragStart(DragStartDetails details) {} - void onDragEnd() {} - void onWillBallisticScrollCancel() {} - - DelegationResult applyUserScrollOffset( - double delta, - SheetContentScrollPosition position, - ) { - return const DelegationResult.notHandled(); - } - - DelegationResult applyBallisticScrollOffset( - double delta, - double velocity, - SheetContentScrollPosition position, - ) { - return const DelegationResult.notHandled(); - } - - DelegationResult goIdleScroll( - SheetContentScrollPosition position, - ) { - return const DelegationResult.notHandled(); - } - - DelegationResult goBallisticScroll( - double velocity, - // ignore: avoid_positional_boolean_parameters - bool shouldIgnorePointer, - SheetContentScrollPosition position, - ) { - return const DelegationResult.notHandled(); - } -} - -@internal -class SheetContentScrollPosition extends ScrollPositionWithSingleContext { - SheetContentScrollPosition({ - required super.physics, - required super.context, - super.oldPosition, - super.initialPixels, - super.debugLabel, - super.keepScrollOffset, - }); - - ValueGetter? delegate; - SheetContentScrollPositionDelegate? get _delegate => delegate?.call(); - - @override - void applyUserOffset(double delta) { - updateUserScrollDirection( - delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse, - ); - - final result = _delegate?.applyUserScrollOffset(delta, this); - switch (result) { - case NotHandled(): - setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta)); - case null || Handled(): - break; - } - } - - double applyBallisticOffset(double delta, double velocity) { - final result = _delegate?.applyBallisticScrollOffset(delta, velocity, this); - return switch (result) { - Handled(value: final overscroll) => overscroll, - null || NotHandled() => physics.applyPhysicsToUserOffset(this, delta), - }; - } - - @override - void goIdle() { - switch (_delegate?.goIdleScroll(this)) { - case null || NotHandled(): - super.goIdle(); - case Handled(value: final activity): - beginActivity(activity); - } - } - - @override - void goBallistic(double velocity) { - final shouldIgnorePointer = activity?.shouldIgnorePointer ?? true; - final result = - _delegate?.goBallisticScroll(velocity, shouldIgnorePointer, this); - - switch (result) { - case Handled(value: final activity): - beginActivity(activity); - case null || NotHandled(): - super.goBallistic(velocity); - } - } - - void onWillBallisticCancel() { - if (_delegate != null) { - _delegate!.onWillBallisticScrollCancel(); - } - } - - @override - Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) { - switch (_delegate) { - case null: - return super.drag(details, dragCancelCallback); - - case final delegate: - delegate.onDragStart(details); - return super.drag(details, () { - _delegate?.onDragEnd(); - dragCancelCallback(); - }); - } - } -} - -@internal -class SheetContentScrollController extends ScrollController { - SheetContentScrollController({ - super.debugLabel, - super.initialScrollOffset, - super.keepScrollOffset, - }); - - ScrollableSheetExtent? _extent; - // ignore: avoid_setters_without_getters - set extent(ScrollableSheetExtent? newExtent) { - if (_extent == newExtent) return; - - if (_extent != null) { - positions - .whereType() - .forEach(_extent!.detach); - } - - if (newExtent != null) { - positions - .whereType() - .forEach(newExtent.attach); - } - - _extent = newExtent; - } - - @override - ScrollPosition createScrollPosition( - ScrollPhysics physics, - ScrollContext context, - ScrollPosition? oldPosition, - ) { - return SheetContentScrollPosition( - initialPixels: initialScrollOffset, - keepScrollOffset: keepScrollOffset, - debugLabel: debugLabel, - context: context, - oldPosition: oldPosition, - physics: switch (physics) { - AlwaysScrollableScrollPhysics() => physics, - _ => AlwaysScrollableScrollPhysics(parent: physics), - }, - ); - } - - @override - void attach(ScrollPosition position) { - if (position is SheetContentScrollPosition) { - _extent?.attach(position); - } - - super.attach(position); - } - - @override - void detach(ScrollPosition position) { - if (position is SheetContentScrollPosition) { - _extent?.detach(position); - } - - super.detach(position); - } -} diff --git a/package/lib/src/scrollable/delegatable_scroll_position.dart b/package/lib/src/scrollable/delegatable_scroll_position.dart new file mode 100644 index 00000000..77ce6e8a --- /dev/null +++ b/package/lib/src/scrollable/delegatable_scroll_position.dart @@ -0,0 +1,191 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:meta/meta.dart'; + +@internal +sealed class DelegationResult { + const DelegationResult(); + const factory DelegationResult.handled(T value) = Handled; + const factory DelegationResult.notHandled() = NotHandled; +} + +@internal +class Handled extends DelegationResult { + const Handled(this.value); + final T value; +} + +@internal +class NotHandled extends DelegationResult { + const NotHandled(); +} + +@internal +mixin ScrollPositionDelegate { + void onContentDragStart( + DragStartDetails details, + DelegatableScrollPosition position, + ) {} + + void onContentDragUpdate( + DragUpdateDetails details, + DelegatableScrollPosition position, + ) {} + + void onContentDragEnd( + DragEndDetails details, + DelegatableScrollPosition position, + ) {} + + void onContentDragCancel( + DelegatableScrollPosition position, + ) {} + + void onWillContentBallisticScrollCancel( + DelegatableScrollPosition position, + ) {} + + DelegationResult onApplyUserScrollOffsetToContent( + double delta, + DelegatableScrollPosition position, + ) { + return const DelegationResult.notHandled(); + } + + DelegationResult onApplyBallisticScrollOffsetToContent( + double delta, + double velocity, + DelegatableScrollPosition position, + ) { + return const DelegationResult.notHandled(); + } + + DelegationResult onContentGoIdle( + DelegatableScrollPosition position, + ) { + return const DelegationResult.notHandled(); + } + + DelegationResult onContentGoBallistic( + double velocity, + // ignore: avoid_positional_boolean_parameters + bool shouldIgnorePointer, + DelegatableScrollPosition position, + ) { + return const DelegationResult.notHandled(); + } +} + +@internal +class DelegatableScrollPosition extends ScrollPositionWithSingleContext { + DelegatableScrollPosition({ + required super.physics, + required super.context, + super.oldPosition, + super.initialPixels, + super.debugLabel, + super.keepScrollOffset, + required this.getDelegate, + }); + + final ValueGetter getDelegate; + ScrollPositionDelegate? get _delegate => getDelegate(); + + @override + void applyUserOffset(double delta) { + updateUserScrollDirection( + delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse, + ); + + final result = _delegate?.onApplyUserScrollOffsetToContent(delta, this); + switch (result) { + case NotHandled(): + setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta)); + case null || Handled(): + break; + } + } + + double applyBallisticOffset(double delta, double velocity) { + final result = + _delegate?.onApplyBallisticScrollOffsetToContent(delta, velocity, this); + return switch (result) { + Handled(value: final overscroll) => overscroll, + null || NotHandled() => physics.applyPhysicsToUserOffset(this, delta), + }; + } + + @override + void goIdle() { + switch (_delegate?.onContentGoIdle(this)) { + case null || NotHandled(): + super.goIdle(); + case Handled(value: final activity): + beginActivity(activity); + } + } + + @override + void goBallistic(double velocity) { + final shouldIgnorePointer = activity?.shouldIgnorePointer ?? true; + final result = + _delegate?.onContentGoBallistic(velocity, shouldIgnorePointer, this); + + switch (result) { + case Handled(value: final activity): + beginActivity(activity); + case null || NotHandled(): + super.goBallistic(velocity); + } + } + + void onWillBallisticCancel() { + if (_delegate != null) { + _delegate!.onWillContentBallisticScrollCancel(this); + } + } + + @override + Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) { + _delegate?.onContentDragStart(details, this); + return _DragProxy( + target: super.drag(details, dragCancelCallback), + onUpdate: (details) => _delegate?.onContentDragUpdate(details, this), + onEnd: (details) => _delegate?.onContentDragEnd(details, this), + onCancel: () => _delegate?.onContentDragCancel(this), + ); + } +} + +class _DragProxy extends Drag { + _DragProxy({ + required this.target, + required this.onUpdate, + required this.onEnd, + required this.onCancel, + }); + + final Drag target; + final void Function(DragUpdateDetails) onUpdate; + final void Function(DragEndDetails) onEnd; + final VoidCallback onCancel; + + @override + void update(DragUpdateDetails details) { + onUpdate(details); + target.update(details); + } + + @override + void end(DragEndDetails details) { + onEnd(details); + target.end(details); + } + + @override + void cancel() { + onCancel(); + target.cancel(); + } +} diff --git a/package/lib/src/scrollable/scrollable_sheet.dart b/package/lib/src/scrollable/scrollable_sheet.dart index 6c45fa34..683b8df6 100644 --- a/package/lib/src/scrollable/scrollable_sheet.dart +++ b/package/lib/src/scrollable/scrollable_sheet.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:smooth_sheets/src/foundation/sheet_extent.dart'; +import 'package:meta/meta.dart'; +import 'package:smooth_sheets/smooth_sheets.dart'; import 'package:smooth_sheets/src/foundation/sized_content_sheet.dart'; -import 'package:smooth_sheets/src/internal/into.dart'; -import 'package:smooth_sheets/src/scrollable/content_scroll_position.dart'; import 'package:smooth_sheets/src/scrollable/scrollable_sheet_extent.dart'; class ScrollableSheet extends SizedContentSheet { @@ -43,6 +42,7 @@ class _ScrollableSheetState extends SizedContentSheetState { } } +@internal class PrimarySheetContentScrollController extends StatelessWidget { const PrimarySheetContentScrollController({ super.key, @@ -59,7 +59,7 @@ class PrimarySheetContentScrollController extends StatelessWidget { @override Widget build(BuildContext context) { - return SheetContentScrollControllerScope( + return SheetScrollable( debugLabel: debugLabel, keepScrollOffset: keepScrollOffset, initialScrollOffset: initialScrollOffset, @@ -73,8 +73,8 @@ class PrimarySheetContentScrollController extends StatelessWidget { } } -class SheetContentScrollControllerScope extends StatefulWidget { - const SheetContentScrollControllerScope({ +class SheetScrollable extends StatefulWidget { + const SheetScrollable({ super.key, this.debugLabel, this.keepScrollOffset = true, @@ -88,28 +88,36 @@ class SheetContentScrollControllerScope extends StatefulWidget { final ScrollableWidgetBuilder builder; @override - State createState() => - _SheetContentScrollControllerScopeState(); + State createState() => _SheetScrollableState(); } -class _SheetContentScrollControllerScopeState - extends State { - late final SheetContentScrollController _scrollController; +class _SheetScrollableState extends State { + late ScrollController _scrollController; @override void initState() { super.initState(); - _scrollController = SheetContentScrollController( - debugLabel: widget.debugLabel, - initialScrollOffset: widget.initialScrollOffset, - keepScrollOffset: widget.keepScrollOffset, - ); } @override void didChangeDependencies() { super.didChangeDependencies(); - _scrollController.extent = SheetExtentScope.maybeOf(context)?.intoOrNull(); + final extent = SheetExtentScope.maybeOf(context); + _scrollController = switch (extent) { + final ScrollableSheetExtent extent => SheetContentScrollController( + extent: extent, + debugLabel: widget.debugLabel, + initialScrollOffset: widget.initialScrollOffset, + keepScrollOffset: widget.keepScrollOffset, + ), + // If this widget is not a descendant of a SheetExtentScope, + // then create a normal ScrollController for stubbing. + _ => ScrollController( + debugLabel: widget.debugLabel, + initialScrollOffset: widget.initialScrollOffset, + keepScrollOffset: widget.keepScrollOffset, + ), + }; } @override diff --git a/package/lib/src/scrollable/scrollable_sheet_extent.dart b/package/lib/src/scrollable/scrollable_sheet_extent.dart index 76baee54..1c41597e 100644 --- a/package/lib/src/scrollable/scrollable_sheet_extent.dart +++ b/package/lib/src/scrollable/scrollable_sheet_extent.dart @@ -1,14 +1,14 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:meta/meta.dart'; import 'package:smooth_sheets/src/foundation/sheet_activity.dart'; import 'package:smooth_sheets/src/foundation/sheet_extent.dart'; import 'package:smooth_sheets/src/foundation/sheet_physics.dart'; import 'package:smooth_sheets/src/foundation/sheet_status.dart'; import 'package:smooth_sheets/src/foundation/sized_content_sheet.dart'; import 'package:smooth_sheets/src/internal/double_utils.dart'; -import 'package:smooth_sheets/src/internal/into.dart'; -import 'package:smooth_sheets/src/scrollable/content_scroll_position.dart'; +import 'package:smooth_sheets/src/scrollable/delegatable_scroll_position.dart'; import 'package:smooth_sheets/src/scrollable/scrollable_sheet_physics.dart'; class ScrollableSheetExtentFactory extends SizedContentSheetExtentFactory { @@ -46,74 +46,164 @@ class ScrollableSheetExtent extends SizedContentSheetExtent { goIdle(); } - final Set _contentScrollPositions = {}; - - void attach(SheetContentScrollPosition position) { - _contentScrollPositions.add(position); - position.delegate = () => activity.intoOrNull(); - } - - void detach(SheetContentScrollPosition position) { - _contentScrollPositions.remove(position); - position.delegate = null; + ScrollPositionDelegate? get _scrollPositionDelegate { + return switch (activity) { + final ScrollPositionDelegate delegate => delegate, + _ => null, + }; } @override - void dispose() { - [..._contentScrollPositions].forEach(detach); - super.dispose(); + void goIdle() { + beginActivity( + _ContentIdleScrollDrivenSheetActivity( + initialExtent: initialExtent, + ), + ); } - @override - void goIdle() => beginActivity( - _ContentIdleScrollDrivenSheetActivity(initialExtent: initialExtent), - ); - @override void goBallisticWith(Simulation simulation) { beginActivity( - _DragInterruptibleBallisticSheetActivity(simulation: simulation), + _DragInterruptibleBallisticSheetActivity( + simulation: simulation, + ), ); } +} + +@internal +class SheetContentScrollController extends ScrollController { + SheetContentScrollController({ + super.debugLabel, + super.initialScrollOffset, + super.keepScrollOffset, + required this.extent, + }); + + final ScrollableSheetExtent extent; @override - void beginActivity(SheetActivity activity) { - // TODO: Stop the content scrolling when the new activity is not '_ContentScrollDrivenSheetActivity'. - super.beginActivity(activity); + ScrollPosition createScrollPosition( + ScrollPhysics physics, + ScrollContext context, + ScrollPosition? oldPosition, + ) { + return DelegatableScrollPosition( + getDelegate: () => extent._scrollPositionDelegate, + initialPixels: initialScrollOffset, + keepScrollOffset: keepScrollOffset, + debugLabel: debugLabel, + context: context, + oldPosition: oldPosition, + physics: switch (physics) { + AlwaysScrollableScrollPhysics() => physics, + _ => AlwaysScrollableScrollPhysics(parent: physics), + }, + ); } } -sealed class _ContentScrollDrivenSheetActivity extends SheetActivity - with SheetContentScrollPositionDelegate { - double _scrolledDistance(ScrollPosition position) => position.pixels; +/// A [SheetActivity] that is driven by one or more [ScrollPosition]s +/// of the scrollable content within the sheet. +abstract class _ContentScrollDrivenSheetActivity extends SheetActivity + with ScrollPositionDelegate { + @override + void onContentDragStart( + DragStartDetails details, + DelegatableScrollPosition position, + ) { + delegate.beginActivity( + _ContentUserScrollDrivenSheetActivity( + contentScrollPosition: position, + ), + ); + } - double _draggedDistance() => pixels! - delegate.minPixels!; + @override + DelegationResult onContentGoIdle( + DelegatableScrollPosition position, + ) { + delegate.goIdle(); + return super.onContentGoIdle(position); + } - double _draggableDistance() => delegate.maxPixels! - delegate.minPixels!; + @override + DelegationResult onContentGoBallistic( + double velocity, + bool shouldIgnorePointer, + DelegatableScrollPosition position, + ) { + if (!delegate.hasPixels) { + return const DelegationResult.notHandled(); + } - double _scrollableDistance(ScrollPosition position) => - position.maxScrollExtent - position.minScrollExtent; + if (position.pixels.isApprox(position.minScrollExtent)) { + final simulation = delegate.physics + .createBallisticSimulation(velocity, delegate.metrics); + if (simulation != null) { + delegate.goBallisticWith(simulation); + return DelegationResult.handled(IdleScrollActivity(position)); + } + } - double _pixelsForPhysics(ScrollPosition position) => - _scrolledDistance(position) + _draggedDistance(); + final scrolledDistance = position.pixels; + final draggedDistance = pixels! - delegate.minPixels!; + final draggableDistance = delegate.maxPixels! - delegate.minPixels!; + final scrollableDistance = + position.maxScrollExtent - position.minScrollExtent; + final pixelsForScrollPhysics = scrolledDistance + draggedDistance; + final scrollMetricsForScrollPhysics = position.copyWith( + minScrollExtent: 0, + // How many pixels the user can scroll/drag + maxScrollExtent: draggableDistance + scrollableDistance, + // How many pixels the user scrolled/dragged + pixels: pixelsForScrollPhysics, + ); - ScrollMetrics _scrollMetricsForPhysics(ScrollPosition position) => - position.copyWith( - minScrollExtent: 0, - // How many pixels the user can scroll/drag - maxScrollExtent: _draggableDistance() + _scrollableDistance(position), - // How many pixels the user scrolled/dragged - pixels: _pixelsForPhysics(position), + final scrollSimulation = position.physics + .createBallisticSimulation(scrollMetricsForScrollPhysics, velocity); + if (scrollSimulation != null) { + delegate.beginActivity( + _ContentBallisticScrollDrivenSheetActivity( + contentScrollPosition: position, + ), + ); + + return DelegationResult.handled( + _SheetContentBallisticScrollActivity( + delegate: position, + simulation: scrollSimulation, + vsync: position.context.vsync, + shouldIgnorePointer: shouldIgnorePointer, + initialPixels: pixelsForScrollPhysics, + ), ); + } + + return const DelegationResult.notHandled(); + } +} + +/// A [SheetActivity] that is driven by a single [ScrollPosition] +/// of the scrollable content within the sheet. +abstract class _SingleContentScrollDrivenSheetActivity + extends _ContentScrollDrivenSheetActivity { + _SingleContentScrollDrivenSheetActivity({ + required this.contentScrollPosition, + }); + + /// The [DelegatableScrollPosition] that drives this activity. + final DelegatableScrollPosition contentScrollPosition; double _applyPhysicsToOffset(double offset) { return delegate.physics.applyPhysicsToOffset(offset, delegate.metrics); } - @protected - double applyScrollOffset(double offset, SheetContentScrollPosition position) { + double _applyScrollOffset(double offset) { if (offset.isApprox(0)) return 0; + final position = contentScrollPosition; final maxPixels = delegate.maxPixels!; final oldPixels = pixels!; final oldScrollPixels = position.pixels; @@ -190,48 +280,24 @@ sealed class _ContentScrollDrivenSheetActivity extends SheetActivity } @override - DelegationResult goIdleScroll( - SheetContentScrollPosition position, + DelegationResult onContentGoIdle( + DelegatableScrollPosition position, ) { - delegate.goIdle(); - return super.goIdleScroll(position); + return identical(position, contentScrollPosition) + ? super.onContentGoIdle(position) + : const DelegationResult.notHandled(); } @override - DelegationResult goBallisticScroll( + DelegationResult onContentGoBallistic( double velocity, + // ignore: avoid_positional_boolean_parameters bool shouldIgnorePointer, - SheetContentScrollPosition position, + DelegatableScrollPosition position, ) { - if (!delegate.hasPixels) { - return const DelegationResult.notHandled(); - } - - if (position.pixels.isApprox(position.minScrollExtent)) { - final simulation = delegate.physics - .createBallisticSimulation(velocity, delegate.metrics); - if (simulation != null) { - delegate.goBallisticWith(simulation); - return DelegationResult.handled(IdleScrollActivity(position)); - } - } - - final scrollSimulation = position.physics.createBallisticSimulation( - _scrollMetricsForPhysics(position), velocity); - if (scrollSimulation != null) { - delegate.beginActivity(_ContentBallisticScrollDrivenSheetActivity()); - return DelegationResult.handled( - _SheetContentBallisticScrollActivity( - delegate: position, - simulation: scrollSimulation, - vsync: position.context.vsync, - shouldIgnorePointer: shouldIgnorePointer, - initialPixels: _pixelsForPhysics(position), - ), - ); - } - - return const DelegationResult.notHandled(); + return identical(position, contentScrollPosition) + ? super.onContentGoBallistic(velocity, shouldIgnorePointer, position) + : const DelegationResult.notHandled(); } } @@ -253,27 +319,26 @@ class _ContentIdleScrollDrivenSheetActivity setPixels(initialExtent.resolve(delegate.contentDimensions!)); } } - - @override - void onDragStart(DragStartDetails details) { - delegate.beginActivity(_ContentUserScrollDrivenSheetActivity()); - } } class _ContentUserScrollDrivenSheetActivity - extends _ContentScrollDrivenSheetActivity + extends _SingleContentScrollDrivenSheetActivity with UserControlledSheetActivityMixin { + _ContentUserScrollDrivenSheetActivity({ + required super.contentScrollPosition, + }); + @override - DelegationResult applyUserScrollOffset( + DelegationResult onApplyUserScrollOffsetToContent( double delta, - SheetContentScrollPosition position, + DelegatableScrollPosition position, ) { - if (!delegate.hasPixels) { + if (!delegate.hasPixels || !identical(position, contentScrollPosition)) { return const DelegationResult.notHandled(); } final oldPixels = pixels!; - applyScrollOffset(-1 * delta, position); + _applyScrollOffset(-1 * delta); if (pixels != oldPixels) { dispatchDragUpdateNotification(delta: pixels! - oldPixels); @@ -283,30 +348,34 @@ class _ContentUserScrollDrivenSheetActivity } @override - void onDragEnd() { - if (mounted) { + void onContentDragCancel(DelegatableScrollPosition position) { + if (identical(position, contentScrollPosition)) { delegate.goBallistic(0); } } } class _ContentBallisticScrollDrivenSheetActivity - extends _ContentScrollDrivenSheetActivity { + extends _SingleContentScrollDrivenSheetActivity { + _ContentBallisticScrollDrivenSheetActivity({ + required super.contentScrollPosition, + }); + @override SheetStatus get status => SheetStatus.controlled; @override - DelegationResult applyBallisticScrollOffset( + DelegationResult onApplyBallisticScrollOffsetToContent( double delta, double velocity, - SheetContentScrollPosition position, + DelegatableScrollPosition position, ) { - if (!delegate.hasPixels) { + if (!delegate.hasPixels || !identical(position, contentScrollPosition)) { return const DelegationResult.notHandled(); } final oldPixels = pixels!; - final overscroll = applyScrollOffset(delta, position); + final overscroll = _applyScrollOffset(delta); if (pixels != oldPixels) { dispatchUpdateNotification(); @@ -324,18 +393,15 @@ class _ContentBallisticScrollDrivenSheetActivity } @override - void onWillBallisticScrollCancel() { - delegate.goBallistic(0); - } - - @override - void onDragStart(DragStartDetails details) { - delegate.beginActivity(_ContentUserScrollDrivenSheetActivity()); + void onWillContentBallisticScrollCancel(DelegatableScrollPosition position) { + if (identical(position, contentScrollPosition)) { + delegate.goBallistic(0); + } } } class _DragInterruptibleBallisticSheetActivity extends BallisticSheetActivity - with SheetContentScrollPositionDelegate { + with ScrollPositionDelegate { _DragInterruptibleBallisticSheetActivity({ required super.simulation, }); @@ -347,15 +413,22 @@ class _DragInterruptibleBallisticSheetActivity extends BallisticSheetActivity } @override - void onDragStart(DragStartDetails details) { + void onContentDragStart( + DragStartDetails details, + DelegatableScrollPosition position, + ) { _cancelSimulation(); - delegate.beginActivity(_ContentUserScrollDrivenSheetActivity()); + delegate.beginActivity( + _ContentUserScrollDrivenSheetActivity( + contentScrollPosition: position, + ), + ); } } class _SheetContentBallisticScrollActivity extends BallisticScrollActivity { _SheetContentBallisticScrollActivity({ - required SheetContentScrollPosition delegate, + required DelegatableScrollPosition delegate, required Simulation simulation, required TickerProvider vsync, required double initialPixels, @@ -367,7 +440,7 @@ class _SheetContentBallisticScrollActivity extends BallisticScrollActivity { @override bool applyMoveTo(double pixels) { - final position = delegate as SheetContentScrollPosition; + final position = delegate as DelegatableScrollPosition; final delta = pixels - _oldPixels; final overscroll = position.applyBallisticOffset(delta, velocity); _oldPixels = pixels; diff --git a/package/lib/src/scrollable/scrollable_sheet_physics.dart b/package/lib/src/scrollable/scrollable_sheet_physics.dart index 7936865e..ff354886 100644 --- a/package/lib/src/scrollable/scrollable_sheet_physics.dart +++ b/package/lib/src/scrollable/scrollable_sheet_physics.dart @@ -1,12 +1,7 @@ import 'package:smooth_sheets/src/foundation/sheet_extent.dart'; import 'package:smooth_sheets/src/foundation/sheet_physics.dart'; -mixin ScrollableSheetPhysicsMixin on SheetPhysics { - bool shouldInterruptBallisticScroll(double velocity, SheetMetrics metrics); -} - -class ScrollableSheetPhysics extends SheetPhysics - with ScrollableSheetPhysicsMixin { +class ScrollableSheetPhysics extends SheetPhysics { const ScrollableSheetPhysics({ super.parent, super.spring, @@ -15,7 +10,6 @@ class ScrollableSheetPhysics extends SheetPhysics final double maxScrollSpeedToInterrupt; - @override bool shouldInterruptBallisticScroll(double velocity, SheetMetrics metrics) { return velocity.abs() < maxScrollSpeedToInterrupt; }