Material scaffold to have simultaneous left-and-right drawers#12686
Material scaffold to have simultaneous left-and-right drawers#12686abarth merged 15 commits intoflutter:masterfrom
Conversation
| "author" : "xcode" | ||
| } | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
looks like the IDE made some unexpected changes here
|
Some high-level comments:
|
|
We'll need some tests, too. |
|
@Hixie Thanks for the feedback. Following points have been incorporated:
|
| void _handleDrawerButton() { | ||
| Scaffold.of(context).openDrawer(); | ||
| void _handleDrawerButtonLeft() { | ||
| Scaffold.of(context).openLeftDrawer(); |
There was a problem hiding this comment.
These two functions still refer to the "left" drawer, which isn't an accurate name in languages with right-to-left reading orders.
| icon: const Icon(Icons.menu), | ||
| onPressed: _handleDrawerButtonEndSide, | ||
| tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip, | ||
| ); |
There was a problem hiding this comment.
Does the mean the button to open the end-side drawer appears only when there are no other actions in the app bar?
There was a problem hiding this comment.
Yes, that is the assumption.
| import 'material.dart'; | ||
| import 'scaffold.dart'; | ||
|
|
||
| enum DrawerType { START, END } |
There was a problem hiding this comment.
s/START/start/
s/END/end/
I wonder if DrawerAlignment would be a better name for this enum. You could imagine DrawerType as something more general (e.g., floating, expanding, circular).
| return; | ||
| } else { | ||
| close(); | ||
| } |
There was a problem hiding this comment.
DrawerController already knows how to flip around based on the reading direction. Rather than plumb this logic in this way, maybe it would make sense to flip the drawer controller around in the same way.
Specifically, I'd recommend keeping _controller.value logically "closed" and just change the visual presentation of the logical state. That way _controller.isDismissed still corresponds to the drawer being closed, which makes more sense semantically.
| _controller.value += delta; | ||
| break; | ||
| if (widget.type == DrawerType.START) { | ||
| if (Scaffold.of(context).isEndSideDrawerOpen() == true) { |
There was a problem hiding this comment.
There should be no need to interact with the Scaffold here. The DrawerController should not have any knowledge of the Scaffold and should work independently of it.
| case TextDirection.ltr: | ||
| _controller.value += delta; | ||
| break; | ||
| if (widget.type == DrawerType.START) { |
There was a problem hiding this comment.
Rather than break down these case by DrawerAlignment. We should translate the visual delta into a logical valueDelta by (possibly) negating it once for DrawerAlignment and (possibly) again for Directionality. Then we can apply the valueDelta to _controller.value on a single line of code.
|
|
||
| } else if (_controller.value < 0.5) { | ||
| close(); | ||
| if (widget.type == DrawerType.START) { |
There was a problem hiding this comment.
Same comment here. Rather than duplicating all this code, we can translate visualVelocity into valueVelocity by first considering the DrawerAlignment and then considering the Directionality. Once we do that, we can fling the _controller using the commuted valueVelocity.
| _controller.fling(velocity: -1.0); | ||
| } | ||
|
|
||
| bool isOpen() { |
There was a problem hiding this comment.
This function doesn't appear to be needed.
| return new Align( | ||
| alignment: AlignmentDirectional.centerStart, | ||
| alignment: (widget.type == DrawerType.START) ? | ||
| AlignmentDirectional.centerStart : AlignmentDirectional.centerEnd, |
There was a problem hiding this comment.
I'd break these out into private getters and use a switch statement instead of operator== on the enum.
| /// [ScaffoldState.openDrawer] function. | ||
| /// | ||
| /// Typically a [Drawer]. | ||
| final Widget endSideDrawer; |
There was a problem hiding this comment.
What does side mean in this context? A better name might be endDrawer
| /// | ||
| /// See [Scaffold.of] for information about how to obtain the [ScaffoldState]. | ||
| void openDrawer() { | ||
| void opendrawer() { |
| } else { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
It looks like you're using these functions to couple the behavior of the start and end drawers. The way you've done this introduces too tight a coupling. I'm not clear on what behavior you're trying to create. Can you explain what end-user visible behavior you're trying to achieve?
There was a problem hiding this comment.
The idea was to have only one of the drawers open at a time. Otherwise the drawers will overlap on top of one another.
There was a problem hiding this comment.
That makes sense, but there are a bunch of different interaction designs that might achieve that goal. For example, you could have an edge-trigger on each drawer's animation leaving the isDismissed state that triggers the other drawer to close() (and cancels any current gestures interacting with that drawer).
There was a problem hiding this comment.
Sure. I'll check up on this. Thanks a lot for the feedback.
|
We'll also need tests, including ones that exercise the interaction between start and end drawers as well as covering the LTR and RTL cases. |
| import 'list_tile.dart'; | ||
| import 'material.dart'; | ||
|
|
||
| enum DrawerAlignment { START, END } |
There was a problem hiding this comment.
s/START/start/
s/END/end/
We use camelCase for enum values. Also, please add dartdoc for all public identifiers.
| /// | ||
| /// Typically a [Drawer]. | ||
| final Widget child; | ||
| final DrawerAlignment type; |
There was a problem hiding this comment.
Also, should probably be named "alignment" now that the type is called DrawerAlignment rather than DrawerType
| if (canPop) { | ||
| Navigator.of(context).pop(); | ||
| return; | ||
| } |
There was a problem hiding this comment.
I don't think this interaction design is of sufficient quality to land. We can land the patch without an interaction design for the two-drawer case or we can figure out an interaction design for the two-drawer case in this patch, but I'd prefer not to land a patch with a half-designed interaction.
There was a problem hiding this comment.
Okay.
I have not been able to get the behavior working via addStatusListener on the _controller.
Hence, I'm removing this logic from _move for now.
| case DrawerAlignment.end: | ||
| delta = -delta; | ||
| break; | ||
| } |
There was a problem hiding this comment.
I'm surprised the analyzer doesn't complain about the start case not being handled. In any case, the idiom we prefer is to handle the start case (just break; is fine).
| case DrawerAlignment.end: | ||
| visualVelocity = -visualVelocity; | ||
| break; | ||
| } |
| /// devices. | ||
| /// | ||
| /// devices. Swipes in from either left-to-right (LTR languages) or | ||
| /// right-to-left (RTL languages) |
There was a problem hiding this comment.
This is a tiny nit, but I'd replace "LTR languages" with "[TextDirection.ltr]" (and the same for "RTL languages") so that the dartdocs link back to the TextDirection docs, which explain more about what's going on here.
|
|
||
| /// Whether this scaffold has a non-null [Scaffold.drawer]. | ||
| bool get hasDrawer => widget.drawer != null; | ||
| bool get hasEndDrawer => widget.endDrawer != null; |
|
@abarth Do you have any more feedback? |
| @required this.child, | ||
| }) : assert(child != null), | ||
| @required this.alignment, | ||
| }) : assert(child != null || alignment != null), |
There was a problem hiding this comment.
Do you really mean || here? I would have expected
assert(child != null),
assert(alignment != null),Which is the "and" rather than "or" of these conditions.
| final ColorTween _color = new ColorTween(begin: Colors.transparent, end: Colors.black54); | ||
| final GlobalKey _gestureDetectorKey = new GlobalKey(); | ||
|
|
||
| AlignmentDirectional get _drawerStartAlignment { |
There was a problem hiding this comment.
The term "start" here is a bit confusing because "start" and "end" are possible alignment values themselves. For example, it's confusing that a "start alignment" would return "centerEnd".
Maybe _drawerOuterAlignment for this one and _drawerInnerAlignment for the one below?
|
@abarth Thanks a lot for your feedback. Your latest feedback has been incorporated. |
| }; | ||
| rootObject = 97C146E61CF9000F007C117D /* Project object */; | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
You've still got spurious changes to these two files.
| @@ -1086,14 +1143,16 @@ class PersistentBottomSheetController<T> extends ScaffoldFeatureController<_Pers | |||
| class _ScaffoldScope extends InheritedWidget { | |||
There was a problem hiding this comment.
These changes to _ScaffoldScope aren't needed anymore.
|
Sorry I missed that the changes to |
|
@abarth Please could be clarify what you mean by " |
You've add an |
|
@abarth Reverted the |
|
Yay! Thanks so much for the patch and all the revisions. |
|
Thanks for working alongside :) |
…r#12686) Adds `Scaffold#endDrawer` property to supply a second drawer to a Scaffold.

Material scaffold to have simultaneous left and right drawers