Skip to content

Commit

Permalink
(#25) Add autoscroll on drag bottom and top
Browse files Browse the repository at this point in the history
  • Loading branch information
alexcmgit committed Dec 3, 2022
1 parent c82766b commit 91b033b
Showing 1 changed file with 81 additions and 47 deletions.
128 changes: 81 additions & 47 deletions lib/widgets/packages_list.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:math';
import 'dart:ui';

Expand Down Expand Up @@ -115,6 +116,9 @@ class _PackagesListState extends State<PackagesList>
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(() {
if (menuStore.context.isSelection) _onSelectionGesture();
});
}

@override
Expand Down Expand Up @@ -152,10 +156,12 @@ class _PackagesListState extends State<PackagesList>

if (_globalInitialDragPosition == null) return;

final dragStartGlobalPosition = _globalInitialDragPosition!;
final dragStartGlobalPosition =
_globalInitialDragPosition! + _scrollControllerOffset;

final dragFinalGlobalPosition =
_globalFinalDragPosition ?? _globalInitialDragPosition!;
(_globalFinalDragPosition ?? _globalInitialDragPosition!) +
_scrollControllerOffset;

final pointerRect =
_rectFromPoints(dragStartGlobalPosition, dragFinalGlobalPosition);
Expand Down Expand Up @@ -201,68 +207,92 @@ class _PackagesListState extends State<PackagesList>
}
}

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<void> _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<void>? _autoscrollStreamSubscription;

const kAutoscrollArea = kToolbarHeight * 3;
Future<void> _startAutoscrolling() async {
const kFps = 60;
const k1s = 1000;

bool shouldAutoscrollToTop() =>
hasPointer() ? pointer()!.dy <= kAutoscrollArea : false;
_autoscrollStreamSubscription =
Stream<void>.periodic(const Duration(milliseconds: k1s ~/ kFps))
.listen((_) => _autoscrollListener());
}

bool shouldAutoscrollToBottom() =>
hasPointer() ? pointer()!.dy >= logicalHeight - kAutoscrollArea : false;
Future<void> _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<void> _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(
Expand All @@ -286,21 +316,25 @@ class _PackagesListState extends State<PackagesList>
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();
},
Expand Down

0 comments on commit 91b033b

Please sign in to comment.