Skip to content

Material scaffold to have simultaneous left-and-right drawers#12686

Merged
abarth merged 15 commits intoflutter:masterfrom
carnivorestudios:chq_scaffold
Nov 2, 2017
Merged

Material scaffold to have simultaneous left-and-right drawers#12686
abarth merged 15 commits intoflutter:masterfrom
carnivorestudios:chq_scaffold

Conversation

@5u3it
Copy link
Contributor

@5u3it 5u3it commented Oct 24, 2017

Material scaffold to have simultaneous left and right drawers

"author" : "xcode"
}
}
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like the IDE made some unexpected changes here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^^^

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@Hixie
Copy link
Contributor

Hixie commented Oct 24, 2017

Some high-level comments:

  • rather than "left drawer" and "right drawer" we should probably call these "drawer" and "end-side drawer". In right-to-left environments (e.g. Arabic, Hebrew) the "left" drawer ends up on the right and the "right" drawer ends up on the left, so rather than call them "left" and "right" we normally refer to the sides as "start" and "end".
  • rather than rename the left (start) drawer, we should leave it named drawer so that we don't break existing applications.
  • since we need to be agnostic about the side anyway (start vs end), we can just have a single controller class that handles both sides (like it does today, in fact), and instantiate it with a different argument for the "right" (end) side.

@Hixie
Copy link
Contributor

Hixie commented Oct 24, 2017

We'll need some tests, too.

@5u3it
Copy link
Contributor Author

5u3it commented Oct 25, 2017

@Hixie Thanks for the feedback. Following points have been incorporated:

  • rather than "left drawer" and "right drawer" we should probably call these "drawer" and "end-side drawer". In right-to-left environments (e.g. Arabic, Hebrew) the "left" drawer ends up on the right and the "right" drawer ends up on the left, so rather than call them "left" and "right" we normally refer to the sides as "start" and "end": DONE
  • rather than rename the left (start) drawer, we should leave it named drawer so that we don't break existing applications: DONE
  • since we need to be agnostic about the side anyway (start vs end), we can just have a single controller class that handles both sides (like it does today, in fact), and instantiate it with a different argument for the "right" (end) side: DONE

@sethladd sethladd added framework flutter/packages/flutter repository. See also f: labels. c: new feature Nothing broken; request for a new capability labels Oct 25, 2017
void _handleDrawerButton() {
Scaffold.of(context).openDrawer();
void _handleDrawerButtonLeft() {
Scaffold.of(context).openLeftDrawer();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two functions still refer to the "left" drawer, which isn't an accurate name in languages with right-to-left reading orders.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

icon: const Icon(Icons.menu),
onPressed: _handleDrawerButtonEndSide,
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the mean the button to open the end-side drawer appears only when there are no other actions in the app bar?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is the assumption.

import 'material.dart';
import 'scaffold.dart';

enum DrawerType { START, END }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

return;
} else {
close();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

_controller.value += delta;
break;
if (widget.type == DrawerType.START) {
if (Scaffold.of(context).isEndSideDrawerOpen() == true) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

case TextDirection.ltr:
_controller.value += delta;
break;
if (widget.type == DrawerType.START) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


} else if (_controller.value < 0.5) {
close();
if (widget.type == DrawerType.START) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

_controller.fling(velocity: -1.0);
}

bool isOpen() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function doesn't appear to be needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

return new Align(
alignment: AlignmentDirectional.centerStart,
alignment: (widget.type == DrawerType.START) ?
AlignmentDirectional.centerStart : AlignmentDirectional.centerEnd,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd break these out into private getters and use a switch statement instead of operator== on the enum.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

/// [ScaffoldState.openDrawer] function.
///
/// Typically a [Drawer].
final Widget endSideDrawer;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does side mean in this context? A better name might be endDrawer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

///
/// See [Scaffold.of] for information about how to obtain the [ScaffoldState].
void openDrawer() {
void opendrawer() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/opendrawer/openDrawer/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

} else {
return false;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea was to have only one of the drawers open at a time. Otherwise the drawers will overlap on top of one another.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'll check up on this. Thanks a lot for the feedback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@abarth
Copy link
Contributor

abarth commented Oct 25, 2017

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 }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/START/start/
s/END/end/

We use camelCase for enum values. Also, please add dartdoc for all public identifiers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

///
/// Typically a [Drawer].
final Widget child;
final DrawerAlignment type;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing dartdoc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, should probably be named "alignment" now that the type is called DrawerAlignment rather than DrawerType

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

if (canPop) {
Navigator.of(context).pop();
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

case DrawerAlignment.end:
visualVelocity = -visualVelocity;
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

/// devices.
///
/// devices. Swipes in from either left-to-right (LTR languages) or
/// right-to-left (RTL languages)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


/// Whether this scaffold has a non-null [Scaffold.drawer].
bool get hasDrawer => widget.drawer != null;
bool get hasEndDrawer => widget.endDrawer != null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing dartdoc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@5u3it
Copy link
Contributor Author

5u3it commented Nov 1, 2017

@abarth Do you have any more feedback?

@required this.child,
}) : assert(child != null),
@required this.alignment,
}) : assert(child != null || alignment != null),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

final ColorTween _color = new ColorTween(begin: Colors.transparent, end: Colors.black54);
final GlobalKey _gestureDetectorKey = new GlobalKey();

AlignmentDirectional get _drawerStartAlignment {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@abarth
Copy link
Contributor

abarth commented Nov 1, 2017

LGTM

Just a couple minor changes and we can land this patch. Thanks so much for sticking with it!

@5u3it
Copy link
Contributor Author

5u3it commented Nov 2, 2017

@abarth Thanks a lot for your feedback. Your latest feedback has been incorporated.

};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've still got spurious changes to these two files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -1086,14 +1143,16 @@ class PersistentBottomSheetController<T> extends ScaffoldFeatureController<_Pers
class _ScaffoldScope extends InheritedWidget {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes to _ScaffoldScope aren't needed anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted the changes

@abarth
Copy link
Contributor

abarth commented Nov 2, 2017

Sorry I missed that the changes to _ScaffoldScope aren't need anymore in my last review :(

@5u3it
Copy link
Contributor Author

5u3it commented Nov 2, 2017

@abarth Please could be clarify what you mean by "_ScaffoldScope changes aren't needed"?

@abarth
Copy link
Contributor

abarth commented Nov 2, 2017

Please could be clarify what you mean by "_ScaffoldScope changes aren't needed"?

You've add an hasEndDrawer property to _ScaffoldScope, but that property isn't used for anything. You can undo your changes to _ScaffoldScope and everything will continue to work the same way.

@5u3it
Copy link
Contributor Author

5u3it commented Nov 2, 2017

@abarth Reverted the _ScaffoldScope changes

@abarth
Copy link
Contributor

abarth commented Nov 2, 2017

Yay! Thanks so much for the patch and all the revisions.

@abarth abarth merged commit c608e66 into flutter:master Nov 2, 2017
@5u3it
Copy link
Contributor Author

5u3it commented Nov 2, 2017

Thanks for working alongside :)

@abarth abarth mentioned this pull request Nov 2, 2017
DaveShuckerow pushed a commit to DaveShuckerow/flutter that referenced this pull request May 14, 2018
…r#12686)

Adds `Scaffold#endDrawer` property to supply a second drawer to a Scaffold.
@lsi8
Copy link

lsi8 commented Dec 10, 2019

Please give issues #39736 & #34151 some attention ):

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 2, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

c: new feature Nothing broken; request for a new capability framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants