Skip to content

Commit

Permalink
Call bringIntoView after RenderEditable updates on paste (flutter#98604)
Browse files Browse the repository at this point in the history
  • Loading branch information
tgucio committed Feb 24, 2022
1 parent edc1612 commit 533a5dd
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 7 deletions.
26 changes: 21 additions & 5 deletions packages/flutter/lib/src/widgets/editable_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1689,7 +1689,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
Clipboard.setData(ClipboardData(text: selection.textInside(text)));
_replaceText(ReplaceTextIntent(textEditingValue, '', selection, cause));
if (cause == SelectionChangedCause.toolbar) {
bringIntoView(textEditingValue.selection.extent);
// Schedule a call to bringIntoView() after renderEditable updates.
SchedulerBinding.instance.addPostFrameCallback((_) {
bringIntoView(textEditingValue.selection.extent);
});
hideToolbar();
}
_clipboardStatus?.update();
Expand All @@ -1715,7 +1718,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien

_replaceText(ReplaceTextIntent(textEditingValue, data.text!, selection, cause));
if (cause == SelectionChangedCause.toolbar) {
bringIntoView(textEditingValue.selection.extent);
// Schedule a call to bringIntoView() after renderEditable updates.
SchedulerBinding.instance.addPostFrameCallback((_) {
bringIntoView(textEditingValue.selection.extent);
});
hideToolbar();
}
}
Expand Down Expand Up @@ -3095,10 +3101,20 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}

void _replaceText(ReplaceTextIntent intent) {
userUpdateTextEditingValue(
intent.currentTextEditingValue.replaced(intent.replacementRange, intent.replacementText),
intent.cause,
final TextEditingValue oldValue = _value;
final TextEditingValue newValue = intent.currentTextEditingValue.replaced(
intent.replacementRange,
intent.replacementText,
);
userUpdateTextEditingValue(newValue, intent.cause);

// If there's no change in text and selection (e.g. when selecting and
// pasting identical text), the widget won't be rebuilt on value update.
// Handle this by calling _didChangeTextEditingValue() so caret and scroll
// updates can happen.
if (newValue == oldValue) {
_didChangeTextEditingValue();
}
}
late final Action<ReplaceTextIntent> _replaceTextAction = CallbackAction<ReplaceTextIntent>(onInvoke: _replaceText);

Expand Down
48 changes: 46 additions & 2 deletions packages/flutter/test/widgets/editable_text_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10948,12 +10948,12 @@ void main() {

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

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

Expand All @@ -10980,6 +10980,50 @@ void main() {
expect(scrollController.offset.roundToDouble(), 0.0);
});

testWidgets('Should not scroll on paste if caret already visible', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/96658.
final ScrollController scrollController = ScrollController();
final TextEditingController controller = TextEditingController(
text: 'Lorem ipsum please paste here: \n${".\n" * 50}',
);
final FocusNode focusNode = FocusNode();

await tester.pumpWidget(
MaterialApp(
home: Center(
child: SizedBox(
height: 600.0,
width: 600.0,
child: EditableText(
controller: controller,
scrollController: scrollController,
focusNode: focusNode,
maxLines: null,
style: const TextStyle(fontSize: 36.0),
backgroundCursorColor: Colors.grey,
cursorColor: cursorColor,
),
),
),
)
);

await Clipboard.setData(const ClipboardData(text: 'Fairly long text to be pasted'));
focusNode.requestFocus();

final EditableTextState state =
tester.state<EditableTextState>(find.byType(EditableText));

expect(scrollController.offset, 0.0);

controller.selection = const TextSelection.collapsed(offset: 31);
await state.pasteText(SelectionChangedCause.toolbar);
await tester.pumpAndSettle();

// No scroll should happen as the caret is in the viewport all the time.
expect(scrollController.offset, 0.0);
});

testWidgets('Autofill enabled by default', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(
Expand Down

0 comments on commit 533a5dd

Please sign in to comment.