fix: long press auto-scroll speed on mobile (#1191)

* fix: long press auto-scroll speed on mobile

When long pressing and dragging on mobile, auto-scroll was too fast
because cursor drag mode used Duration.zero (instant) instead of
animated scrolling, and the proxy rect was a large centered square
triggering in both directions.

- Add AxisDirection.down handling in auto_scroller for directional
  proxy rects extending downward from cursor position
- Add cursor drag mode case in scroll_service_widget with direction
  detection based on cursor movement relative to last selection
- Include cursor mode in isDragOperation for animated scroll duration
- Pass dragMode in Android long press selectionExtraInfo so scroll
  service can detect cursor drag on Android

* fix: correct auto-scroller test for downward scroll rect
This commit is contained in:
Lucas 2026-03-24 16:01:47 +08:00 • committed by GitHub
commit 99e7e1efa2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 30 additions and 7 deletions

View file

@ -50,6 +50,13 @@ class AutoScroller extends EdgeDraggingAutoScroller
); );
} }
if (direction != null && direction == AxisDirection.down) {
return startAutoScrollIfNecessary(
Rect.fromLTWH(offset.dx, offset.dy, 1, edgeOffset),
duration: duration,
);
}
final dragTarget = Rect.fromCenter( final dragTarget = Rect.fromCenter(
center: offset, center: offset,
width: edgeOffset, width: edgeOffset,

View file

@ -121,6 +121,16 @@ class _ScrollServiceWidgetState extends State<ScrollServiceWidget>
direction = AxisDirection.down; direction = AxisDirection.down;
break; break;
case 'MobileSelectionDragMode.cursor':
targetRect = selectionRects.last;
if (lastSelection != null) {
final isMovingUp = selection.end.path < lastSelection!.end.path ||
(selection.end.path.equals(lastSelection!.end.path) &&
selection.end.offset < lastSelection!.end.offset);
direction = isMovingUp ? AxisDirection.up : AxisDirection.down;
}
break;
default: default:
targetRect = selectionRects.last; targetRect = selectionRects.last;
@ -146,7 +156,8 @@ class _ScrollServiceWidgetState extends State<ScrollServiceWidget>
(dragMode.toString() == (dragMode.toString() ==
'MobileSelectionDragMode.leftSelectionHandle' || 'MobileSelectionDragMode.leftSelectionHandle' ||
dragMode.toString() == dragMode.toString() ==
'MobileSelectionDragMode.rightSelectionHandle'); 'MobileSelectionDragMode.rightSelectionHandle' ||
dragMode.toString() == 'MobileSelectionDragMode.cursor');
// Use animation for drag operations, instant for others // Use animation for drag operations, instant for others
final scrollDuration = final scrollDuration =

View file

@ -787,6 +787,7 @@ class _MobileSelectionServiceWidgetState
selection, selection,
reason: SelectionUpdateReason.uiEvent, reason: SelectionUpdateReason.uiEvent,
extraInfo: { extraInfo: {
selectionDragModeKey: dragMode,
selectionExtraInfoDisableFloatingToolbar: true, selectionExtraInfoDisableFloatingToolbar: true,
}, },
); );
@ -835,6 +836,7 @@ class _MobileSelectionServiceWidgetState
newSelection, newSelection,
reason: SelectionUpdateReason.uiEvent, reason: SelectionUpdateReason.uiEvent,
extraInfo: { extraInfo: {
selectionDragModeKey: dragMode,
selectionExtraInfoDisableFloatingToolbar: true, selectionExtraInfoDisableFloatingToolbar: true,
}, },
); );

View file

@ -102,14 +102,17 @@ void main() {
expect(scroller.capturedRect, isNotNull); expect(scroller.capturedRect, isNotNull);
// For AxisDirection.down the generic Rect.fromCenter path is used. // For AxisDirection.down the rect starts at offset and extends downward.
expect( expect(
scroller.capturedRect!.center, scroller.capturedRect!.top,
offset, offset.dy,
reason: reason: 'proxy rect should start at the handle position',
'right handle proxy rect should be centered on the handle offset', );
expect(
scroller.capturedRect!.bottom,
offset.dy + edgeOffset,
reason: 'proxy rect should extend edgeOffset pixels BELOW the handle',
); );
expect(scroller.capturedRect!.width, edgeOffset);
expect(scroller.capturedRect!.height, edgeOffset); expect(scroller.capturedRect!.height, edgeOffset);
}, },
); );