-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Text selection menu show/hide cases #35219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| } | ||
| _requestKeyboard(); | ||
| _confirmCurrentSplash(); | ||
| _editableTextKey.currentState.hideToolbar(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix 1/2: This fixes the problem where the selection menu could get stuck open. I had to add a check to hideToolbar to make sure that we're not hiding a toolbar that's already hidden, but it seems like that probably should have been there anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be safer to turn it off at the start of the function? although I don't think we turn on toolbar in selectPosition
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I like putting it at the top more, I'll make that change. It seems to work fine.
| if (!_value.selection.isValid) { | ||
| // Place cursor at the end if the selection is invalid when we receive focus. | ||
| widget.controller.selection = TextSelection.collapsed(offset: _value.text.length); | ||
| _handleSelectionChanged(TextSelection.collapsed(offset: _value.text.length), renderEditable, SelectionChangedCause.tap); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix 2/2: This fixes the case where an autofocused field couldn't ever display its selection menu. The reason was that the menu was never created.
However, this had an implication in our tests. I had to wrap a bunch of pumped test widgets in MaterialApp, because otherwise I was getting an error that there was no parent Overlay widget when it tried to create the selection menu. See the changes below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I just remembered that I used SelectionChangedCause.tap and it felt a bit hacky. This isn't isn't caused by a tap. Really, it's called when the widget is rebuilt and is automatically focused. Should I add a new value to the SelectionChangedCause enum?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel a bit hacky too, but I can't think of a good cause either. I wonder if it make sense to leave it null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. I thought it couldn't be null for some reason, but it seems to work fine.
| if (!_value.selection.isValid) { | ||
| // Place cursor at the end if the selection is invalid when we receive focus. | ||
| widget.controller.selection = TextSelection.collapsed(offset: _value.text.length); | ||
| _handleSelectionChanged(TextSelection.collapsed(offset: _value.text.length), renderEditable, SelectionChangedCause.tap); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel a bit hacky too, but I can't think of a good cause either. I wonder if it make sense to leave it null?
| } | ||
| _requestKeyboard(); | ||
| _confirmCurrentSplash(); | ||
| _editableTextKey.currentState.hideToolbar(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be safer to turn it off at the start of the function? although I don't think we turn on toolbar in selectPosition
|
Can you also add a regression test regarding #34215 ? |
|
Good call, I totally forgot to test those cases. I will add a couple to make sure they're covered. |
|
@chunhtai Updated with the tests. |
4ed393d to
77e75ad
Compare
| ); | ||
| }); | ||
|
|
||
| testWidgets( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is passing without the patch. Is the original issue already resolved?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After looking into this, the "single tap hides" test was passing because of strangeness with the tap location. Using a direct longPress(textFieldFinder) made the test fail on master and pass in this branch.
For the other test, "long press on autofocused", I could not get it to fail on master no matter what I tried. However, trying a sample app, the behavior looked as if the test should definitely fail. I gave up trying to make it fail and instead wrote an integration test that fails on master and passes on this branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think i know what is going on.
When you use longPressAt(textOffsetToPosition(tester, 0)), it will tap at the start of the text which is also part of the selection handle. this will trigger _handleSelectionHandleTapped which toggle the toolbar off. That is why it still passing in master.
As for long press on autofocused, add an additional pump after the pump widget will fail the test as the on focus callback will update the selection from the default (-1, -1) to (0,0) but it take one frame to propagate down to renderEditable. While the long press started, the renderEditable is still has selection(-1,-1) which allow it to update its selection
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Brilliant, that did in fact make it fail on master. Thanks so much. I will make that change and then remove the e2e test (since it's much slower).
|
@chunhtai Updated with tests that fail on master. |
|
@chunhtai Updated with your fix for the test that wasn't failing on master, thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it requires a pump here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
0b208df to
a22d636
Compare
|
@chunhtai Whoops sorry, I think I lost the pumps I added when I was switching to master to try it out. Fixed. |
chunhtai
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with a minor suggestion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: It might be more clear to add a comment describe why we need a pump here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: same as below.
a22d636 to
147ab98
Compare
- flutter#35219 tried to resolve this and autofocus, but missed the usecase where the user may empty out the field and then try to long-press to paste something. - When one taps on an empty text field, the long-press menu works, because: selection = TextSelection( baseOffset: -1, extentOffset: -1, affinity: TextAffinity.downstream, isDirectional: false, ) nextSelection = TextSelection( baseOffset: 0, extentOffset: 0, affinity: TextAffinity.downstream, isDirectional: false, ) - When one clears the text in a text field and then long-presses however, the menu doesn't appear because: selection = nextSelection = TextSelection( baseOffset: 0, extentOffset: 0, affinity: TextAffinity.downstream, isDirectional: false, ) and _handlePotentialSelectionChange() returns. - Check if baseOffset and extentOffset of current and next are both 0, and set current to -1, to replicate working behaviour.
Show and hide the text selection menu at the correct time with various gestures in the text field.
Description
There are various situations, described in the linked issues, where the text selection menu is not shown/hidden when it should be. This PR aims to get all cases right.
Related Issues
Closes #34215
Closes #33293
Probably should close #35837 too