-
Notifications
You must be signed in to change notification settings - Fork 29.7k
iOS 13 Context Menu #37778
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 13 Context Menu #37778
Conversation
|
It looks like this pull request may not have tests. Please make sure to add tests before merging. While there are exceptions to this rule, if this patch modifies code it is probably not an exception. Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
4a9fef5 to
346bd20
Compare
346bd20 to
2cc3813
Compare
0afdd9d to
5d534ee
Compare
d1abc7b to
96e9f0c
Compare
88e574e to
4f75892
Compare
867363b to
45fd739
Compare
171b77a to
d5ddf75
Compare
3c16d3d to
904447f
Compare
|
@HansMuller @LongCatIsLooong Ready for re-review. I've renamed to CupertinoContextMenu, changed One question I had: Is there a callback type that I can reuse instead of defining the PreviewBuilder and _PreviewBuilderChildless types ? |
HansMuller
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.
This looks good; just some small feedback plus: need to document/test ModalRoute.filter
| /// A function that produces the preview when the CupertinoContextMenu is open. | ||
| /// | ||
| /// Called every time the animation value changes. | ||
| typedef PreviewBuilder = Widget Function( |
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 should have a name that's a little more specific because inadvertant collisions. Maybe ContextMenuPreviewBuilder
| /// Widget build(BuildContext context) { | ||
| /// return Scaffold( | ||
| /// key: scaffoldKey, | ||
| /// body: Center( |
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.
My mistake, what you had is fine.
| /// The widget that can be "opened" with the [CupertinoContextMenu]. | ||
| /// | ||
| /// When the [CupertinoContextMenu] is long-pressed, the menu will open and | ||
| /// this widget (or the widget returned by [previewBuilder], if provided) will |
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 this may be revealing a detail that's not important: that the child/previewBuilder will be moved to the overlay. Can we explain what it means to "open" the menu just in terms of the route we're going to push? That approach jibes with using Navigator.pop() to close the menu.
|
|
||
| /// The actions that are shown in the menu. | ||
| /// | ||
| /// This parameter cannot be null, and items in the List must be |
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.
can't be null or empty
| /// sharp corners when in the page. | ||
| /// | ||
| /// In addition to the current [BuildContext], the function is also called | ||
| /// with an [Animation] and the [child]. The animation is used internally to |
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 "is used internally", you could explain that the animation goes from 0-1 when the menu opens, 1-0 when it closes.
| /// // it's closed. It uses the given animation to animate the corners | ||
| /// // in sync with the opening animation. | ||
| /// child: ClipRRect( | ||
| /// borderRadius: BorderRadius.circular(64.0 * animation.value), |
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.
NICE
| // child in the screen. | ||
| _ContextMenuOrientation get _contextMenuOrientation { | ||
| final Rect childRect = _getRect(_childGlobalKey); | ||
| final double screenWidth = MediaQuery.of(context).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.
What's the answer here?
|
|
||
| // A floating copy of the CupertinoContextMenu's child. | ||
| // | ||
| // When the child is pressed, but before the CupertinoContextMenu opens, it does |
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.
Thanks for the explanation!
| }) : _filter = filter, | ||
| super(settings: settings); | ||
|
|
||
| final ui.ImageFilter _filter; |
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.
Doc needed
This new route feature should also get its own test.
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 added a test to check that CupertinoContextMenu is able to add a BackdropFilter using this filter param, which was easiest since ModalRoute is abstract. Let me know if you had something else in mind.
| /// Below is an example of using `previewBuilder` to show an image tile that's | ||
| /// similar to each tile in the iOS iPhoto app's context menu. Several of | ||
| /// these could be used in a GridView for a similar effect. | ||
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.
Good catch
|
|
||
| class _CupertinoContextMenuState extends State<CupertinoContextMenu> with TickerProviderStateMixin { | ||
| final GlobalKey _childGlobalKey = GlobalKey(); | ||
| double _childOpacity = 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.
I wonder if we can use an Offstage and a TickerMode instead, like the Hero widget does?
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'm trying this out.
Actually @goderbauer I got this code from you awhile back. I was asking about animating to a final laid out state, and you suggested rendering the first frame offstage and sent me this gist: https://gist.github.com/goderbauer/b136e871fac33960dbd539ea1930ff97
Any idea if there is an advantage to Offstage and TickerMode instead?
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 think it's because PopupRoute is already using Offstage internally, and so this widget is just managing the offstage variable.
| child: Offstage( |
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.
What I originally meant was to use Offstage + TickerMode to hide the original child instead of Opacity. Should we stop the animation in the original child when it's hidden, by adding a TickerMode parent? As for Offstage vs Opacity they seem interchangeable?
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 I see, sorry! That makes more sense and is probably more performant. I'll change it.
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.
Actually after changing it something broke. I was getting the size of the Opacity widget using a GlobalKey, and I couldn't do that with an Offstage widget (even when it was onstage). It gave me weird values and broke the tests.
My compromise is to use a TickerMode and an Opacity widget together, but let me know if you know why getting the size wasn't working.
| /// | ||
| /// This parameter cannot be null, and items in the List must be | ||
| /// [CupertinoContextMenuAction]s. | ||
| final List<CupertinoContextMenuAction> actions; |
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.
Might have overlooked something, but CupertinoContextMenu's implementation does not seem to make use of the fact that actions are CupertinoContextMenuActions. Is it possible to relax the type constraint to List<Widget>? According to the sketch file the actions can be grouped, and group separators look quite different from regular ones, so I wonder if we can make this a list of 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 there is nothing preventing us from using List<Widget> here. I did it just to encourage users to always use CupertinoContextMenuActions, but I also didn't realize there were group separators. Does Flutter usually allow any type of widget when it can?
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's an issue for this: #12078. Also I tried adding a context menu to a UISegmentedControl, and it turns out other than group separators there can also be headers (similar to action sheet headers)
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 that's more in line with how we usually do it then changing it sounds good to me.
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 sorry I posted my last comment before Github updated to show yours. That issue definitely confirms that this should be agnostic so I'm changing it now 👍
| flex: 2, | ||
| child: IntrinsicHeight( | ||
| child: ClipRRect( | ||
| borderRadius: BorderRadius.circular(16.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.
Per xcode the value is 13.
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.
Thank you!
Codecov Report
@@ Coverage Diff @@
## master #37778 +/- ##
==========================================
- Coverage 61.77% 61.62% -0.15%
==========================================
Files 199 195 -4
Lines 19276 19016 -260
==========================================
- Hits 11907 11718 -189
+ Misses 7369 7298 -71
Continue to review full report at Codecov.
|
| final Widget child; | ||
|
|
||
| /// The actions that are shown in the menu. | ||
| /// |
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 would be a good place to say that typically the actions are [CupertinoContextMenuAction]s.
HansMuller
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
|
Closing in favor of: #43918 |
Description
This PR implements the
ContextMenuwidget.Related Issues
Closes #34728
Tests