Commit 65e77142 authored by Dragos Tiselice's avatar Dragos Tiselice

Updated DrawerHeader and added UserAccountDrawer.

Removed old Stack layout and added a simple-to-extend interface for the
new drawer header. Also added a specialized UserAccountsDrawerHeader
consistent with Material Design guidelines.
parent 5f7b8999
......@@ -126,7 +126,7 @@ class CardCollectionState extends State<CardCollection> {
child: new IconTheme(
data: const IconThemeData(color: Colors.black),
child: new Block(children: <Widget>[
new DrawerHeader(content: new Center(child: new Text('Options'))),
new DrawerHeader(child: new Center(child: new Text('Options'))),
buildDrawerCheckbox("Make card labels editable", _editable, _toggleEditable),
buildDrawerCheckbox("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter),
buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards),
......
......@@ -85,7 +85,7 @@ class PageableListAppState extends State<PageableListApp> {
Widget _buildDrawer() {
return new Drawer(
child: new Block(children: <Widget>[
new DrawerHeader(content: new Center(child: new Text('Options'))),
new DrawerHeader(child: new Center(child: new Text('Options'))),
new DrawerItem(
icon: new Icon(Icons.more_horiz),
selected: scrollDirection == Axis.horizontal,
......
......@@ -121,7 +121,8 @@ class _PestoDemoState extends State<PestoDemo> {
child: new Block(
children: <Widget>[
new DrawerHeader(
content: new Column(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Container(
decoration: new BoxDecoration(
......@@ -131,7 +132,7 @@ class _PestoDemoState extends State<PestoDemo> {
width: 72.0,
height: 72.0,
padding: const EdgeInsets.all(2.0),
margin: const EdgeInsets.only(bottom: 8.0),
margin: const EdgeInsets.only(bottom: 16.0),
child: new ClipOval(
child: new Image(
image: new AssetImage(_kUserImage),
......
......@@ -49,7 +49,7 @@ class GalleryDrawer extends StatelessWidget {
child: new Block(
children: <Widget>[
new DrawerHeader(
content: new Center(child: new Text('Flutter gallery'))
child: new Center(child: new Text('Flutter gallery'))
),
new DrawerItem(
icon: new Icon(Icons.brightness_5),
......
......@@ -124,7 +124,7 @@ class StockHomeState extends State<StockHome> {
Widget _buildDrawer(BuildContext context) {
return new Drawer(
child: new Block(children: <Widget>[
new DrawerHeader(content: new Center(child: new Text('Stocks'))),
new DrawerHeader(child: new Center(child: new Text('Stocks'))),
new DrawerItem(
icon: new Icon(Icons.assessment),
selected: true,
......
......@@ -72,5 +72,6 @@ export 'src/material/toggleable.dart';
export 'src/material/tooltip.dart';
export 'src/material/two_level_list.dart';
export 'src/material/typography.dart';
export 'src/material/user_accounts_drawer_header.dart';
export 'widgets.dart';
......@@ -7,11 +7,11 @@ import 'package:flutter/widgets.dart';
import 'debug.dart';
import 'theme.dart';
const double _kDrawerHeaderHeight = 140.0;
const double _kDrawerHeaderHeight = 160.0 + 1.0; // bottom edge
/// The top-most region of a material design drawer. The header's [background]
/// widget extends behind the system status bar and its [content] widget is
/// stacked on top of the background and below the status bar.
/// The top-most region of a material design drawer. The header's [child]
/// widget is placed inside of a [Container] whose [decoration] can be passed as
/// an argument.
///
/// Part of the material design [Drawer].
///
......@@ -22,19 +22,24 @@ const double _kDrawerHeaderHeight = 140.0;
/// * [Drawer]
/// * [DrawerItem]
/// * <https://www.google.com/design/spec/patterns/navigation-drawer.html>
class DrawerHeader extends StatelessWidget {
/// Creates a material design drawer header.
///
/// Requires one of its ancestors to be a [Material] widget.
const DrawerHeader({ Key key, this.background, this.content }) : super(key: key);
const DrawerHeader({
Key key,
this.decoration,
this.child
}) : super(key: key);
/// A widget that extends behind the system status bar and is stacked
/// behind the [content] widget.
final Widget background;
/// Decoration for the main drawer header [Container]; useful for applying
/// backgrounds.
final BoxDecoration decoration;
/// A widget that's positioned below the status bar and stacked on top of the
/// [background] widget. Typically a view of the user's id.
final Widget content;
/// A widget that extends behind the system status bar and is placed inside a
/// [Container].
final Widget child;
@override
Widget build(BuildContext context) {
......@@ -42,7 +47,7 @@ class DrawerHeader extends StatelessWidget {
final double statusBarHeight = MediaQuery.of(context).padding.top;
return new Container(
height: statusBarHeight + _kDrawerHeaderHeight,
margin: const EdgeInsets.only(bottom: 7.0), // 8 less 1 for the bottom border.
margin: const EdgeInsets.only(bottom: 8.0),
decoration: new BoxDecoration(
border: const Border(
bottom: const BorderSide(
......@@ -51,20 +56,18 @@ class DrawerHeader extends StatelessWidget {
)
)
),
child: new Stack(
children: <Widget>[
background ?? new Container(),
new Positioned(
top: statusBarHeight + 16.0,
left: 16.0,
right: 16.0,
bottom: 8.0,
child: new DefaultTextStyle(
style: Theme.of(context).textTheme.body2,
child: content
)
)
]
child: new Container(
padding: new EdgeInsets.only(
top: 16.0 + statusBarHeight,
left: 16.0,
right: 16.0,
bottom: 8.0
),
decoration: decoration,
child: new DefaultTextStyle(
style: Theme.of(context).textTheme.body2,
child: child
)
)
);
}
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'debug.dart';
/// A material design [Drawer] header that identifies the app's user.
///
/// The top-most region of a material design drawer with user accounts. The
/// header's [decoration] is used to provide a background.
/// [currentAccountPicture] is the main account picture on the left, while
/// [otherAccountsPictures] are the smaller account pictures on the right.
/// [accountName] and [accountEmail] provide access to the top and bottom rows
/// of the account details in the lower part of the header. When touched, this
/// area triggers [onDetailsPressed] and toggles the dropdown icon on the right.
///
/// Requires one of its ancestors to be a [Material] widget.
///
/// See also:
///
/// * [Drawer]
/// * [DrawerItem]
/// * <https://www.google.com/design/spec/patterns/navigation-drawer.html>
class UserAccountsDrawerHeader extends StatefulWidget {
/// Creates a material design drawer header.
///
/// Requires one of its ancestors to be a [Material] widget.
UserAccountsDrawerHeader({
Key key,
this.decoration,
this.currentAccountPicture,
this.otherAccountsPictures,
this.accountName,
this.accountEmail,
this.onDetailsPressed
}) : super(key: key);
/// A callback that gets called when the account name/email/dropdown
/// section is pressed.
final VoidCallback onDetailsPressed;
/// Decoration for the main drawer header container useful for applying
/// backgrounds.
final BoxDecoration decoration;
/// A widget placed in the upper-left corner representing the current
/// account picture. Normally a [CircleAvatar].
final Widget currentAccountPicture;
/// A list of widgets that represent the user's accounts. Up to three of them
/// are arranged in a row in the header's upper-right corner. Normally a list
/// of [CircleAvatar] widgets.
final List<Widget> otherAccountsPictures;
/// A widget placed on the top row of the account details representing
/// account name.
final Widget accountName;
/// A widget placed on the bottom row of the account details representing
/// account email.
final Widget accountEmail;
@override
_UserAccountsDrawerHeaderState createState() =>
new _UserAccountsDrawerHeaderState();
}
class _UserAccountsDrawerHeaderState extends State<UserAccountsDrawerHeader> {
/// Saves whether the account dropdown is open or not.
bool isOpen = false;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
final List<Widget> otherAccountsPictures = config.otherAccountsPictures ??
<Widget>[];
return new DrawerHeader(
decoration: config.decoration,
child: new Column(
children: <Widget>[
new Flexible(
child: new Stack(
children: <Widget>[
new Positioned(
top: 0.0,
right: 0.0,
child: new Row(
children: otherAccountsPictures.take(3).map(
(Widget picture) {
return new Container(
margin: const EdgeInsets.only(left: 16.0),
width: 40.0,
height: 40.0,
child: picture
);
}
).toList()
)
),
new Positioned(
top: 0.0,
child: new Container(
width: 72.0,
height: 72.0,
child: config.currentAccountPicture
)
)
]
)
),
new Container(
height: 56.0,
child: new InkWell(
onTap: () {
setState(() {
isOpen = !isOpen;
});
if (config.onDetailsPressed != null)
config.onDetailsPressed();
},
child: new Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new DefaultTextStyle(
style: const TextStyle(
fontWeight: FontWeight.w500,
color: Colors.white
),
child: config.accountName
),
new Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new DefaultTextStyle(
style: const TextStyle(color: Colors.white),
child: config.accountEmail
),
new Flexible(
child: new Align(
alignment: FractionalOffset.centerRight,
child: new Icon(
isOpen ? Icons.arrow_drop_up :
Icons.arrow_drop_down,
color: Colors.white
)
)
)
]
)
]
)
)
)
)
]
)
);
}
}
......@@ -7,13 +7,18 @@ import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Drawer control test', (WidgetTester tester) async {
final Key containerKey = new Key('container');
await tester.pumpWidget(
new Scaffold(
drawer: new Drawer(
child: new Block(
children: <Widget>[
new DrawerHeader(
content: new Text('header')
child: new Container(
key: containerKey,
child: new Text('header')
)
),
new DrawerItem(
icon: new Icon(Icons.archive),
......@@ -28,8 +33,21 @@ void main() {
expect(find.text('Archive'), findsNothing);
ScaffoldState state = tester.firstState(find.byType(Scaffold));
state.openDrawer();
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(find.text('Archive'), findsOneWidget);
RenderBox box = tester.renderObject(find.byType(DrawerHeader));
expect(box.size.height, equals(160.0 + 8.0 + 1.0)); // height + bottom margin + bottom edge
final double drawerWidth = box.size.width;
final double drawerHeight = box.size.height;
box = tester.renderObject(find.byKey(containerKey));
expect(box.size.width, equals(drawerWidth - 2 * 16.0));
expect(box.size.height, equals(drawerHeight - 2 * 16.0 - 1.0)); // bottom edge
expect(find.text('header'), findsOneWidget);
});
}
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('UserAccuntsDrawerHeader test', (WidgetTester tester) async {
final Key avatarA = new Key('A');
final Key avatarC = new Key('C');
final Key avatarD = new Key('D');
await tester.pumpWidget(
new Material(
child: new UserAccountsDrawerHeader(
currentAccountPicture: new CircleAvatar(
key: avatarA,
child: new Text('A')
),
otherAccountsPictures: <Widget>[
new CircleAvatar(
child: new Text('B')
),
new CircleAvatar(
key: avatarC,
child: new Text('C')
),
new CircleAvatar(
key: avatarD,
child: new Text('D')
),
new CircleAvatar(
child: new Text('E')
)
],
accountName: new Text("name"),
accountEmail: new Text("email")
)
)
);
expect(find.text('A'), findsOneWidget);
expect(find.text('B'), findsOneWidget);
expect(find.text('C'), findsOneWidget);
expect(find.text('D'), findsOneWidget);
expect(find.text('E'), findsNothing);
expect(find.text('name'), findsOneWidget);
expect(find.text('email'), findsOneWidget);
RenderBox box = tester.renderObject(find.byKey(avatarA));
expect(box.size.width, equals(72.0));
expect(box.size.height, equals(72.0));
box = tester.renderObject(find.byKey(avatarC));
expect(box.size.width, equals(40.0));
expect(box.size.height, equals(40.0));
Point topLeft = tester.getTopLeft(find.byType(UserAccountsDrawerHeader));
Point topRight = tester.getTopRight(find.byType(UserAccountsDrawerHeader));
Point avatarATopLeft = tester.getTopLeft(find.byKey(avatarA));
Point avatarDTopRight = tester.getTopRight(find.byKey(avatarD));
Point avatarCTopRight = tester.getTopRight(find.byKey(avatarC));
expect(avatarATopLeft.x - topLeft.x, equals(16.0));
expect(avatarATopLeft.y - topLeft.y, equals(16.0));
expect(topRight.x - avatarDTopRight.x, equals(16.0));
expect(avatarDTopRight.y - topRight.y, equals(16.0));
expect(avatarDTopRight.x - avatarCTopRight.x, equals(40.0 + 16.0)); // size + space between
});
}
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