Skip to content

Commit

Permalink
[SuperEditor][Android] Fix auto-scrolling start region (Resolves #2000)…
Browse files Browse the repository at this point in the history
… (#2036)
  • Loading branch information
angelosilvestre committed May 26, 2024
1 parent e698f2f commit 07f6838
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1388,6 +1388,18 @@ class SuperEditorAndroidControlsOverlayManagerState extends State<SuperEditorAnd
@visibleForTesting
bool get wantsToDisplayMagnifier => _controlsController!.shouldShowMagnifier.value;

/// Returns the `RenderBox` for the scrolling viewport.
///
/// If this widget has an ancestor `Scrollable`, then the returned
/// `RenderBox` belongs to that ancestor `Scrollable`.
///
/// If this widget doesn't have an ancestor `Scrollable`, then this
/// widget includes a `ScrollView` and this `State`'s render object
/// is the viewport `RenderBox`.
RenderBox get viewportBox =>
(context.findAncestorScrollableWithVerticalScroll?.context.findRenderObject() ?? context.findRenderObject())
as RenderBox;

void _onSelectionChange() {
final selection = widget.selection.value;
if (selection == null) {
Expand Down Expand Up @@ -1547,11 +1559,12 @@ class SuperEditorAndroidControlsOverlayManagerState extends State<SuperEditorAnd
documentLayout.getDocumentOffsetFromAncestorOffset(_dragHandleSelectionGlobalFocalPoint.value!),
)!;

final centerOfContentInContentSpace = documentLayout.getRectForPosition(nearestPosition)!.center;

// Move the magnifier focal point to match the drag x-offset, but always remain focused on the vertical
// center of the line.
final centerOfContentAtNearestPosition = documentLayout.getAncestorOffsetFromDocumentOffset(
documentLayout.getRectForPosition(nearestPosition)!.center,
);
final centerOfContentAtNearestPosition =
documentLayout.getAncestorOffsetFromDocumentOffset(centerOfContentInContentSpace);
_magnifierFocalPoint.value = Offset(
_magnifierFocalPoint.value!.dx + dragDx,
centerOfContentAtNearestPosition.dy,
Expand Down Expand Up @@ -1581,10 +1594,17 @@ class SuperEditorAndroidControlsOverlayManagerState extends State<SuperEditorAnd
// Update the auto-scroll focal point so that the viewport scrolls if we're
// close to the boundary.
widget.dragHandleAutoScroller.value?.updateAutoScrollHandleMonitoring(
dragEndInViewport: centerOfContentAtNearestPosition,
dragEndInViewport: _contentOffsetInViewport(centerOfContentInContentSpace),
);
}

/// Converts the [offset] in content space to an offset in the viewport space.
Offset _contentOffsetInViewport(Offset offset) {
final documentLayout = widget.getDocumentLayout();
final globalOffset = documentLayout.getGlobalOffsetFromDocumentOffset(offset);
return viewportBox.globalToLocal(globalOffset);
}

@override
Widget build(BuildContext context) {
return OverlayPortal(
Expand Down
48 changes: 48 additions & 0 deletions super_editor/test/super_editor/supereditor_scrolling_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,54 @@ void main() {
);
});

testWidgetsOnMobile('starts auto-scrolling when dragging near the top', (tester) async {
final scrollController = ScrollController();

// Pump an editor with an appbar above the editor so we make sure that
// auto-scroll starts when the user dragged near the top of the editor,
// not at the top of the screen.
await tester
.createDocument()
.withLongTextContent()
.withScrollController(scrollController)
.withEditorSize(Size(300, 300))
.autoFocus(true)
.withAppBar(100.0)
.pump();

// Scroll all the way to the bottom.
scrollController.jumpTo(scrollController.position.maxScrollExtent);
await tester.pumpAndSettle();

// Place the caret at approximately at the middle of the first visible line.
await tester.tapAt(tester.getTopLeft(find.byType(SuperEditor)) + Offset(150, 20));
await tester.pump(kDoubleTapTimeout);

final scrollOffsetBeforeDrag = scrollController.offset;

// Drag the handle a bit to the top.
final dragGesture = await tester.startGesture(tester.getCenter(
SuperEditorInspector.findMobileCaretDragHandle(),
));
await dragGesture.moveBy(Offset(0, -20));
await tester.pump();

// Pump some frames to let the auto-scroll kick in.
for (int i = 0; i < 60; i += 1) {
await tester.pump();
}

// Release the gesture.
await dragGesture.up();
await tester.pump();

// Ensure the editor scrolled up.
expect(scrollController.offset, lessThan(scrollOffsetBeforeDrag));

// Let the long-press timer resolve.
await tester.pump(kLongPressTimeout);
});

testWidgetsOnDesktop("auto-scrolls to caret position", (tester) async {
const windowSize = Size(800, 600);
tester.view.physicalSize = windowSize;
Expand Down
20 changes: 20 additions & 0 deletions super_editor/test/super_editor/supereditor_test_tools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,14 @@ class TestSuperEditorConfigurator {
return this;
}

/// Configures the [SuperEditor] to display an [AppBar] with the given height above the [SuperEditor].
///
/// If [withCustomWidgetTreeBuilder] is used, this setting is ignored.
TestSuperEditorConfigurator withAppBar(double height) {
_config.appBarHeight = height;
return this;
}

/// Configures the [SuperEditor] to use the given [scrollController]
TestSuperEditorConfigurator withScrollController(ScrollController? scrollController) {
_config.scrollController = scrollController;
Expand Down Expand Up @@ -438,6 +446,17 @@ class TestSuperEditorConfigurator {
// Flutter to pick the web shortcuts.
shortcuts: defaultFlutterShortcuts,
home: Scaffold(
appBar: _config.appBarHeight != null
? PreferredSize(
preferredSize: ui.Size(double.infinity, _config.appBarHeight!),
child: SafeArea(
child: SizedBox(
height: _config.appBarHeight!,
child: ColoredBox(color: Colors.yellow),
),
),
)
: null,
body: superEditor,
),
debugShowCheckedModeBanner: false,
Expand Down Expand Up @@ -674,6 +693,7 @@ class SuperEditorTestConfiguration {
final plugins = <SuperEditorPlugin>{};

WidgetTreeBuilder? widgetTreeBuilder;
double? appBarHeight;
}

/// Must return a widget tree containing the given [superEditor]
Expand Down

0 comments on commit 07f6838

Please sign in to comment.