Skip to content

Commit e078c93

Browse files
authored
SelectionChangedCause for iOS keyboard-select (#122144)
SelectionChangedCause for iOS keyboard-select
1 parent d7e8517 commit e078c93

3 files changed

Lines changed: 42 additions & 5 deletions

File tree

packages/flutter/lib/src/widgets/editable_text.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2775,7 +2775,15 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
27752775

27762776
if (value.text == _value.text && value.composing == _value.composing) {
27772777
// `selection` is the only change.
2778-
_handleSelectionChanged(value.selection, (_textInputConnection?.scribbleInProgress ?? false) ? SelectionChangedCause.scribble : SelectionChangedCause.keyboard);
2778+
SelectionChangedCause cause;
2779+
if (_textInputConnection?.scribbleInProgress ?? false) {
2780+
cause = SelectionChangedCause.scribble;
2781+
} else if (_pointOffsetOrigin != null) {
2782+
cause = SelectionChangedCause.forcePress;
2783+
} else {
2784+
cause = SelectionChangedCause.keyboard;
2785+
}
2786+
_handleSelectionChanged(value.selection, cause);
27792787
} else {
27802788
if (value.text != _value.text) {
27812789
// Hide the toolbar if the text was changed, but only hide the toolbar

packages/flutter/test/widgets/editable_text_test.dart

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2751,6 +2751,8 @@ void main() {
27512751
await tester.pumpAndSettle();
27522752

27532753
expect(selectionCause, SelectionChangedCause.scribble);
2754+
2755+
await tester.testTextInput.finishScribbleInteraction();
27542756
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }));
27552757

27562758
testWidgets('Requests focus and changes the selection when onScribbleFocus is called', (WidgetTester tester) async {
@@ -12411,6 +12413,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1241112413
EditableText.debugDeterministicCursor = true;
1241212414
final FocusNode focusNode = FocusNode();
1241312415
final GlobalKey key = GlobalKey();
12416+
SelectionChangedCause? lastSelectionChangedCause;
1241412417

1241512418
final TextEditingController controller = TextEditingController(text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ\n1234567890');
1241612419
controller.selection = const TextSelection.collapsed(offset: 0);
@@ -12425,6 +12428,9 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1242512428
cursorColor: Colors.blue,
1242612429
backgroundCursorColor: Colors.grey,
1242712430
cursorOpacityAnimates: true,
12431+
onSelectionChanged: (TextSelection selection, SelectionChangedCause? cause) {
12432+
lastSelectionChangedCause = cause;
12433+
},
1242812434
maxLines: 2,
1242912435
),
1243012436
),
@@ -12461,6 +12467,8 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1246112467
// Selection should be updated based on the floating cursor location.
1246212468
expect(controller.selection.isCollapsed, true);
1246312469
expect(controller.selection.baseOffset, 4);
12470+
expect(lastSelectionChangedCause, SelectionChangedCause.forcePress);
12471+
lastSelectionChangedCause = null;
1246412472

1246512473
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Start, offset: Offset.zero));
1246612474
await tester.pump();
@@ -12486,7 +12494,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1248612494
));
1248712495

1248812496
// Simulate UIKit setting the selection using keyboard selection.
12489-
controller.selection = const TextSelection(baseOffset: 0, extentOffset: 4);
12497+
state.updateEditingValue(state.currentTextEditingValue.copyWith(selection: const TextSelection(baseOffset: 0, extentOffset: 4)));
1249012498
await tester.pump();
1249112499

1249212500
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.End, offset: Offset.zero));
@@ -12496,9 +12504,11 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1249612504
expect(controller.selection.isCollapsed, false);
1249712505
expect(controller.selection.baseOffset, 0);
1249812506
expect(controller.selection.extentOffset, 4);
12507+
expect(lastSelectionChangedCause, SelectionChangedCause.forcePress);
12508+
lastSelectionChangedCause = null;
1249912509

1250012510
// Now test using keyboard selection in a forwards direction.
12501-
controller.selection = const TextSelection.collapsed(offset: 0);
12511+
state.updateEditingValue(state.currentTextEditingValue.copyWith(selection: const TextSelection.collapsed(offset: 0)));
1250212512
await tester.pump();
1250312513
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Start, offset: Offset.zero));
1250412514
await tester.pump();
@@ -12523,7 +12533,7 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1252312533
));
1252412534

1252512535
// Simulate UIKit setting the selection using keyboard selection.
12526-
controller.selection = const TextSelection(baseOffset: 0, extentOffset: 4);
12536+
state.updateEditingValue(state.currentTextEditingValue.copyWith(selection: const TextSelection(baseOffset: 0, extentOffset: 4)));
1252712537
await tester.pump();
1252812538

1252912539
state.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.End, offset: Offset.zero));
@@ -12533,11 +12543,13 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1253312543
expect(controller.selection.isCollapsed, false);
1253412544
expect(controller.selection.baseOffset, 0);
1253512545
expect(controller.selection.extentOffset, 4);
12546+
expect(lastSelectionChangedCause, SelectionChangedCause.forcePress);
12547+
lastSelectionChangedCause = null;
1253612548

1253712549
// Test that the affinity is updated in case the floating cursor ends at the same offset.
1253812550

1253912551
// Put the selection at the beginning of the second line.
12540-
controller.selection = const TextSelection.collapsed(offset: 27);
12552+
state.updateEditingValue(state.currentTextEditingValue.copyWith(selection: const TextSelection.collapsed(offset: 27)));
1254112553
await tester.pump();
1254212554

1254312555
// Now test using keyboard selection in a forwards direction.
@@ -12572,6 +12584,8 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
1257212584
expect(controller.selection.isCollapsed, true);
1257312585
expect(controller.selection.baseOffset, 27);
1257412586
expect(controller.selection.extentOffset, 27);
12587+
expect(lastSelectionChangedCause, SelectionChangedCause.forcePress);
12588+
lastSelectionChangedCause = null;
1257512589

1257612590
EditableText.debugDeterministicCursor = false;
1257712591
});

packages/flutter_test/lib/src/test_text_input.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,21 @@ class TestTextInput {
296296
);
297297
}
298298

299+
/// Simulates a scribble interaction finishing.
300+
Future<void> finishScribbleInteraction() async {
301+
assert(isRegistered);
302+
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
303+
SystemChannels.textInput.name,
304+
SystemChannels.textInput.codec.encodeMethodCall(
305+
MethodCall(
306+
'TextInputClient.scribbleInteractionFinished',
307+
<dynamic>[_client ?? -1,]
308+
),
309+
),
310+
(ByteData? data) { /* response from framework is discarded */ },
311+
);
312+
}
313+
299314
/// Simulates a Scribble focus.
300315
Future<void> scribbleFocusElement(String elementIdentifier, Offset offset) async {
301316
assert(isRegistered);

0 commit comments

Comments
 (0)