Skip to content

Commit

Permalink
SelectAll on iOS does not scroll to end of editable text anymore (#10…
Browse files Browse the repository at this point in the history
…5799)

Co-authored-by: Anthony Oleinik <[email protected]>
  • Loading branch information
antholeole and Anthony Oleinik authored Jul 29, 2022
1 parent f5e4d2b commit db3b141
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 67 deletions.
13 changes: 12 additions & 1 deletion packages/flutter/lib/src/widgets/editable_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1929,8 +1929,19 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
),
cause,
);

if (cause == SelectionChangedCause.toolbar) {
bringIntoView(textEditingValue.selection.extent);
switch (defaultTargetPlatform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
bringIntoView(textEditingValue.selection.extent);
break;
case TargetPlatform.macOS:
case TargetPlatform.iOS:
break;
}
}
}

Expand Down
3 changes: 1 addition & 2 deletions packages/flutter/lib/src/widgets/text_selection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,6 @@ abstract class TextSelectionControls {
/// by the user.
void handleSelectAll(TextSelectionDelegate delegate) {
delegate.selectAll(SelectionChangedCause.toolbar);
delegate.bringIntoView(delegate.textEditingValue.selection.extent);
}
}

Expand Down Expand Up @@ -578,7 +577,7 @@ class TextSelectionOverlay {
}
}

/// An object that manages a pair of selection handles.
/// An object that manages a pair of selection handles and a toolbar.
///
/// The selection handles are displayed in the [Overlay] that most closely
/// encloses the given [BuildContext].
Expand Down
147 changes: 83 additions & 64 deletions packages/flutter/test/widgets/editable_text_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11233,38 +11233,13 @@ void main() {
expect(tester.hasRunningAnimations, isFalse);
});

testWidgets('Selection will be scrolled into view with SelectionChangedCause', (WidgetTester tester) async {
final GlobalKey<EditableTextState> key = GlobalKey<EditableTextState>();
group('Selection changed scroll into view', () {
final String text = List<int>.generate(64, (int index) => index).join('\n');
final TextEditingController controller = TextEditingController(text: text);
final ScrollController scrollController = ScrollController();

await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(
height: 32,
child: EditableText(
key: key,
focusNode: focusNode,
style: Typography.material2018().black.subtitle1!,
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
controller: controller,
scrollController: scrollController,
maxLines: 2,
),
),
),
),
),
);

final TextSelectionDelegate textSelectionDelegate = key.currentState!;

late double maxScrollExtent;
Future<void> resetSelectionAndScrollOffset([bool setMaxScrollExtent = true]) async {

Future<void> resetSelectionAndScrollOffset(WidgetTester tester, {required bool setMaxScrollExtent}) async {
controller.value = controller.value.copyWith(
text: text,
selection: controller.selection.copyWith(baseOffset: 0, extentOffset: 1),
Expand All @@ -11277,49 +11252,93 @@ void main() {
expect(scrollController.offset, targetOffset);
}

// Cut
await resetSelectionAndScrollOffset();
textSelectionDelegate.cutSelection(SelectionChangedCause.keyboard);
await tester.pump();
expect(scrollController.offset, maxScrollExtent);

await resetSelectionAndScrollOffset();
textSelectionDelegate.cutSelection(SelectionChangedCause.toolbar);
await tester.pump();
expect(scrollController.offset.roundToDouble(), 0.0);
Future<TextSelectionDelegate> pumpLongScrollableText(WidgetTester tester) async {
final GlobalKey<EditableTextState> key = GlobalKey<EditableTextState>();

// Paste
await resetSelectionAndScrollOffset();
await textSelectionDelegate.pasteText(SelectionChangedCause.keyboard);
await tester.pump();
expect(scrollController.offset, maxScrollExtent);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(
height: 32,
child: EditableText(
key: key,
focusNode: focusNode,
style: Typography.material2018().black.subtitle1!,
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
controller: controller,
scrollController: scrollController,
maxLines: 2,
),
),
),
),
),
);

