diff --git a/package/lib/smooth_sheets.dart b/package/lib/smooth_sheets.dart index 2d523d62..5d1137fb 100644 --- a/package/lib/smooth_sheets.dart +++ b/package/lib/smooth_sheets.dart @@ -8,7 +8,7 @@ export 'src/draggable/sheet_draggable.dart'; export 'src/foundation/animation.dart'; export 'src/foundation/framework.dart'; export 'src/foundation/keyboard_dismissible.dart'; -export 'src/foundation/notification.dart'; +export 'src/foundation/notifications.dart'; export 'src/foundation/sheet_activity.dart'; export 'src/foundation/sheet_content_scaffold.dart'; export 'src/foundation/sheet_controller.dart' hide SheetControllerScope; diff --git a/package/lib/src/draggable/sheet_draggable.dart b/package/lib/src/draggable/sheet_draggable.dart index 16e5ab3e..0c361122 100644 --- a/package/lib/src/draggable/sheet_draggable.dart +++ b/package/lib/src/draggable/sheet_draggable.dart @@ -1,3 +1,5 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:smooth_sheets/src/draggable/draggable_sheet.dart'; import 'package:smooth_sheets/src/foundation/sheet_activity.dart'; @@ -39,7 +41,7 @@ class SheetDraggable extends StatefulWidget { class _SheetDraggableState extends State { SheetExtent? _extent; - UserDragSheetActivity? _activity; + VerticalDragGestureRecognizer? _dragRecognizer; @override void didChangeDependencies() { @@ -50,66 +52,39 @@ class _SheetDraggableState extends State { @override void dispose() { _extent = null; + _dragRecognizer = null; super.dispose(); } void _handleDragStart(DragStartDetails details) { - assert(_activity == null); - if (_extent != null) { - _activity = UserDragSheetActivity(); - _extent!.beginActivity(_activity!); + final extent = _extent; + final recognizer = _dragRecognizer; + if (extent != null && recognizer != null) { + extent.activity.dispatchDragStartNotification(details); + extent.beginActivity( + UserDragSheetActivity( + gestureRecognizer: recognizer, + ), + ); } } - void _handleDragUpdate(DragUpdateDetails details) { - _activity?.onDragUpdate(details); - } - - void _handleDragEnd(DragEndDetails details) { - _activity?.onDragEnd(details); - _activity = null; - } - - void _handleDragCancel() { - _activity?.onDragCancel(); - _activity = null; - } - @override Widget build(BuildContext context) { - return GestureDetector( - behavior: widget.behavior, - onVerticalDragCancel: _handleDragCancel, - onVerticalDragStart: _handleDragStart, - onVerticalDragUpdate: _handleDragUpdate, - onVerticalDragEnd: _handleDragEnd, + return RawGestureDetector( + gestures: { + VerticalDragGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + () => VerticalDragGestureRecognizer( + debugOwner: kDebugMode ? runtimeType : null, + supportedDevices: const {PointerDeviceKind.touch}, + ), + (instance) { + _dragRecognizer = instance..onStart = _handleDragStart; + }, + ), + }, child: widget.child, ); } } - -// TODO: Move this class to sheet_activity.dart -// TODO: Add constructor with `DragGestureRecognizer` parameter -class UserDragSheetActivity extends SheetActivity - with UserControlledSheetActivityMixin { - void onDragUpdate(DragUpdateDetails details) { - if (!mounted) return; - final delta = -1 * details.primaryDelta!; - final physicsAppliedDelta = - delegate.physics.applyPhysicsToOffset(delta, delegate.metrics); - if (physicsAppliedDelta != 0) { - setPixels(pixels! + physicsAppliedDelta); - dispatchDragUpdateNotification(delta: physicsAppliedDelta); - } - } - - void onDragEnd(DragEndDetails details) { - if (!mounted) return; - delegate.goBallistic(-1 * details.velocity.pixelsPerSecond.dy); - } - - void onDragCancel() { - if (!mounted) return; - delegate.goBallistic(0); - } -} diff --git a/package/lib/src/foundation/keyboard_dismissible.dart b/package/lib/src/foundation/keyboard_dismissible.dart index b815d015..7fa656bd 100644 --- a/package/lib/src/foundation/keyboard_dismissible.dart +++ b/package/lib/src/foundation/keyboard_dismissible.dart @@ -1,6 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:smooth_sheets/src/draggable/draggable_sheet.dart'; -import 'package:smooth_sheets/src/foundation/notification.dart'; +import 'package:smooth_sheets/src/foundation/notifications.dart'; /// A widget that dismisses the on-screen keyboard when the user /// drags the sheet below this widget. diff --git a/package/lib/src/foundation/notification.dart b/package/lib/src/foundation/notifications.dart similarity index 57% rename from package/lib/src/foundation/notification.dart rename to package/lib/src/foundation/notifications.dart index da98756f..9219f044 100644 --- a/package/lib/src/foundation/notification.dart +++ b/package/lib/src/foundation/notifications.dart @@ -5,14 +5,20 @@ import 'package:smooth_sheets/src/foundation/sheet_physics.dart'; /// A [Notification] that is dispatched when the sheet extent changes. /// /// Sheet widgets notify their ancestors about changes to their extent. -/// There are 3 types of notifications: -/// - [SheetDragUpdateNotification], which is dispatched when the sheet -/// is dragged. +/// There are 6 types of notifications: /// - [SheetOverflowNotification], which is dispatched when the user tries /// to drag the sheet beyond its draggable bounds but the sheet has not /// changed its extent because its [SheetPhysics] does not allow it to be. /// - [SheetUpdateNotification], which is dispatched when the sheet extent /// is updated by other than user interaction such as animation. +/// - [SheetDragUpdateNotification], which is dispatched when the sheet +/// is dragged. +/// - [SheetDragStartNotification], which is dispatched when the user starts +/// dragging the sheet. +/// - [SheetDragEndNotification], which is dispatched when the user stops +/// dragging the sheet. +/// - [SheetDragCancelNotification], which is dispatched when the user cancels +/// dragging the sheet. /// /// See also: /// - [NotificationListener], which can be used to listen for notifications @@ -58,9 +64,57 @@ class SheetDragUpdateNotification extends SheetNotification { } } +/// A [SheetNotification] that is dispatched when the user starts +/// dragging the sheet. +class SheetDragStartNotification extends SheetNotification { + /// Create a notification that is dispatched when the user + /// starts dragging the sheet. + const SheetDragStartNotification({ + required super.metrics, + required this.dragDetails, + }); + + /// The details of the drag start. + final DragStartDetails dragDetails; + + @override + void debugFillDescription(List description) { + super.debugFillDescription(description); + description.add('dragDetails: $dragDetails'); + } +} + +/// A [SheetNotification] that is dispatched when the user stops +/// dragging the sheet. +class SheetDragEndNotification extends SheetNotification { + /// Create a notification that is dispatched when the user + /// stops dragging the sheet. + const SheetDragEndNotification({ + required super.metrics, + required this.dragDetails, + }); + + /// The details of the drag end. + final DragEndDetails dragDetails; + + @override + void debugFillDescription(List description) { + super.debugFillDescription(description); + description.add('dragDetails: $dragDetails'); + } +} + +/// A [SheetNotification] that is dispatched when the user cancels +/// dragging the sheet. +class SheetDragCancelNotification extends SheetNotification { + /// Create a notification that is dispatched when the user + /// cancels dragging the sheet. + const SheetDragCancelNotification({required super.metrics}); +} + /// A [SheetNotification] that is dispatched when the user tries -/// to drag the sheet beyond its draggable bounds but the sheet has not -/// changed its extent because its [SheetPhysics] does not allow it to be. +/// to drag the sheet beyond its draggable bounds but the sheet has not +/// changed its extent because its [SheetPhysics] does not allow it to be. class SheetOverflowNotification extends SheetNotification { const SheetOverflowNotification({ required super.metrics, diff --git a/package/lib/src/foundation/sheet_activity.dart b/package/lib/src/foundation/sheet_activity.dart index 9cd93341..f085b6da 100644 --- a/package/lib/src/foundation/sheet_activity.dart +++ b/package/lib/src/foundation/sheet_activity.dart @@ -1,10 +1,10 @@ import 'dart:async'; import 'dart:math'; +import 'package:flutter/gestures.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; -import 'package:smooth_sheets/src/foundation/notification.dart'; -import 'package:smooth_sheets/src/foundation/sheet_extent.dart'; +import 'package:smooth_sheets/smooth_sheets.dart'; import 'package:smooth_sheets/src/foundation/sheet_status.dart'; abstract class SheetActivity extends ChangeNotifier { @@ -61,7 +61,31 @@ abstract class SheetActivity extends ChangeNotifier { void dispatchUpdateNotification() { if (delegate.hasPixels) { dispatchNotification( - SheetUpdateNotification(metrics: delegate.snapshot), + SheetUpdateNotification( + metrics: delegate.snapshot, + ), + ); + } + } + + void dispatchDragStartNotification(DragStartDetails details) { + if (delegate.hasPixels) { + dispatchNotification( + SheetDragStartNotification( + metrics: delegate.snapshot, + dragDetails: details, + ), + ); + } + } + + void dispatchDragEndNotification(DragEndDetails details) { + if (delegate.hasPixels) { + dispatchNotification( + SheetDragEndNotification( + metrics: delegate.snapshot, + dragDetails: details, + ), ); } } @@ -77,6 +101,16 @@ abstract class SheetActivity extends ChangeNotifier { } } + void dispatchDragCancelNotification() { + if (delegate.hasPixels) { + dispatchNotification( + SheetDragCancelNotification( + metrics: delegate.snapshot, + ), + ); + } + } + void dispatchOverflowNotification(double overflow) { if (delegate.hasPixels) { dispatchNotification( @@ -209,6 +243,59 @@ class IdleSheetActivity extends SheetActivity { SheetStatus get status => SheetStatus.stable; } +class UserDragSheetActivity extends SheetActivity + with UserControlledSheetActivityMixin { + UserDragSheetActivity({ + required this.gestureRecognizer, + }); + + final DragGestureRecognizer gestureRecognizer; + + @override + void initWith(SheetExtent delegate) { + super.initWith(delegate); + gestureRecognizer + ..onUpdate = onDragUpdate + ..onEnd = onDragEnd + ..onCancel = onDragCancel; + } + + @override + void dispose() { + super.dispose(); + gestureRecognizer + ..onUpdate = null + ..onEnd = null + ..onCancel = null; + } + + @protected + void onDragUpdate(DragUpdateDetails details) { + if (!mounted) return; + final delta = -1 * details.primaryDelta!; + final physicsAppliedDelta = + delegate.physics.applyPhysicsToOffset(delta, delegate.metrics); + if (physicsAppliedDelta != 0) { + setPixels(pixels! + physicsAppliedDelta); + dispatchDragUpdateNotification(delta: physicsAppliedDelta); + } + } + + @protected + void onDragEnd(DragEndDetails details) { + if (!mounted) return; + dispatchDragEndNotification(details); + delegate.goBallistic(-1 * details.velocity.pixelsPerSecond.dy); + } + + @protected + void onDragCancel() { + if (!mounted) return; + dispatchDragCancelNotification(); + delegate.goBallistic(0); + } +} + mixin ControlledSheetActivityMixin on SheetActivity { late final AnimationController controller; late double _lastAnimatedValue; diff --git a/package/lib/src/scrollable/scrollable_sheet_extent.dart b/package/lib/src/scrollable/scrollable_sheet_extent.dart index 1c41597e..322b07a8 100644 --- a/package/lib/src/scrollable/scrollable_sheet_extent.dart +++ b/package/lib/src/scrollable/scrollable_sheet_extent.dart @@ -108,6 +108,7 @@ class SheetContentScrollController extends ScrollController { /// of the scrollable content within the sheet. abstract class _ContentScrollDrivenSheetActivity extends SheetActivity with ScrollPositionDelegate { + @mustCallSuper @override void onContentDragStart( DragStartDetails details, @@ -118,6 +119,23 @@ abstract class _ContentScrollDrivenSheetActivity extends SheetActivity contentScrollPosition: position, ), ); + + dispatchDragStartNotification(details); + } + + @mustCallSuper + @override + void onContentDragEnd( + DragEndDetails details, + DelegatableScrollPosition position, + ) { + dispatchDragEndNotification(details); + } + + @mustCallSuper + @override + void onContentDragCancel(DelegatableScrollPosition position) { + dispatchDragCancelNotification(); } @override @@ -349,6 +367,7 @@ class _ContentUserScrollDrivenSheetActivity @override void onContentDragCancel(DelegatableScrollPosition position) { + super.onContentDragCancel(position); if (identical(position, contentScrollPosition)) { delegate.goBallistic(0); }