Unverified Commit 2369d76a authored by chunhtai's avatar chunhtai Committed by GitHub

Makes text selection match the native behavior (#79308)

parent 304448af
......@@ -3042,22 +3042,43 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
// If text is obscured, the entire sentence should be treated as one word.
if (obscureText) {
return TextSelection(baseOffset: 0, extentOffset: _plainText.length);
// If the word is a space, on iOS try to select the previous word instead.
// On Android try to select the previous word instead only if the text is read only.
// On iOS, select the previous word if there is a previous word, or select
// to the end of the next word if there is a next word. Select nothing if
// there is neither a previous word nor a next word.
//
// If the platform is Android and the text is read only, try to select the
// previous word if there is one; otherwise, select the single whitespace at
// the position.
} else if (_isWhitespace(_plainText.codeUnitAt(position.offset))
&& position.offset > 0) {
assert(defaultTargetPlatform != null);
final TextRange? previousWord = _getPreviousWord(word.start);
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
if (previousWord == null) {
final TextRange? nextWord = _getNextWord(word.start);
if (nextWord == null) {
return TextSelection.collapsed(offset: position.offset);
}
return TextSelection(
baseOffset: position.offset,
extentOffset: nextWord.end,
);
}
return TextSelection(
baseOffset: previousWord!.start,
baseOffset: previousWord.start,
extentOffset: position.offset,
);
case TargetPlatform.android:
if (readOnly) {
if (previousWord == null) {
return TextSelection(
baseOffset: position.offset,
extentOffset: position.offset + 1,
);
}
return TextSelection(
baseOffset: previousWord!.start,
baseOffset: previousWord.start,
extentOffset: position.offset,
);
}
......
......@@ -499,6 +499,136 @@ void main() {
expect(currentSelection.extentOffset, 9);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61026
test('selects readonly renderEditable matches native behavior for android', () {
// Regression test for https://github.com/flutter/flutter/issues/79166.
final TargetPlatform? previousPlatform = debugDefaultTargetPlatformOverride;
debugDefaultTargetPlatformOverride = TargetPlatform.android;
const String text = ' test';
final TextSelectionDelegate delegate = FakeEditableTextState()
..textEditingValue = const TextEditingValue(text: text);
final ViewportOffset viewportOffset = ViewportOffset.zero();
late TextSelection currentSelection;
final RenderEditable editable = RenderEditable(
backgroundCursorColor: Colors.grey,
selectionColor: Colors.black,
textDirection: TextDirection.ltr,
cursorColor: Colors.red,
readOnly: true,
offset: viewportOffset,
textSelectionDelegate: delegate,
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
currentSelection = selection;
},
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
text: const TextSpan(
text: text,
style: TextStyle(
height: 1.0, fontSize: 10.0, fontFamily: 'Ahem',
),
),
selection: const TextSelection.collapsed(
offset: 4,
),
);
layout(editable);
// Select the second white space, where the text position = 1.
editable.selectWordsInRange(from: const Offset(10, 2), cause:SelectionChangedCause.longPress);
pumpFrame();
expect(currentSelection.isCollapsed, false);
expect(currentSelection.baseOffset, 1);
expect(currentSelection.extentOffset, 2);
debugDefaultTargetPlatformOverride = previousPlatform;
});
test('selects renderEditable matches native behavior for iOS case 1', () {
// Regression test for https://github.com/flutter/flutter/issues/79166.
final TargetPlatform? previousPlatform = debugDefaultTargetPlatformOverride;
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
const String text = ' test';
final TextSelectionDelegate delegate = FakeEditableTextState()
..textEditingValue = const TextEditingValue(text: text);
final ViewportOffset viewportOffset = ViewportOffset.zero();
late TextSelection currentSelection;
final RenderEditable editable = RenderEditable(
backgroundCursorColor: Colors.grey,
selectionColor: Colors.black,
textDirection: TextDirection.ltr,
cursorColor: Colors.red,
offset: viewportOffset,
textSelectionDelegate: delegate,
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
currentSelection = selection;
},
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
text: const TextSpan(
text: text,
style: TextStyle(
height: 1.0, fontSize: 10.0, fontFamily: 'Ahem',
),
),
selection: const TextSelection.collapsed(
offset: 4,
),
);
layout(editable);
// Select the second white space, where the text position = 1.
editable.selectWordsInRange(from: const Offset(10, 2), cause:SelectionChangedCause.longPress);
pumpFrame();
expect(currentSelection.isCollapsed, false);
expect(currentSelection.baseOffset, 1);
expect(currentSelection.extentOffset, 6);
debugDefaultTargetPlatformOverride = previousPlatform;
});
test('selects renderEditable matches native behavior for iOS case 2', () {
// Regression test for https://github.com/flutter/flutter/issues/79166.
final TargetPlatform? previousPlatform = debugDefaultTargetPlatformOverride;
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
const String text = ' ';
final TextSelectionDelegate delegate = FakeEditableTextState()
..textEditingValue = const TextEditingValue(text: text);
final ViewportOffset viewportOffset = ViewportOffset.zero();
late TextSelection currentSelection;
final RenderEditable editable = RenderEditable(
backgroundCursorColor: Colors.grey,
selectionColor: Colors.black,
textDirection: TextDirection.ltr,
cursorColor: Colors.red,
offset: viewportOffset,
textSelectionDelegate: delegate,
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
currentSelection = selection;
},
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
text: const TextSpan(
text: text,
style: TextStyle(
height: 1.0, fontSize: 10.0, fontFamily: 'Ahem',
),
),
selection: const TextSelection.collapsed(
offset: 4,
),
);
layout(editable);
// Select the second white space, where the text position = 1.
editable.selectWordsInRange(from: const Offset(10, 2), cause:SelectionChangedCause.longPress);
pumpFrame();
expect(currentSelection.isCollapsed, true);
expect(currentSelection.baseOffset, 1);
expect(currentSelection.extentOffset, 1);
debugDefaultTargetPlatformOverride = previousPlatform;
});
test('selects correct place when offsets are flipped', () {
const String text = 'abc def ghi';
final TextSelectionDelegate delegate = FakeEditableTextState()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment