[EditableText] call onSelectionChanged only when there are actual selection/cause changes#87971
Conversation
selection/cause changes Fixes flutter#76349. Also cleaned up the `updateEditingValue`/`userUpdateEditingValue` paths a bit. Hopefully we will be able to merge the two.
onSelectionChanged when there's actual selection/cause changesonSelectionChanged only when there're actual selection/cause changes
| bool _wasSelectingVerticallyWithKeyboard = false; | ||
|
|
||
| void _setTextEditingValue(TextEditingValue newValue, SelectionChangedCause cause) { | ||
| textSelectionDelegate.textEditingValue = newValue; |
There was a problem hiding this comment.
I kept this for backward compatibility, we should only remove it once we remove the deprecated setter textEditingValue
There was a problem hiding this comment.
Could you elaborate? People are required to implement userUpdateTextEditingValue right? It's part of the TextSelectionDelegate interface.
There was a problem hiding this comment.
I didn't notice userUpdateTextEditingValue is abstract method. yeah this should be fine.
| ScrollController? _scrollController; | ||
|
|
||
| late AnimationController _cursorBlinkOpacityController; | ||
| late final AnimationController _cursorBlinkOpacityController = AnimationController(vsync: this, duration: _fadeDuration); |
There was a problem hiding this comment.
I think we missed this but it should be disposed in the dispose method.
same for _floatingCursorResetController. I think the existing format is cleaner, it is weird the we give a default value in here and then attach listener in initState
There was a problem hiding this comment.
Whoops good catch about the dispose.
There was a problem hiding this comment.
moved the addListener to the lazy initializer. My initial goal was to mark the property as final. I'll do the same for other animation controllers in #87973.
| ? _value.selection != value.selection | ||
| : _value != value; | ||
| if (shouldShowCaret) { | ||
| _scheduleShowCaretOnScreen(); |
There was a problem hiding this comment.
I didnt get the logic, the _scheduleShowCaretOnScreen will be called in line 1770 anyway?
There was a problem hiding this comment.
Ah I forgot to remove the existing _sheduleShowCaretOnScreen when copied the other one over.
| await gesture.moveBy(const Offset(600, 0)); | ||
| // To the edge of the screen basically. | ||
| await tester.pump(); | ||
| await tester.pumpAndSettle(); |
There was a problem hiding this comment.
where do we introduce frame delays?
There was a problem hiding this comment.
There's no need for these changes. Removed.
justinmc
left a comment
There was a problem hiding this comment.
LGTM 👍 . This was badly needed, thanks for cleaning this up.
| void onDoubleTapDown(TapDownDetails details) { | ||
| if (delegate.selectionEnabled) { | ||
| renderEditable.selectWord(cause: SelectionChangedCause.tap); | ||
| renderEditable.selectWord(cause: SelectionChangedCause.doubleTap); |
|
|
||
| await testTextEditing(tester, targetPlatform: defaultTargetPlatform); | ||
|
|
||
| debugKeyEventSimulatorTransitModeOverride = null; |
There was a problem hiding this comment.
Can you remove this line now that you have the teardown?
There was a problem hiding this comment.
Same with the next test below.
There was a problem hiding this comment.
This doesn't seem to work, _verifyInvariants is called before the the tear down callbacks are invoked.
There was a problem hiding this comment.
Ah ok interesting, best to keep both then 👍
| index++; | ||
| } | ||
|
|
||
| expect( |
| expect(selection, const TextSelection(baseOffset: 1, extentOffset: 2)); | ||
| expect(cause, SelectionChangedCause.keyboard); | ||
|
|
||
| // Selection and cause both changed |
| _handleSelectionChanged(TextSelection.collapsed(offset: _lastTextPosition!.offset), SelectionChangedCause.forcePress); | ||
| final TextEditingValue newValue = _value.copyWith( | ||
| selection: TextSelection.fromPosition(_lastTextPosition!), | ||
| ); |
There was a problem hiding this comment.
The new code preserves the TextAffinity of _lastTextPosition, but the old code didn't. Probably better this way but something to be aware of.
There was a problem hiding this comment.
The floating cursor stuff currently doesn't care much about affinity (e.g., final TextPosition currentTextPosition = TextPosition(offset: renderEditable.selection!.baseOffset);). I'm hoping we can take cursor handling out of EditableTextState and maybe clean floating cursor a bit in the process.
| Rect? composingRect = renderEditable.getRectForComposingRange(composingRange); | ||
| // Send the caret location instead if there's no marked text yet. | ||
| if (composingRect == null) { | ||
| assert(!composingRange.isValid || composingRange.isCollapsed); |
There was a problem hiding this comment.
Why was this assert removed?
There was a problem hiding this comment.
I must have thought getRectForComposingRange only returns null only when !composingRange.isValid || composingRange.isCollapsed is true but if getBoxesForSelection returns an empty list that method returns null too. It's triggered in one of the tests so I removed it.
| // This method is similar to updateEditingValue, but is used to handle user | ||
| // input caused by hardware keyboard events and gesture events, while | ||
| // updateEditingValue handles IME/software keyboard input. |
There was a problem hiding this comment.
Thank you for adding the comment here!
| _currentPromptRectRange = null; | ||
|
|
||
| if (_hasInputConnection) { | ||
| if (widget.obscureText && value.text.length == _value.text.length + 1) { |
There was a problem hiding this comment.
I got really worried about extended grapheme clusters and obscured fields when I saw this + 1 here (even though I know it has nothing to do with this PR). Indeed they show up as multiple characters when entered into an obscured field and don't have a delay, but it turns out that's the behavior on native too 🤷 . All is well.
| ScrollController? _scrollController; | ||
|
|
||
| late AnimationController _cursorBlinkOpacityController; | ||
| late final AnimationController _cursorBlinkOpacityController = AnimationController(vsync: this, duration: _fadeDuration); |
There was a problem hiding this comment.
Whoops good catch about the dispose.
b04b362 to
d751b73
Compare
d751b73 to
f674e17
Compare
…lection/cause changes (flutter#87971)
…ctual selection/cause changes (flutter#87971)" (flutter#88183)
onSelectionChanged only when there're actual selection/cause changesonSelectionChanged only when there are actual selection/cause changes
Fixes #76349.
Also cleaned up the
updateEditingValue/userUpdateEditingValuepaths a bit. Hopefully we will be able to merge the two.
Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.
List which issues are fixed by this PR. You must list at least one issue.
If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.