diff --git a/lib/common/widgets/refresh_indicator.dart b/lib/common/widgets/refresh_indicator.dart index 5ba208d2..97439312 100644 --- a/lib/common/widgets/refresh_indicator.dart +++ b/lib/common/widgets/refresh_indicator.dart @@ -12,7 +12,7 @@ Widget refreshIndicator({ return RefreshIndicator( displacement: displacement, onRefresh: onRefresh, - child: child, + child: (onCancelDrag) => child, ); } @@ -188,7 +188,7 @@ class RefreshIndicator extends StatefulWidget { /// will appear when child's Scrollable descendant is over-scrolled. /// /// Typically a [ListView] or [CustomScrollView]. - final Widget child; + final Widget Function(ValueChanged onCancelDrag) child; /// The distance from the child's top or bottom [edgeOffset] where /// the refresh indicator will settle. During the drag that exposes the refresh @@ -270,6 +270,8 @@ class RefreshIndicator extends StatefulWidget { RefreshIndicatorState createState() => RefreshIndicatorState(); } +bool isRefreshing = false; + /// Contains the state for a [RefreshIndicator]. This class can be used to /// programmatically show the refresh indicator, see the [show] method. class RefreshIndicatorState extends State @@ -368,6 +370,8 @@ class RefreshIndicatorState extends State _start(notification.metrics.axisDirection); } + double? containerExtent; + bool _handleScrollNotification(ScrollNotification notification) { if (!widget.notificationPredicate(notification)) { return false; @@ -443,6 +447,7 @@ class RefreshIndicatorState extends State return false; } if (_mode == _RefreshIndicatorMode.drag) { + isRefreshing = true; notification.disallowIndicator(); return true; } @@ -470,6 +475,7 @@ class RefreshIndicatorState extends State } void _checkDragOffset(double containerExtent) { + this.containerExtent ??= containerExtent; assert(_mode == _RefreshIndicatorMode.drag || _mode == _RefreshIndicatorMode.armed); double newValue = @@ -487,6 +493,7 @@ class RefreshIndicatorState extends State // Stop showing the refresh indicator. Future _dismiss(_RefreshIndicatorMode newMode) async { + isRefreshing = false; await Future.value(); // This can only be called from _show() when refreshing and // _handleScrollNotification in response to a ScrollEndNotification or @@ -579,7 +586,10 @@ class RefreshIndicatorState extends State onNotification: _handleScrollNotification, child: NotificationListener( onNotification: _handleIndicatorNotification, - child: widget.child, + child: widget.child((delta) { + _dragOffset = _dragOffset! + delta; + _checkDragOffset(containerExtent!); + }), ), ); assert(() { diff --git a/lib/common/widgets/spring_physics.dart b/lib/common/widgets/spring_physics.dart index 16b4b2e3..c0883fda 100644 --- a/lib/common/widgets/spring_physics.dart +++ b/lib/common/widgets/spring_physics.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:flutter/material.dart'; @@ -52,3 +53,41 @@ class CustomTabBarViewClampingScrollPhysics extends ClampingScrollPhysics { damping: GStorage.springDescription[2], ); } + +class CustomScrollPosition extends ScrollPositionWithSingleContext { + CustomScrollPosition({ + required super.physics, + required super.context, + required this.onCancelDrag, + }); + + final ValueChanged onCancelDrag; + + @override + void applyUserOffset(double delta) { + if (isRefreshing && delta < 0) { + onCancelDrag(delta); + return; + } + super.applyUserOffset(delta); + } +} + +class CustomScrollController extends ScrollController { + CustomScrollController(this.onCancelDrag); + + final ValueChanged onCancelDrag; + + @override + CustomScrollPosition createScrollPosition( + ScrollPhysics physics, + ScrollContext context, + ScrollPosition? oldPosition, + ) { + return CustomScrollPosition( + physics: physics.applyTo(const AlwaysScrollableScrollPhysics()), + context: context, + onCancelDrag: onCancelDrag, + ); + } +} diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index 0eca275a..a022e840 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -1,8 +1,9 @@ import 'dart:async'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; +import 'package:PiliPlus/common/widgets/spring_physics.dart'; import 'package:PiliPlus/http/loading_state.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide RefreshIndicator; import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/common/constants.dart'; @@ -67,12 +68,12 @@ class _RcmdPageState extends State decoration: BoxDecoration( borderRadius: StyleString.mdRadius, ), - child: refreshIndicator( + child: RefreshIndicator( onRefresh: () async { await _controller.onRefresh(); }, - child: CustomScrollView( - controller: _controller.scrollController, + child: (onCancelDrag) => CustomScrollView( + controller: CustomScrollController(onCancelDrag), physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverPadding(