-
Notifications
You must be signed in to change notification settings - Fork 27.2k
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
iOS Text Selection Menu Overflow #54140
Conversation
commit 729501d Author: Justin McCandless <jmccandless@google.com> Date: Mon Apr 6 09:16:32 2020 -0700 Trying to fix CL failure with update-packages --force-upgrade commit 0cbc472 Author: Justin McCandless <jmccandless@google.com> Date: Fri Apr 3 11:09:38 2020 -0700 Comments commit 43cbd77 Author: Justin McCandless <jmccandless@google.com> Date: Fri Apr 3 11:05:18 2020 -0700 Custom long locale test commit 3b5a7e4 Author: Justin McCandless <jmccandless@google.com> Date: Fri Apr 3 09:38:40 2020 -0700 Clean up code, fix test with incorrect width. commit 491ed03 Author: Justin McCandless <jmccandless@google.com> Date: Thu Apr 2 16:28:15 2020 -0700 On selectall, page resets. Width maxes out at first page width. commit c690d50 Merge: 6c9bd26 e2621bf Author: Justin McCandless <jmccandless@google.com> Date: Thu Apr 2 14:43:41 2020 -0700 Merge branch 'master' into selection-overflow-ios commit 6c9bd26 Author: Justin McCandless <jmccandless@google.com> Date: Thu Apr 2 14:41:29 2020 -0700 Slot naming commit 90aa6b3 Author: Justin McCandless <jmccandless@google.com> Date: Thu Apr 2 14:39:38 2020 -0700 debugDescribeChildren commit 0126737 Author: Justin McCandless <jmccandless@google.com> Date: Thu Apr 2 14:32:44 2020 -0700 Reimplement dividers as empty space commit 56653a5 Author: Justin McCandless <jmccandless@google.com> Date: Thu Apr 2 11:12:34 2020 -0700 Fix debugImplementOnstageChildren so tests work commit 4dc4d29 Author: Justin McCandless <jmccandless@google.com> Date: Wed Apr 1 16:22:38 2020 -0700 Fix analyze errors commit 3cf51ec Author: Justin McCandless <jmccandless@google.com> Date: Wed Apr 1 15:54:05 2020 -0700 Hit test slot children as well, allowing pages to work commit 305e3b9 Author: Justin McCandless <jmccandless@google.com> Date: Wed Apr 1 15:31:23 2020 -0700 Fix crash when dismissing menu due to duplicate detach commit ee3294d Author: Justin McCandless <jmccandless@google.com> Date: Wed Mar 25 17:00:08 2020 -0700 Painting slotted and list children together, but no divider yet, no changing page. commit e0640f5 Author: Justin McCandless <jmccandless@google.com> Date: Wed Mar 25 11:48:25 2020 -0700 Rendering properly in the simple case of no overflow commit 56122a4 Author: Justin McCandless <jmccandless@google.com> Date: Tue Mar 24 16:24:25 2020 -0700 Slots for divider and buttons. Compiles but needs rendering support. commit be69380 Author: Justin McCandless <jmccandless@google.com> Date: Fri Mar 20 09:25:06 2020 -0700 Divider infrastructure commit 0f2bd59 Author: Justin McCandless <jmccandless@google.com> Date: Fri Mar 20 08:47:38 2020 -0700 Share parent data between material and cupertino commit de84e9b Author: Justin McCandless <jmccandless@google.com> Date: Thu Mar 19 08:20:04 2020 -0700 Keep const while passing linter commit cdf9ab6 Author: Justin McCandless <jmccandless@google.com> Date: Wed Mar 18 14:25:12 2020 -0700 Code cleanup commit f3ff782 Author: Justin McCandless <jmccandless@google.com> Date: Wed Mar 18 14:11:33 2020 -0700 Don't create menu if no buttons commit 21b262a Author: Justin McCandless <jmccandless@google.com> Date: Wed Mar 18 14:03:46 2020 -0700 Full test suite working (with dividers disabled for now commit c1da1db Author: Justin McCandless <jmccandless@google.com> Date: Wed Mar 18 11:02:34 2020 -0700 Tests (some WIP) and shouldPaint commit 6acf592 Merge: a12d1e7 a811bce Author: Justin McCandless <jmccandless@google.com> Date: Wed Mar 18 08:48:20 2020 -0700 Merge branch 'master' into selection-overflow-ios commit a12d1e7 Author: Justin McCandless <jmccandless@google.com> Date: Tue Mar 10 12:58:37 2020 -0700 Long child at any position works. commit 898e946 Author: Justin McCandless <jmccandless@google.com> Date: Mon Mar 9 15:35:04 2020 -0700 First long child fits and is ellided commit 267feb3 Author: Justin McCandless <jmccandless@google.com> Date: Mon Mar 9 14:55:45 2020 -0700 Next arrow always shows on subsequent pages, but may be disabled commit a5388c5 Author: Justin McCandless <jmccandless@google.com> Date: Mon Mar 9 09:48:34 2020 -0700 Back and forward buttons work commit 385174f Author: Justin McCandless <jmccandless@google.com> Date: Thu Mar 5 15:36:13 2020 -0800 Rendering by page, but needs hit detection and cleanup commit d512941 Author: Justin McCandless <jmccandless@google.com> Date: Fri Feb 28 11:05:41 2020 -0800 A working renderbox setup, but fading and pages is broken right now commit 113638f Author: Justin McCandless <jmccandless@google.com> Date: Fri Feb 28 10:40:06 2020 -0800 Animate between pages commit 55b7ea6 Author: Justin McCandless <jmccandless@google.com> Date: Fri Feb 28 09:49:10 2020 -0800 Proof of concept menu with multiple pages
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 only review the widget part of it. will review the renderobject later
@required this.children, | ||
@required this.isArrowPointingDown, | ||
}) : assert(children != null), | ||
assert(children.length > 0), // ignore: prefer_is_empty |
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.
Is this to get around the const?
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.
Please add a comment to the ignore explaining why it is there per our style guide.
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.
Yes, .isNotEmpty
isn't const. I'll add a comment about it.
} | ||
|
||
void _listener() { | ||
if (_controller.value != 0.0) { |
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.
If this only trigger when animation finishes, you can use addStatusListener
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.
That should be more performant, thanks for the tip 👍
void _handlePreviousPage() { | ||
_controller.reverse(); | ||
_controller.addListener(_listener); | ||
setState(() { |
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.
do we need setstate here? it seems the the only _page is used in building the widget
void _handleNextPage() { | ||
_controller.reverse(); | ||
_controller.addListener(_listener); | ||
setState(() { |
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
void didUpdateWidget(_CupertinoTextSelectionToolbarContent oldWidget) { | ||
// If the children are changing, the current page should be reset. | ||
if (widget.children != oldWidget.children) { | ||
_page = 0; |
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 wonder if we need to worry about if the animation is on going
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.
That's a great catch, it would be potentially possible for the animation to navigate to a non-existent page. I'll fix this by resetting the animation here.
// This was eyeballed on a physical iOS device running iOS 13. | ||
duration: const Duration(milliseconds: 150), | ||
); | ||
super.initState(); |
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: this should be at the start of the method.
@@ -16,9 +16,9 @@ import 'package:flutter/foundation.dart'; | |||
import 'package:flutter/gestures.dart' show DragStartBehavior, PointerDeviceKind; | |||
|
|||
import '../rendering/mock_canvas.dart'; | |||
import '../text.dart' show findRenderEditable, globalize, textOffsetToPosition; |
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.
putting this file in the toplevel test directory feels odd. Can we stick it in tests/widgets since they seem to include util functions for dealing with EditableText widgets?
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.
Yeah good call.
@required this.children, | ||
@required this.isArrowPointingDown, | ||
}) : assert(children != null), | ||
assert(children.length > 0), // ignore: prefer_is_empty |
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.
Please add a comment to the ignore explaining why it is there per our style guide.
borderRadius: null, | ||
color: _kToolbarBackgroundColor, | ||
minSize: _kToolbarHeight, | ||
onPressed: () {}, |
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 would have expected a disabled button to have null
as onPressed
handler. Why is it an empty closure?
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.
Ah, I did this because the default disabled background color didn't look right. I can fix this with disabledColor though, so I'll remove this onPressed.
pressedOpacity: 0.7, | ||
child: const Text('▶', style: _kToolbarButtonFontStyle), | ||
), | ||
nextButtonDisabled: CupertinoButton( |
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.
for my own understanding: When does the toolbar show a disabled button? I thought if there is no page to go to it just doesn't show a button?
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.
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.
Interesting. I never noticed that. Thanks for the explanation.
@override | ||
void initState() { | ||
_controller = AnimationController( | ||
value: 1.0, |
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.
initially, the toolbar doesn't fade in? It just appears?
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.
Initially the entire toolbar fades in. This controller handles the fading of the buttons within the toolbar. During page changes, the toolbar background stays visible, but the buttons fade in/out inside of it. There is another existing widget that handles fading the entire toolbar when it shows/hides.
I'll add a comment to the declaration of _controller about what it does.
I also noticed there's a slight existing bug with transitioning between states that's somewhat related to this. I opened a new issue: #54470
i++; | ||
final RenderBox child = renderObjectChild as RenderBox; | ||
final ToolbarItemsParentData childParentData = child.parentData as ToolbarItemsParentData; | ||
childParentData.shouldPaint = false; |
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.
Is this necessary when it gets overwritten by line 931?
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.
Now it is! Since I made the optimization above where I don't layout all children, line 931 doesn't get run on all children.
_nextButton.layout(constraints.loosen(), parentUsesSize: true); | ||
_nextButtonDisabled.layout(constraints.loosen(), parentUsesSize: true); | ||
|
||
double buttonPosition = 0.0; |
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.
When first reading through this code I was a little confused by the variable naming of buttonPosition and buttonsWidth. buttonsWidth only includes the width of the forward/back button, where-as buttonPosition is for all buttons.
Not super-sure how to make this better. Maybe call this one nextButtonPostition? And maybe the other paginationButtonWidth?
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 struggled with coming up with good names when writing this. I'm going to go with currentButtonPosition since nextButtonPosition could be interpreted as the position of the right arrow. paginationButtonsWidth sounds good.
_nextButtonDisabled.layout(constraints.loosen(), parentUsesSize: true); | ||
|
||
double buttonPosition = 0.0; | ||
double parentWidth = constraints.maxWidth; // The width of the whole widget. |
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.
maybe: toolbarWidth?
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.
Why does it start out as the max width possible instead of zero?
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 was using it as a constraint earlier and then I changed my code, good call.
@override | ||
void attach(PipelineOwner owner) { | ||
super.attach(owner); | ||
visitChildren((RenderObject renderObjectChild) { |
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.
Will this not attach the list children twice? From the supercall and here again?
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.
Yes, good catch.
super.detach(); | ||
|
||
// Detach slot children. | ||
visitChildren((RenderObject renderObjectChild) { |
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.
Instead of iterating over all children, couldn't you just iterate over the slot children in childToSlot?
@chunhtai @goderbauer Thank you guys! It's ready for re-review when you have time. |
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
// Returns true iff the button is visually enabled. | ||
bool appearsEnabled(WidgetTester tester, String text) { | ||
final CupertinoButton button = tester.widget<CupertinoButton>( | ||
|
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: remove this blank line?
} | ||
|
||
@override | ||
void initState() { |
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: order-wise it would be nice to place initState before dispose and didUpdateWidget.
void didUpdateWidget(_CupertinoTextSelectionToolbarContent oldWidget) { | ||
// If the children are changing, the current page should be reset. | ||
if (widget.children != oldWidget.children) { | ||
_page = 0; |
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.
Maybe also reset _nextPage to null? (I don't think this is a visual bug, but it may be strange during debugging if this is set to something that's no longer valid.)
pressedOpacity: 0.7, | ||
child: const Text('▶', style: _kToolbarButtonFontStyle), | ||
), | ||
nextButtonDisabled: CupertinoButton( |
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.
Interesting. I never noticed that. Thanks for the explanation.
_nextButtonDisabled.layout(constraints.loosen(), parentUsesSize: true); | ||
|
||
final double subsequentPageButtonsWidth = | ||
_backButton.size.width + _nextButton.size.width; |
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: either leave this on the previous line or indent by 4
buttonsWidth = _backButton.size.width + _nextButton.size.width; | ||
} | ||
|
||
// The width of the menu is set by the first page. |
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.
Interesting observation. Thanks for the gif.
assert(page <= currentPage); | ||
|
||
// Position page nav buttons. | ||
final ToolbarItemsParentData nextButtonParentData = |
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: these three vars could all be declared inside the if (currentPage > 0) {
, no?
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 catch 👍
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
child: Text(text, style: _kToolbarButtonFontStyle), | ||
child: Text( | ||
text, | ||
overflow: TextOverflow.ellipsis, |
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.
Just a FYI, this currently only work with word boundary. If for some reason the space is not enough, copy -> ... Paste -> ... select all -> select ...
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.
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.
oh nvm it seems you are right.
Description
If the selection menu buttons are too wide for their available space, they will cause an overflow error. This PR implements the native iOS solution for this problem, which paginates overflowing items.
Related Issues
Closes #35826
Android solution: #49391
Reopened from #52852 to fix weird versioning test failures.
Tests
Breaking Change
None.