From 91b033b387fa433a15cd1d01deb5d45b4a171516 Mon Sep 17 00:00:00 2001 From: Alex Rintt Date: Sat, 3 Dec 2022 01:05:46 -0300 Subject: [PATCH] (#25) Add autoscroll on drag bottom and top --- lib/widgets/packages_list.dart | 128 +++++++++++++++++++++------------ 1 file changed, 81 insertions(+), 47 deletions(-) diff --git a/lib/widgets/packages_list.dart b/lib/widgets/packages_list.dart index 9bf83e8..51d97b4 100644 --- a/lib/widgets/packages_list.dart +++ b/lib/widgets/packages_list.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:math'; import 'dart:ui'; @@ -115,6 +116,9 @@ class _PackagesListState extends State void initState() { super.initState(); _scrollController = ScrollController(); + _scrollController.addListener(() { + if (menuStore.context.isSelection) _onSelectionGesture(); + }); } @override @@ -152,10 +156,12 @@ class _PackagesListState extends State if (_globalInitialDragPosition == null) return; - final dragStartGlobalPosition = _globalInitialDragPosition!; + final dragStartGlobalPosition = + _globalInitialDragPosition! + _scrollControllerOffset; final dragFinalGlobalPosition = - _globalFinalDragPosition ?? _globalInitialDragPosition!; + (_globalFinalDragPosition ?? _globalInitialDragPosition!) + + _scrollControllerOffset; final pointerRect = _rectFromPoints(dragStartGlobalPosition, dragFinalGlobalPosition); @@ -201,68 +207,92 @@ class _PackagesListState extends State } } - var _autoscrolling = false; + Offset get _scrollControllerOffset => Offset(0, _scrollController.offset); - void _endAutoscrollFeature() { - _autoscrolling = false; - } + double get _maxScrollExtent => _scrollController.position.maxScrollExtent; + double get _minScrollExtent => _scrollController.position.minScrollExtent; - Future _startAutoscrollFeature() async { - var _autoscrolling = true; + // var _acceleration = 0.01; + var _velocity = 4.0; + // static const _kInitialAutoscrollVelocity = 2.0; + + double get _pixelRatio => window.devicePixelRatio; + Size get _logicalScreenSize => window.physicalSize / _pixelRatio; + double get _logicalHeight => _logicalScreenSize.height; - Offset? pointer() => _globalFinalDragPosition ?? _globalInitialDragPosition; - bool hasPointer() => pointer() != null; + void _autoscrollListener() { + _velocity = _acceleration * _logicalHeight / 10; - if (pointer() == null) return; + final step = + _autoscrollDirection == AxisDirection.up ? -_velocity : _velocity; - final pixelRatio = window.devicePixelRatio; + final newOffset = (_scrollControllerOffset.dy + step) + .clamp(_minScrollExtent, _maxScrollExtent); - final logicalScreenSize = window.physicalSize / pixelRatio; - final logicalHeight = logicalScreenSize.height; + _scrollController.jumpTo(newOffset); + } + + StreamSubscription? _autoscrollStreamSubscription; - const kAutoscrollArea = kToolbarHeight * 3; + Future _startAutoscrolling() async { + const kFps = 60; + const k1s = 1000; - bool shouldAutoscrollToTop() => - hasPointer() ? pointer()!.dy <= kAutoscrollArea : false; + _autoscrollStreamSubscription = + Stream.periodic(const Duration(milliseconds: k1s ~/ kFps)) + .listen((_) => _autoscrollListener()); + } - bool shouldAutoscrollToBottom() => - hasPointer() ? pointer()!.dy >= logicalHeight - kAutoscrollArea : false; + Future _stopAutoscroll() async { + _autoscrollDirection = null; + _autoscrollStreamSubscription?.cancel(); + } - double topDiffInPixels() => - hasPointer() ? (kAutoscrollArea - pointer()!.dy).abs() : 0; + void _endAutoscrollFeature() { + _calcAutoscrollDirectionAndForce(); + _stopAutoscroll(); + } - double bottomDiffInPixels() => hasPointer() - ? (pointer()!.dy - logicalHeight - kAutoscrollArea).abs() - : 0; + void _updateAutoscrollFeature() { + _calcAutoscrollDirectionAndForce(); + } - double topDiff() => (topDiffInPixels() / kAutoscrollArea).clamp(0, 1); - double bottomDiff() => (bottomDiffInPixels() / kAutoscrollArea).clamp(0, 1); + Future _startAutoscrollFeature() async { + _calcAutoscrollDirectionAndForce(); + _startAutoscrolling(); + } - if (shouldAutoscrollToTop() || shouldAutoscrollToBottom()) { - final autoscrollStep = - (shouldAutoscrollToTop() ? topDiff() : bottomDiff()) * 300 + 10; + AxisDirection? _autoscrollDirection; + var _acceleration = 0.0; + void _calcAutoscrollDirectionAndForce() { + final pointer = _globalFinalDragPosition ?? _globalInitialDragPosition; + final hasPointer = pointer != null; - final autoscrollDuration = Duration(milliseconds: autoscrollStep ~/ 1); + const kAutoscrollArea = kToolbarHeight * 2; - _autoscrolling = true; + final topDiff = hasPointer ? 1 - pointer!.dy / kAutoscrollArea : 0.0; + final bottomDiff = hasPointer + ? 1 - (pointer!.dy - _logicalHeight).abs() / kAutoscrollArea + : 0.0; - final current = _scrollControllerOffset.dy; + final shouldAutoscrollToTop = + hasPointer ? pointer!.dy <= kAutoscrollArea : false; - final newOffset = shouldAutoscrollToTop() ? current - 1 : current + 1; + final shouldAutoscrollToBottom = + hasPointer ? pointer!.dy >= _logicalHeight - kAutoscrollArea : false; - _scrollController.jumpTo( - newOffset.clamp( - _scrollController.position.minScrollExtent, - _scrollController.position.maxScrollExtent, - ), - ); - } else if (!hasPointer()) { - _scrollController.jumpTo(_scrollControllerOffset.dy); + if (shouldAutoscrollToTop) { + _autoscrollDirection = AxisDirection.up; + _acceleration = topDiff; + } else if (shouldAutoscrollToBottom) { + _autoscrollDirection = AxisDirection.down; + _acceleration = bottomDiff; + } else { + _autoscrollDirection = null; + _acceleration = 0.0; } } - Offset get _scrollControllerOffset => Offset(0, _scrollController.offset); - @override Widget build(BuildContext context) { return WillPopScope( @@ -286,21 +316,25 @@ class _PackagesListState extends State animations: [store, menuStore], builder: (context, child) => GestureDetector( onLongPressStart: (details) { - _globalInitialDragPosition = - details.globalPosition + _scrollControllerOffset; + _globalInitialDragPosition = details.globalPosition; _startAutoscrollFeature(); }, onLongPressMoveUpdate: (details) { - _globalFinalDragPosition = - details.globalPosition + _scrollControllerOffset; + _globalFinalDragPosition = details.globalPosition; _onSelectionGesture(); + _updateAutoscrollFeature(); }, onLongPressEnd: (details) { _globalInitialDragPosition = null; _globalFinalDragPosition = null; _endAutoscrollFeature(); }, + onLongPressCancel: () { + _globalInitialDragPosition = null; + _globalFinalDragPosition = null; + _endAutoscrollFeature(); + }, onLongPress: () { _onSelectionGesture(); },