await resetSelectionAndScrollOffset();
await textSelectionDelegate.pasteText(SelectionChangedCause.toolbar);
await tester.pump();
expect(scrollController.offset.roundToDouble(), 0.0);
// Populate [maxScrollExtent].
await resetSelectionAndScrollOffset(tester, setMaxScrollExtent: false);
return key.currentState!;
}

// Select all
await resetSelectionAndScrollOffset(false);
textSelectionDelegate.selectAll(SelectionChangedCause.keyboard);
await tester.pump();
expect(scrollController.offset, 0.0);
testWidgets('SelectAll toolbar action will not set max scroll on designated platforms', (WidgetTester tester) async {
final TextSelectionDelegate textSelectionDelegate = await pumpLongScrollableText(tester);

await resetSelectionAndScrollOffset(false);
textSelectionDelegate.selectAll(SelectionChangedCause.toolbar);
await tester.pump();
expect(scrollController.offset.roundToDouble(), maxScrollExtent);
await resetSelectionAndScrollOffset(tester, setMaxScrollExtent: false);
textSelectionDelegate.selectAll(SelectionChangedCause.toolbar);
await tester.pump();
expect(scrollController.offset, 0.0);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));

// Copy
await resetSelectionAndScrollOffset();
textSelectionDelegate.copySelection(SelectionChangedCause.keyboard);
await tester.pump();
expect(scrollController.offset, maxScrollExtent);
testWidgets('Selection will be scrolled into view with SelectionChangedCause', (WidgetTester tester) async {
final TextSelectionDelegate textSelectionDelegate = await pumpLongScrollableText(tester);

await resetSelectionAndScrollOffset();
textSelectionDelegate.copySelection(SelectionChangedCause.toolbar);
await tester.pump();
expect(scrollController.offset.roundToDouble(), 0.0);
// Cut
await resetSelectionAndScrollOffset(tester, setMaxScrollExtent: true);
textSelectionDelegate.cutSelection(SelectionChangedCause.keyboard);
await tester.pump();
expect(scrollController.offset, maxScrollExtent);

await resetSelectionAndScrollOffset(tester, setMaxScrollExtent: true);
textSelectionDelegate.cutSelection(SelectionChangedCause.toolbar);
await tester.pump();
expect(scrollController.offset.roundToDouble(), 0.0);

// Paste
await resetSelectionAndScrollOffset(tester, setMaxScrollExtent: true);
await textSelectionDelegate.pasteText(SelectionChangedCause.keyboard);
await tester.pump();
expect(scrollController.offset, maxScrollExtent);

await resetSelectionAndScrollOffset(tester, setMaxScrollExtent: true);
await textSelectionDelegate.pasteText(SelectionChangedCause.toolbar);
await tester.pump();
expect(scrollController.offset.roundToDouble(), 0.0);

// Select all
await resetSelectionAndScrollOffset(tester, setMaxScrollExtent: false);
textSelectionDelegate.selectAll(SelectionChangedCause.keyboard);
await tester.pump();
expect(scrollController.offset, 0.0);

await resetSelectionAndScrollOffset(tester, setMaxScrollExtent: false);
textSelectionDelegate.selectAll(SelectionChangedCause.toolbar);
await tester.pump();
expect(scrollController.offset.roundToDouble(), maxScrollExtent);

// Copy
await resetSelectionAndScrollOffset(tester, setMaxScrollExtent: true);
textSelectionDelegate.copySelection(SelectionChangedCause.keyboard);
await tester.pump();
expect(scrollController.offset, maxScrollExtent);

await resetSelectionAndScrollOffset(tester, setMaxScrollExtent: true);
textSelectionDelegate.copySelection(SelectionChangedCause.toolbar);
await tester.pump();
expect(scrollController.offset.roundToDouble(), 0.0);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
});

testWidgets('Should not scroll on paste if caret already visible', (WidgetTester tester) async {
Expand Down

0 comments on commit db3b141

Please sign in to comment.