Unverified Commit 6031c56d authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

Material Long Press Text Handle Flash (#32911)

Fix a bug where holding down on text selection caused the handles to flash. The fix was to only update selection when it actually changed.
parent 0e8720e9
5e0cf0b9b347526d299718a4755e2d65cffacaba
25ab3c95b1ac02a41973c05f96e664370857ce55
......@@ -350,6 +350,18 @@ class RenderEditable extends RenderBox {
static const int _kShiftMask = 1; // https://developer.android.com/reference/android/view/KeyEvent.html#META_SHIFT_ON
static const int _kControlMask = 1 << 12; // https://developer.android.com/reference/android/view/KeyEvent.html#META_CTRL_ON
// Call through to onSelectionChanged only if the given nextSelection is
// different from the existing selection.
void _handlePotentialSelectionChange(
TextSelection nextSelection,
SelectionChangedCause cause,
) {
if (nextSelection == selection) {
return;
}
onSelectionChanged(nextSelection, this, cause);
}
// TODO(goderbauer): doesn't handle extended grapheme clusters with more than one Unicode scalar value (https://github.com/flutter/flutter/issues/13404).
void _handleKeyEvent(RawKeyEvent keyEvent) {
// Only handle key events on Android.
......@@ -484,21 +496,19 @@ class RenderEditable extends RenderBox {
// base offset is always less than the extent offset.
if (shift) {
if (_baseOffset < newOffset) {
onSelectionChanged(
_handlePotentialSelectionChange(
TextSelection(
baseOffset: _baseOffset,
extentOffset: newOffset,
),
this,
SelectionChangedCause.keyboard,
);
} else {
onSelectionChanged(
_handlePotentialSelectionChange(
TextSelection(
baseOffset: newOffset,
extentOffset: _baseOffset,
),
this,
SelectionChangedCause.keyboard,
);
}
......@@ -511,13 +521,12 @@ class RenderEditable extends RenderBox {
else if (rightArrow)
newOffset = _baseOffset > _extentOffset ? _baseOffset : _extentOffset;
}
onSelectionChanged(
_handlePotentialSelectionChange(
TextSelection.fromPosition(
TextPosition(
offset: newOffset
)
),
this,
SelectionChangedCause.keyboard,
);
}
......@@ -564,12 +573,11 @@ class RenderEditable extends RenderBox {
case _kAKeyCode:
_baseOffset = 0;
_extentOffset = textSelectionDelegate.textEditingValue.text.length;
onSelectionChanged(
_handlePotentialSelectionChange(
TextSelection(
baseOffset: 0,
extentOffset: textSelectionDelegate.textEditingValue.text.length,
),
this,
SelectionChangedCause.keyboard,
);
break;
......@@ -967,7 +975,7 @@ class RenderEditable extends RenderBox {
}
void _handleSetSelection(TextSelection selection) {
onSelectionChanged(selection, this, SelectionChangedCause.keyboard);
_handlePotentialSelectionChange(selection, SelectionChangedCause.keyboard);
}
void _handleMoveCursorForwardByCharacter(bool extentSelection) {
......@@ -975,8 +983,8 @@ class RenderEditable extends RenderBox {
if (extentOffset == null)
return;
final int baseOffset = !extentSelection ? extentOffset : _selection.baseOffset;
onSelectionChanged(
TextSelection(baseOffset: baseOffset, extentOffset: extentOffset), this, SelectionChangedCause.keyboard,
_handlePotentialSelectionChange(
TextSelection(baseOffset: baseOffset, extentOffset: extentOffset), SelectionChangedCause.keyboard,
);
}
......@@ -985,8 +993,8 @@ class RenderEditable extends RenderBox {
if (extentOffset == null)
return;
final int baseOffset = !extentSelection ? extentOffset : _selection.baseOffset;
onSelectionChanged(
TextSelection(baseOffset: baseOffset, extentOffset: extentOffset), this, SelectionChangedCause.keyboard,
_handlePotentialSelectionChange(
TextSelection(baseOffset: baseOffset, extentOffset: extentOffset), SelectionChangedCause.keyboard,
);
}
......@@ -998,12 +1006,11 @@ class RenderEditable extends RenderBox {
if (nextWord == null)
return;
final int baseOffset = extentSelection ? _selection.baseOffset : nextWord.start;
onSelectionChanged(
_handlePotentialSelectionChange(
TextSelection(
baseOffset: baseOffset,
extentOffset: nextWord.start,
),
this,
SelectionChangedCause.keyboard,
);
}
......@@ -1016,12 +1023,11 @@ class RenderEditable extends RenderBox {
if (previousWord == null)
return;
final int baseOffset = extentSelection ? _selection.baseOffset : previousWord.start;
onSelectionChanged(
_handlePotentialSelectionChange(
TextSelection(
baseOffset: baseOffset,
extentOffset: previousWord.start,
),
this,
SelectionChangedCause.keyboard,
);
}
......@@ -1400,7 +1406,7 @@ class RenderEditable extends RenderBox {
);
// Call [onSelectionChanged] only when the selection actually changed.
if (newSelection != _selection) {
onSelectionChanged(newSelection, this, cause);
_handlePotentialSelectionChange(newSelection, cause);
}
}
}
......@@ -1428,12 +1434,13 @@ class RenderEditable extends RenderBox {
final TextSelection lastWord = to == null ?
firstWord : _selectWordAtOffset(_textPainter.getPositionForOffset(globalToLocal(to - _paintOffset)));
onSelectionChanged(
_handlePotentialSelectionChange(
TextSelection(
baseOffset: firstWord.base.offset,
extentOffset: lastWord.extent.offset,
affinity: firstWord.affinity,
), this, cause,
),
cause,
);
}
}
......@@ -1449,15 +1456,13 @@ class RenderEditable extends RenderBox {
final TextPosition position = _textPainter.getPositionForOffset(globalToLocal(_lastTapDownPosition - _paintOffset));
final TextRange word = _textPainter.getWordBoundary(position);
if (position.offset - word.start <= 1) {
onSelectionChanged(
_handlePotentialSelectionChange(
TextSelection.collapsed(offset: word.start, affinity: TextAffinity.downstream),
this,
cause,
);
} else {
onSelectionChanged(
_handlePotentialSelectionChange(
TextSelection.collapsed(offset: word.end, affinity: TextAffinity.upstream),
this,
cause,
);
}
......
......@@ -367,13 +367,14 @@ void main() {
const String testValue = 'A short phrase';
await tester.enterText(find.byType(CupertinoTextField), testValue);
await tester.pump();
await tester.tapAt(textOffsetToPosition(tester, testValue.length));
await tester.pump();
await tester.pumpAndSettle();
await expectLater(
find.byKey(const ValueKey<int>(1)),
matchesGoldenFile('text_field_cursor_test.0.1.png'),
matchesGoldenFile('text_field_cursor_test.0.2.png'),
);
}, skip: !Platform.isLinux);
......@@ -395,14 +396,15 @@ void main() {
const String testValue = 'A short phrase';
await tester.enterText(find.byType(CupertinoTextField), testValue);
await tester.pump();
await tester.tapAt(textOffsetToPosition(tester, testValue.length));
await tester.pump();
await tester.pumpAndSettle();
debugDefaultTargetPlatformOverride = null;
await expectLater(
find.byKey(const ValueKey<int>(1)),
matchesGoldenFile('text_field_cursor_test.1.1.png'),
matchesGoldenFile('text_field_cursor_test.1.2.png'),
);
}, skip: !Platform.isLinux);
......
......@@ -735,6 +735,45 @@ void main() {
expect(controller.selection.extentOffset, testValue.indexOf('f')+1);
});
testWidgets('Slight movements in longpress don\'t hide/show handles', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController();
await tester.pumpWidget(
overlay(
child: TextField(
controller: controller,
),
)
);
const String testValue = 'abc def ghi';
await tester.enterText(find.byType(TextField), testValue);
expect(controller.value.text, testValue);
await skipPastScrollingAnimation(tester);
expect(controller.selection.isCollapsed, true);
// Long press the 'e' to select 'def', but don't release the gesture.
final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e'));
final TestGesture gesture = await tester.startGesture(ePos, pointer: 7);
await tester.pump(const Duration(seconds: 2));
await tester.pumpAndSettle();
// Handles are shown
final Finder fadeFinder = find.byType(FadeTransition);
expect(fadeFinder, findsNWidgets(2)); // 2 handles, 1 toolbar
FadeTransition handle = tester.widget(fadeFinder.at(0));
expect(handle.opacity.value, equals(1.0));
// Move the gesture very slightly
await gesture.moveBy(const Offset(1.0, 1.0));
await tester.pump(TextSelectionOverlay.fadeDuration * 0.5);
handle = tester.widget(fadeFinder.at(0));
// The handle should still be fully opaque.
expect(handle.opacity.value, equals(1.0));
});
testWidgets('Mouse long press is just like a tap', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController();
......
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