Unverified Commit d2ab29d2 authored by jslavitz's avatar jslavitz Committed by GitHub

Drawer fix (#20015)

* Drawer fix

* fixed nits

* fixed nits

* fixed nits

* final change

* Drawer fix final
parent 93705691
...@@ -141,6 +141,10 @@ class Drawer extends StatelessWidget { ...@@ -141,6 +141,10 @@ class Drawer extends StatelessWidget {
} }
} }
/// Signature for the callback that's called when a [DrawerController] is
/// opened or closed.
typedef void DrawerCallback(bool isOpened);
/// Provides interactive behavior for [Drawer] widgets. /// Provides interactive behavior for [Drawer] widgets.
/// ///
/// Rarely used directly. Drawer controllers are typically created automatically /// Rarely used directly. Drawer controllers are typically created automatically
...@@ -165,6 +169,7 @@ class DrawerController extends StatefulWidget { ...@@ -165,6 +169,7 @@ class DrawerController extends StatefulWidget {
GlobalKey key, GlobalKey key,
@required this.child, @required this.child,
@required this.alignment, @required this.alignment,
this.drawerCallback,
}) : assert(child != null), }) : assert(child != null),
assert(alignment != null), assert(alignment != null),
super(key: key); super(key: key);
...@@ -180,6 +185,9 @@ class DrawerController extends StatefulWidget { ...@@ -180,6 +185,9 @@ class DrawerController extends StatefulWidget {
/// close the drawer. /// close the drawer.
final DrawerAlignment alignment; final DrawerAlignment alignment;
/// Optional callback that is called when a [Drawer] is opened or closed.
final DrawerCallback drawerCallback;
@override @override
DrawerControllerState createState() => new DrawerControllerState(); DrawerControllerState createState() => new DrawerControllerState();
} }
...@@ -270,6 +278,8 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro ...@@ -270,6 +278,8 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro
return _kWidth; // drawer not being shown currently return _kWidth; // drawer not being shown currently
} }
bool _previouslyOpened = false;
void _move(DragUpdateDetails details) { void _move(DragUpdateDetails details) {
double delta = details.primaryDelta / _width; double delta = details.primaryDelta / _width;
switch (widget.alignment) { switch (widget.alignment) {
...@@ -287,6 +297,11 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro ...@@ -287,6 +297,11 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro
_controller.value += delta; _controller.value += delta;
break; break;
} }
final bool opened = _controller.value > 0.5 ? true : false;
if (opened != _previouslyOpened && widget.drawerCallback != null)
widget.drawerCallback(opened);
_previouslyOpened = opened;
} }
void _settle(DragEndDetails details) { void _settle(DragEndDetails details) {
...@@ -321,11 +336,15 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro ...@@ -321,11 +336,15 @@ class DrawerControllerState extends State<DrawerController> with SingleTickerPro
/// Typically called by [ScaffoldState.openDrawer]. /// Typically called by [ScaffoldState.openDrawer].
void open() { void open() {
_controller.fling(velocity: 1.0); _controller.fling(velocity: 1.0);
if (widget.drawerCallback != null)
widget.drawerCallback(true);
} }
/// Starts an animation to close the drawer. /// Starts an animation to close the drawer.
void close() { void close() {
_controller.fling(velocity: -1.0); _controller.fling(velocity: -1.0);
if (widget.drawerCallback != null)
widget.drawerCallback(false);
} }
final ColorTween _color = new ColorTween(begin: Colors.transparent, end: Colors.black54); final ColorTween _color = new ColorTween(begin: Colors.transparent, end: Colors.black54);
......
...@@ -1019,6 +1019,21 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin { ...@@ -1019,6 +1019,21 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
/// Whether this scaffold has a non-null [Scaffold.endDrawer]. /// Whether this scaffold has a non-null [Scaffold.endDrawer].
bool get hasEndDrawer => widget.endDrawer != null; bool get hasEndDrawer => widget.endDrawer != null;
bool _drawerOpened = false;
bool _endDrawerOpened = false;
void _drawerOpenedCallback(bool isOpened) {
setState(() {
_drawerOpened = isOpened;
});
}
void _endDrawerOpenedCallback(bool isOpened) {
setState(() {
_endDrawerOpened = isOpened;
});
}
/// Opens the [Drawer] (if any). /// Opens the [Drawer] (if any).
/// ///
/// If the scaffold has a non-null [Scaffold.drawer], this function will cause /// If the scaffold has a non-null [Scaffold.drawer], this function will cause
...@@ -1032,6 +1047,8 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin { ...@@ -1032,6 +1047,8 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
/// ///
/// See [Scaffold.of] for information about how to obtain the [ScaffoldState]. /// See [Scaffold.of] for information about how to obtain the [ScaffoldState].
void openDrawer() { void openDrawer() {
if (_endDrawerKey.currentState != null && _endDrawerOpened)
_endDrawerKey.currentState.close();
_drawerKey.currentState?.open(); _drawerKey.currentState?.open();
} }
...@@ -1048,6 +1065,8 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin { ...@@ -1048,6 +1065,8 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
/// ///
/// See [Scaffold.of] for information about how to obtain the [ScaffoldState]. /// See [Scaffold.of] for information about how to obtain the [ScaffoldState].
void openEndDrawer() { void openEndDrawer() {
if (_drawerKey.currentState != null && _drawerOpened)
_drawerKey.currentState.close();
_endDrawerKey.currentState?.open(); _endDrawerKey.currentState?.open();
} }
...@@ -1408,6 +1427,48 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin { ...@@ -1408,6 +1427,48 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
} }
} }
void _buildEndDrawer(List<LayoutId> children, TextDirection textDirection) {
if (widget.endDrawer != null) {
assert(hasEndDrawer);
_addIfNonNull(
children,
new DrawerController(
key: _endDrawerKey,
alignment: DrawerAlignment.end,
child: widget.endDrawer,
drawerCallback: _endDrawerOpenedCallback,
),
_ScaffoldSlot.endDrawer,
// remove the side padding from the side we're not touching
removeLeftPadding: textDirection == TextDirection.ltr,
removeTopPadding: false,
removeRightPadding: textDirection == TextDirection.rtl,
removeBottomPadding: false,
);
}
}
void _buildDrawer(List<LayoutId> children, TextDirection textDirection) {
if (widget.drawer != null) {
assert(hasDrawer);
_addIfNonNull(
children,
new DrawerController(
key: _drawerKey,
alignment: DrawerAlignment.start,
child: widget.drawer,
drawerCallback: _drawerOpenedCallback,
),
_ScaffoldSlot.drawer,
// remove the side padding from the side we're not touching
removeLeftPadding: textDirection == TextDirection.rtl,
removeTopPadding: false,
removeRightPadding: textDirection == TextDirection.ltr,
removeBottomPadding: false,
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context)); assert(debugCheckHasMediaQuery(context));
...@@ -1440,7 +1501,8 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin { ...@@ -1440,7 +1501,8 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
removeLeftPadding: false, removeLeftPadding: false,
removeTopPadding: widget.appBar != null, removeTopPadding: widget.appBar != null,
removeRightPadding: false, removeRightPadding: false,
removeBottomPadding: widget.bottomNavigationBar != null || widget.persistentFooterButtons != null, removeBottomPadding: widget.bottomNavigationBar != null ||
widget.persistentFooterButtons != null,
); );
if (widget.appBar != null) { if (widget.appBar != null) {
...@@ -1465,7 +1527,8 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin { ...@@ -1465,7 +1527,8 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
} }
if (_snackBars.isNotEmpty) { if (_snackBars.isNotEmpty) {
final bool removeBottomPadding = widget.persistentFooterButtons != null || widget.bottomNavigationBar != null; final bool removeBottomPadding = widget.persistentFooterButtons != null ||
widget.bottomNavigationBar != null;
_addIfNonNull( _addIfNonNull(
children, children,
_snackBars.first._widget, _snackBars.first._widget,
...@@ -1570,40 +1633,12 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin { ...@@ -1570,40 +1633,12 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
); );
} }
if (widget.drawer != null) { if (_endDrawerOpened) {
assert(hasDrawer); _buildDrawer(children, textDirection);
_addIfNonNull( _buildEndDrawer(children, textDirection);
children, } else {
new DrawerController( _buildEndDrawer(children, textDirection);
key: _drawerKey, _buildDrawer(children, textDirection);
alignment: DrawerAlignment.start,
child: widget.drawer,
),
_ScaffoldSlot.drawer,
// remove the side padding from the side we're not touching
removeLeftPadding: textDirection == TextDirection.rtl,
removeTopPadding: false,
removeRightPadding: textDirection == TextDirection.ltr,
removeBottomPadding: false,
);
}
if (widget.endDrawer != null) {
assert(hasEndDrawer);
_addIfNonNull(
children,
new DrawerController(
key: _endDrawerKey,
alignment: DrawerAlignment.end,
child: widget.endDrawer,
),
_ScaffoldSlot.endDrawer,
// remove the side padding from the side we're not touching
removeLeftPadding: textDirection == TextDirection.ltr,
removeTopPadding: false,
removeRightPadding: textDirection == TextDirection.rtl,
removeBottomPadding: false,
);
} }
// The minimum insets for contents of the Scaffold to keep visible. // The minimum insets for contents of the Scaffold to keep visible.
......
...@@ -766,39 +766,6 @@ void main() { ...@@ -766,39 +766,6 @@ void main() {
expect(tester.getRect(find.byKey(insideDrawer)), new Rect.fromLTRB(596.0, 30.0, 750.0, 540.0)); expect(tester.getRect(find.byKey(insideDrawer)), new Rect.fromLTRB(596.0, 30.0, 750.0, 540.0));
}); });
testWidgets('Simultaneous drawers on either side', (WidgetTester tester) async {
const String bodyLabel = 'I am the body';
const String drawerLabel = 'I am the label on start side';
const String endDrawerLabel = 'I am the label on end side';
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(new MaterialApp(home: const Scaffold(
body: const Text(bodyLabel),
drawer: const Drawer(child: const Text(drawerLabel)),
endDrawer: const Drawer(child: const Text(endDrawerLabel)),
)));
expect(semantics, includesNodeWith(label: bodyLabel));
expect(semantics, isNot(includesNodeWith(label: drawerLabel)));
expect(semantics, isNot(includesNodeWith(label: endDrawerLabel)));
final ScaffoldState state = tester.firstState(find.byType(Scaffold));
state.openDrawer();
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(semantics, isNot(includesNodeWith(label: bodyLabel)));
expect(semantics, includesNodeWith(label: drawerLabel));
state.openEndDrawer();
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(semantics, isNot(includesNodeWith(label: bodyLabel)));
expect(semantics, includesNodeWith(label: endDrawerLabel));
semantics.dispose();
});
group('ScaffoldGeometry', () { group('ScaffoldGeometry', () {
testWidgets('bottomNavigationBar', (WidgetTester tester) async { testWidgets('bottomNavigationBar', (WidgetTester tester) async {
...@@ -969,6 +936,93 @@ void main() { ...@@ -969,6 +936,93 @@ void main() {
numNotificationsAtLastFrame = listenerState.numNotifications; numNotificationsAtLastFrame = listenerState.numNotifications;
}); });
testWidgets('Simultaneous drawers on either side', (WidgetTester tester) async {
const String bodyLabel = 'I am the body';
const String drawerLabel = 'I am the label on start side';
const String endDrawerLabel = 'I am the label on end side';
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(new MaterialApp(home: const Scaffold(
body: const Text(bodyLabel),
drawer: const Drawer(child: const Text(drawerLabel)),
endDrawer: const Drawer(child: const Text(endDrawerLabel)),
)));
expect(semantics, includesNodeWith(label: bodyLabel));
expect(semantics, isNot(includesNodeWith(label: drawerLabel)));
expect(semantics, isNot(includesNodeWith(label: endDrawerLabel)));
final ScaffoldState state = tester.firstState(find.byType(Scaffold));
state.openDrawer();
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(semantics, isNot(includesNodeWith(label: bodyLabel)));
expect(semantics, includesNodeWith(label: drawerLabel));
state.openEndDrawer();
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(semantics, isNot(includesNodeWith(label: bodyLabel)));
expect(semantics, includesNodeWith(label: endDrawerLabel));
semantics.dispose();
});
testWidgets('Dual Drawer Opening', (WidgetTester tester) async {
await tester.pumpWidget(
new MaterialApp(
home: new SafeArea(
left: false,
top: true,
right: false,
bottom: false,
child: new Scaffold(
endDrawer: const Drawer(
child: const Text('endDrawer'),
),
drawer: const Drawer(
child: const Text('drawer'),
),
body: const Text('scaffold body'),
appBar: new AppBar(
centerTitle: true,
title: const Text('Title')
)
),
),
),
);
// Open Drawer, tap on end drawer, which closes the drawer, but does
// not open the drawer.
await tester.tap(find.byType(IconButton).first);
await tester.pumpAndSettle();
await tester.tap(find.byType(IconButton).last);
await tester.pumpAndSettle();
expect(find.text('endDrawer'), findsNothing);
expect(find.text('drawer'), findsNothing);
// Tapping the first opens the first drawer
await tester.tap(find.byType(IconButton).first);
await tester.pumpAndSettle();
expect(find.text('endDrawer'), findsNothing);
expect(find.text('drawer'), findsOneWidget);
// Tapping on the end drawer and then on the drawer should close the
// drawer and then reopen it.
await tester.tap(find.byType(IconButton).last);
await tester.pumpAndSettle();
await tester.tap(find.byType(IconButton).first);
await tester.pumpAndSettle();
expect(find.text('endDrawer'), findsNothing);
expect(find.text('drawer'), findsOneWidget);
});
}); });
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment