Skip to content

Commit

Permalink
[SuperEditor][SuperReader][Android] Fix scroll physics (Resolves #1539)…
Browse files Browse the repository at this point in the history
… (#1541)
  • Loading branch information
angelosilvestre committed Nov 22, 2023
1 parent 7b7b237 commit 3d0f3d3
Show file tree
Hide file tree
Showing 9 changed files with 785 additions and 141 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt
double? _dragStartScrollOffset;
Offset? _globalDragOffset;
Offset? _dragEndInInteractor;

/// Holds the drag gesture that scrolls the document.
Drag? _scrollingDrag;

SelectionHandleType? _selectionType;

Timer? _tapDownLongPressTimer;
Expand Down Expand Up @@ -744,6 +748,11 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt

if (!_isLongPressInProgress) {
// We only care about starting a pan if we're long-press dragging.
_scrollingDrag = scrollPosition.drag(details, () {
// Allows receiving touches while scrolling due to scroll momentum.
// This is needed to allow the user to stop scrolling by tapping down.
scrollPosition.context.setIgnorePointer(false);
});
return;
}

Expand Down Expand Up @@ -788,8 +797,10 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt
return;
}

// The user is trying to scroll the document. Change the scroll offset.
scrollPosition.jumpTo(scrollPosition.pixels - details.delta.dy);
if (_scrollingDrag != null) {
// The user is trying to scroll the document. Change the scroll offset.
_scrollingDrag!.update(details);
}
}

void _updateLongPressSelection(DocumentSelection newSelection) {
Expand Down Expand Up @@ -820,10 +831,10 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt
return;
}

final pos = scrollPosition;
if (pos is ScrollPositionWithSingleContext) {
pos.goBallistic(-details.velocity.pixelsPerSecond.dy);
pos.context.setIgnorePointer(false);
if (_scrollingDrag != null) {
// The user was performing a drag gesture to scroll the document.
// End the scroll activity and let the document scrolling with momentum.
_scrollingDrag!.end(details);
}
}

Expand All @@ -832,6 +843,12 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt
_onLongPressEnd();
return;
}

if (_scrollingDrag != null) {
// The user was performing a drag gesture to scroll the document.
// End the drag gesture.
_scrollingDrag!.cancel();
}
}

void _onLongPressEnd() {
Expand Down Expand Up @@ -1255,10 +1272,11 @@ class _AndroidDocumentTouchInteractorState extends State<AndroidDocumentTouchInt
..gestureSettings = gestureSettings;
},
),
PanGestureRecognizer: GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
() => PanGestureRecognizer(),
(PanGestureRecognizer recognizer) {
VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer(),
(VerticalDragGestureRecognizer recognizer) {
recognizer
..dragStartBehavior = DragStartBehavior.down
..onStart = _onPanStart
..onUpdate = _onPanUpdate
..onEnd = _onPanEnd
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ class _IosDocumentTouchInteractorState extends State<IosDocumentTouchInteractor>
// not collapsed/upstream/downstream. Change the type once it's working.
HandleType? _dragHandleType;

/// Holds the drag gesture that scrolls the document.
Drag? _scrollingDrag;

Timer? _tapDownLongPressTimer;
Offset? _globalTapDownOffset;
bool get _isLongPressInProgress => _longPressStrategy != null;
Expand Down Expand Up @@ -814,6 +817,8 @@ class _IosDocumentTouchInteractorState extends State<IosDocumentTouchInteractor>
// bit of slop might be the problem.
final selection = widget.selection.value;
if (selection == null) {
// There isn't a selection, the user is dragging to scroll the document.
_startDragScrolling(details);
return;
}

Expand All @@ -831,6 +836,10 @@ class _IosDocumentTouchInteractorState extends State<IosDocumentTouchInteractor>
_dragMode = DragMode.extent;
_dragHandleType = HandleType.downstream;
} else {
// The user isn't dragging over a handle.
// Start scrolling the document.
_startDragScrolling(details);

return;
}

Expand Down Expand Up @@ -914,10 +923,9 @@ class _IosDocumentTouchInteractorState extends State<IosDocumentTouchInteractor>
}

void _onPanUpdate(DragUpdateDetails details) {
// If the user isn't dragging a handle, then the user is trying to
// scroll the document. Scroll it, accordingly.
if (_dragMode == null) {
scrollPosition.jumpTo(scrollPosition.pixels - details.delta.dy);
if (_dragMode == DragMode.scroll) {
// The user is trying to scroll the document. Scroll it, accordingly.
_scrollingDrag!.update(details);
return;
}

Expand Down Expand Up @@ -995,27 +1003,39 @@ class _IosDocumentTouchInteractorState extends State<IosDocumentTouchInteractor>
..hideMagnifier()
..blinkCaret();

if (_dragMode == null) {
// User was dragging the scroll area. Go ballistic.
if (scrollPosition is ScrollPositionWithSingleContext) {
(scrollPosition as ScrollPositionWithSingleContext).goBallistic(-details.velocity.pixelsPerSecond.dy);

if (_activeScrollPosition != scrollPosition) {
// We add the scroll change listener again, because going ballistic
// seems to switch out the scroll position.
_activeScrollPosition = scrollPosition;
}
}
} else {
// The user was dragging a selection change in some way, either with handles
// or with a long-press. Finish that interaction.
_onDragSelectionEnd();
switch (_dragMode) {
case DragMode.scroll:
// The user was performing a drag gesture to scroll the document.
// End the scroll activity and let the document scrolling with momentum.
_scrollingDrag!.end(details);
_scrollingDrag = null;
_dragMode = null;
break;
case DragMode.collapsed:
case DragMode.base:
case DragMode.extent:
case DragMode.longPress:
// The user was dragging a selection change in some way, either with handles
// or with a long-press. Finish that interaction.
_onDragSelectionEnd();
break;
case null:
// The user wasn't dragging over a selection. Do nothing.
break;
}
}

void _onPanCancel() {
_magnifierOffset.value = null;

if (_scrollingDrag != null) {
// The user was performing a drag gesture to scroll the document.
// Cancel the drag gesture.
_scrollingDrag!.cancel();
_scrollingDrag = null;
return;
}

if (_dragMode != null) {
_onDragSelectionEnd();
}
Expand Down Expand Up @@ -1212,6 +1232,17 @@ class _IosDocumentTouchInteractorState extends State<IosDocumentTouchInteractor>
return ancestorScrollable;
}

/// Starts a drag activity to scroll the document.
void _startDragScrolling(DragStartDetails details) {
_dragMode = DragMode.scroll;

_scrollingDrag = scrollPosition.drag(details, () {
// Allows receiving touches while scrolling due to scroll momentum.
// This is needed to allow the user to stop scrolling by tapping down.
scrollPosition.context.setIgnorePointer(false);
});
}

@override
Widget build(BuildContext context) {
if (widget.scrollController.hasClients) {
Expand Down Expand Up @@ -1304,6 +1335,8 @@ enum DragMode {
// Dragging after a long-press, which selects by the word
// around the selected word.
longPress,
// Dragging to scroll the document.
scroll
}

/// Adds and removes an iOS-style editor toolbar, as dictated by an ancestor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State<ReadOnlyAndroid
AndroidDocumentLongPressSelectionStrategy? _longPressStrategy;
final _longPressMagnifierGlobalOffset = ValueNotifier<Offset?>(null);

/// Holds the drag gesture that scrolls the document.
Drag? _scrollingDrag;

@override
void initState() {
super.initState();
Expand Down Expand Up @@ -676,6 +679,11 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State<ReadOnlyAndroid

if (!_isLongPressInProgress) {
// We only care about starting a pan if we're long-press dragging.
_scrollingDrag = scrollPosition.drag(details, () {
// Allows receiving touches while scrolling due to scroll momentum.
// This is needed to allow the user to stop scrolling by tapping down.
scrollPosition.context.setIgnorePointer(false);
});
return;
}

Expand Down Expand Up @@ -720,7 +728,10 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State<ReadOnlyAndroid
return;
}

scrollPosition.jumpTo(scrollPosition.pixels - details.delta.dy);
if (_scrollingDrag != null) {
// The user is trying to scroll the document. Change the scroll offset.
_scrollingDrag!.update(details);
}
}

void _updateLongPressSelection(DocumentSelection newSelection) {
Expand Down Expand Up @@ -751,10 +762,10 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State<ReadOnlyAndroid
return;
}

final pos = scrollPosition;
if (pos is ScrollPositionWithSingleContext) {
pos.goBallistic(-details.velocity.pixelsPerSecond.dy);
pos.context.setIgnorePointer(false);
if (_scrollingDrag != null) {
// The user was performing a drag gesture to scroll the document.
// End the scroll activity and let the document scrolling with momentum.
_scrollingDrag!.end(details);
}
}

Expand All @@ -763,6 +774,12 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State<ReadOnlyAndroid
_onLongPressEnd();
return;
}

if (_scrollingDrag != null) {
// The user was performing a drag gesture to scroll the document.
// Cancel the drag gesture.
_scrollingDrag!.cancel();
}
}

void _onLongPressEnd() {
Expand Down Expand Up @@ -1044,10 +1061,11 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State<ReadOnlyAndroid
..gestureSettings = gestureSettings;
},
),
PanGestureRecognizer: GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
() => PanGestureRecognizer(),
(PanGestureRecognizer recognizer) {
VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer(),
(VerticalDragGestureRecognizer recognizer) {
recognizer
..dragStartBehavior = DragStartBehavior.down
..onStart = _onPanStart
..onUpdate = _onPanUpdate
..onEnd = _onPanEnd
Expand Down
Loading

0 comments on commit 3d0f3d3

Please sign in to comment.