Skip to content

Commit c7b73fa

Browse files
maryxwilllarche
authored andcommitted
add backdrop and working menu with filtering
1 parent 0982bf3 commit c7b73fa

File tree

9 files changed

+412
-92
lines changed

9 files changed

+412
-92
lines changed

mdc_100_series/lib/app.dart

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,62 @@
1515
import 'package:flutter/material.dart';
1616
import 'package:flutter/services.dart';
1717

18+
import 'backdrop.dart';
1819
import 'colors.dart';
1920
import 'home.dart';
2021
import 'login.dart';
22+
import 'menu_page.dart';
23+
import 'model/product.dart';
2124
import 'supplemental/cut_corners_border.dart';
2225

23-
class ShrineApp extends StatelessWidget {
26+
class ShrineApp extends StatefulWidget {
27+
@override
28+
_ShrineAppState createState() => _ShrineAppState();
29+
}
30+
31+
class _ShrineAppState extends State<ShrineApp> {
32+
Category _currentCategory = Category.all;
2433
@override
2534
Widget build(BuildContext context) {
2635
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);
2736

2837
return MaterialApp(
2938
title: 'Shrine',
30-
home: new HomePage(),
39+
home: Backdrop(
40+
currentCategory: _currentCategory,
41+
frontPanel: HomePage(category: _currentCategory),
42+
backPanel: MenuPage(
43+
currentCategory: _currentCategory,
44+
onCategoryTap: _onCategoryTap,
45+
),
46+
frontTitle: Text('SHRINE'),
47+
backTitle: Text('MENU'),
48+
),
49+
3150
initialRoute: '/login',
3251
onGenerateRoute: _getRoute,
3352
theme: _kShrineTheme,
3453
);
3554
}
3655

37-
Route<dynamic> _getRoute(RouteSettings settings) {
38-
if (settings.name == '/login') {
39-
return new MaterialPageRoute<void>(
40-
settings: settings,
41-
builder: (BuildContext context) => new LoginPage(),
42-
fullscreenDialog: true,
43-
);
44-
}
56+
/// Function to call when a [Category] is tapped.
57+
void _onCategoryTap(Category category) {
58+
setState(() {
59+
_currentCategory = category;
60+
});
61+
}
62+
}
4563

46-
return null;
64+
Route<dynamic> _getRoute(RouteSettings settings) {
65+
if (settings.name == '/login') {
66+
return new MaterialPageRoute<void>(
67+
settings: settings,
68+
builder: (BuildContext context) => new LoginPage(),
69+
fullscreenDialog: true,
70+
);
4771
}
72+
73+
return null;
4874
}
4975

5076
final ThemeData _kShrineTheme = _buildShrineTheme();

mdc_100_series/lib/backdrop.dart

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
// Copyright 2018 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:meta/meta.dart';
7+
8+
import 'model/product.dart';
9+
import 'login.dart';
10+
11+
const double _kFlingVelocity = 2.0;
12+
13+
class _BackdropPanel extends StatelessWidget {
14+
const _BackdropPanel({
15+
Key key,
16+
this.onTap,
17+
this.child,
18+
}) : super(key: key);
19+
20+
final VoidCallback onTap;
21+
final Widget child;
22+
23+
@override
24+
Widget build(BuildContext context) {
25+
return Material(
26+
elevation: 16.0,
27+
shape: BeveledRectangleBorder(
28+
borderRadius: BorderRadius.only(topLeft: Radius.circular(64.0)),
29+
),
30+
child: Column(
31+
crossAxisAlignment: CrossAxisAlignment.stretch,
32+
children: <Widget>[
33+
GestureDetector(
34+
behavior: HitTestBehavior.opaque,
35+
onTap: onTap,
36+
child: Container(
37+
height: 40.0,
38+
alignment: AlignmentDirectional.centerStart,
39+
),
40+
),
41+
Expanded(
42+
child: child,
43+
),
44+
],
45+
),
46+
);
47+
}
48+
}
49+
50+
class _BackdropTitle extends AnimatedWidget {
51+
final Widget frontTitle;
52+
final Widget backTitle;
53+
54+
const _BackdropTitle({
55+
Key key,
56+
Listenable listenable,
57+
this.frontTitle,
58+
this.backTitle,
59+
}) : super(key: key, listenable: listenable);
60+
61+
@override
62+
Widget build(BuildContext context) {
63+
final Animation<double> animation = this.listenable;
64+
return DefaultTextStyle(
65+
style: Theme.of(context).primaryTextTheme.title,
66+
softWrap: false,
67+
overflow: TextOverflow.ellipsis,
68+
// Here, we do a custom cross fade between backTitle and frontTitle.
69+
// This makes a smooth animation between the two texts.
70+
child: Stack(
71+
children: <Widget>[
72+
Opacity(
73+
opacity: CurvedAnimation(
74+
parent: ReverseAnimation(animation),
75+
curve: Interval(0.5, 1.0),
76+
).value,
77+
child: backTitle,
78+
),
79+
Opacity(
80+
opacity: CurvedAnimation(
81+
parent: animation,
82+
curve: Interval(0.5, 1.0),
83+
).value,
84+
child: frontTitle,
85+
),
86+
],
87+
),
88+
);
89+
}
90+
}
91+
92+
/// Builds a Backdrop.
93+
///
94+
/// A Backdrop widget has two panels, front and back. The front panel is shown
95+
/// by default, and slides down to show the back panel, from which a user
96+
/// can make a selection. The user can also configure the titles for when the
97+
/// front or back panel is showing.
98+
class Backdrop extends StatefulWidget {
99+
final Category currentCategory;
100+
final Widget frontPanel;
101+
final Widget backPanel;
102+
final Widget frontTitle;
103+
final Widget backTitle;
104+
105+
const Backdrop({
106+
@required this.currentCategory,
107+
@required this.frontPanel,
108+
@required this.backPanel,
109+
@required this.frontTitle,
110+
@required this.backTitle,
111+
}) : assert(currentCategory != null),
112+
assert(frontPanel != null),
113+
assert(backPanel != null),
114+
assert(frontTitle != null),
115+
assert(backTitle != null);
116+
117+
@override
118+
_BackdropState createState() => _BackdropState();
119+
}
120+
121+
class _BackdropState extends State<Backdrop>
122+
with SingleTickerProviderStateMixin {
123+
final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop');
124+
AnimationController _controller;
125+
126+
@override
127+
void initState() {
128+
super.initState();
129+
_controller = AnimationController(
130+
duration: Duration(milliseconds: 300),
131+
value: 1.0,
132+
vsync: this,
133+
);
134+
}
135+
136+
@override
137+
void didUpdateWidget(Backdrop old) {
138+
super.didUpdateWidget(old);
139+
if (widget.currentCategory != old.currentCategory) {
140+
setState(() {
141+
_controller.fling(
142+
velocity:
143+
_backdropPanelVisible ? -_kFlingVelocity : _kFlingVelocity);
144+
});
145+
} else if (!_backdropPanelVisible) {
146+
setState(() {
147+
_controller.fling(velocity: _kFlingVelocity);
148+
});
149+
}
150+
}
151+
152+
@override
153+
void dispose() {
154+
_controller.dispose();
155+
super.dispose();
156+
}
157+
158+
bool get _backdropPanelVisible {
159+
final AnimationStatus status = _controller.status;
160+
return status == AnimationStatus.completed ||
161+
status == AnimationStatus.forward;
162+
}
163+
164+
void _toggleBackdropPanelVisibility() {
165+
_controller.fling(
166+
velocity: _backdropPanelVisible ? -_kFlingVelocity : _kFlingVelocity);
167+
}
168+
169+
Widget _buildStack(BuildContext context, BoxConstraints constraints) {
170+
const double panelTitleHeight = 48.0;
171+
final Size panelSize = constraints.biggest;
172+
final double panelTop = panelSize.height - panelTitleHeight;
173+
174+
Animation<RelativeRect> panelAnimation = RelativeRectTween(
175+
begin: RelativeRect.fromLTRB(
176+
0.0, panelTop, 0.0, panelTop - panelSize.height),
177+
end: RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
178+
).animate(_controller.view);
179+
180+
return Container(
181+
key: _backdropKey,
182+
child: Stack(
183+
children: <Widget>[
184+
widget.backPanel,
185+
PositionedTransition(
186+
rect: panelAnimation,
187+
child: _BackdropPanel(
188+
onTap: _toggleBackdropPanelVisibility,
189+
child: widget.frontPanel,
190+
),
191+
),
192+
],
193+
),
194+
);
195+
}
196+
197+
@override
198+
Widget build(BuildContext context) {
199+
var appBar = AppBar(
200+
brightness: Brightness.light,
201+
elevation: 0.0,
202+
leading: IconButton(
203+
onPressed: _toggleBackdropPanelVisibility,
204+
icon: AnimatedIcon(
205+
icon: AnimatedIcons.close_menu,
206+
progress: _controller.view,
207+
),
208+
),
209+
title: _BackdropTitle(
210+
listenable: _controller.view,
211+
frontTitle: widget.frontTitle,
212+
backTitle: widget.backTitle,
213+
),
214+
actions: <Widget>[
215+
new IconButton(
216+
icon: const Icon(Icons.search),
217+
onPressed: () {
218+
Navigator.push(
219+
context,
220+
MaterialPageRoute(builder: (BuildContext context) => LoginPage()),
221+
);
222+
},
223+
),
224+
new IconButton(
225+
icon: const Icon(Icons.tune),
226+
onPressed: () {
227+
Navigator.push(
228+
context,
229+
MaterialPageRoute(builder: (BuildContext context) => LoginPage()),
230+
);
231+
},
232+
),
233+
],
234+
);
235+
return Scaffold(
236+
appBar: appBar,
237+
body: LayoutBuilder(
238+
builder: _buildStack,
239+
),
240+
);
241+
}
242+
}

mdc_100_series/lib/home.dart

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,38 +14,17 @@
1414

1515
import 'package:flutter/material.dart';
1616

17+
import 'model/product.dart';
1718
import 'model/data.dart';
1819
import 'supplemental/asymmetric_view.dart';
1920

2021
class HomePage extends StatelessWidget {
22+
final Category category;
23+
24+
const HomePage({this.category: Category.all});
25+
2126
@override
2227
Widget build(BuildContext context) {
23-
return new Scaffold(
24-
appBar: new AppBar(
25-
brightness: Brightness.light,
26-
leading: new IconButton(
27-
icon: const Icon(Icons.menu),
28-
onPressed: () {
29-
print('Menu button');
30-
},
31-
),
32-
title: const Text('SHRINE'),
33-
actions: <Widget>[
34-
new IconButton(
35-
icon: const Icon(Icons.search),
36-
onPressed: () {
37-
print('Search button');
38-
},
39-
),
40-
new IconButton(
41-
icon: const Icon(Icons.tune),
42-
onPressed: () {
43-
print('Filter button');
44-
},
45-
),
46-
],
47-
),
48-
body: AsymmetricView(products: getAllProducts()),
49-
);
28+
return AsymmetricView(products: getProducts(category));
5029
}
51-
}
30+
}

mdc_100_series/lib/main.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ import 'package:flutter/material.dart';
1616

1717
import 'app.dart';
1818

19-
void main() => runApp(new ShrineApp());
19+
void main() => runApp(ShrineApp());

0 commit comments

Comments
 (0)