Skip to content

add backdrop and working menu with filtering #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 46 additions & 11 deletions mdc_100_series/lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,68 @@

import 'package:flutter/material.dart';

import 'backdrop.dart';
import 'colors.dart';
import 'home.dart';
import 'login.dart';
import 'menu_page.dart';
import 'model/product.dart';
import 'supplemental/cut_corners_border.dart';

class ShrineApp extends StatelessWidget {
class ShrineApp extends StatefulWidget {
@override
_ShrineAppState createState() => _ShrineAppState();
}

class _ShrineAppState extends State<ShrineApp> {
Category _currentCategory = Category.all;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Shrine',
home: HomePage(),
home: Backdrop(
currentCategory: _currentCategory,
frontPanel: HomePage(category: _currentCategory),
backPanel: MenuPage(
currentCategory: _currentCategory,
onCategoryTap: _onCategoryTap,
),
frontTitle: Text('SHRINE'),
backTitle: Text('MENU'),
),

initialRoute: '/login',
onGenerateRoute: _getRoute,
theme: _kShrineTheme,
);
}

Route<dynamic> _getRoute(RouteSettings settings) {
if (settings.name == '/login') {
return new MaterialPageRoute<void>(
settings: settings,
builder: (BuildContext context) => LoginPage(),
fullscreenDialog: true,
);
}
/// Function to call when a [Category] is tapped.
void _onCategoryTap(Category category) {
setState(() {
_currentCategory = category;
});
}
}

return null;
Route<dynamic> _getRoute(RouteSettings settings) {
if (settings.name == '/login') {
return MaterialPageRoute<void>(
settings: settings,
builder: (BuildContext context) => LoginPage(),
fullscreenDialog: true,
);
}

return null;
}

final ThemeData _kShrineTheme = _buildShrineTheme();

IconThemeData _customIconTheme(IconThemeData original) {
return original.copyWith(color: kShrineBrown900);
}

ThemeData _buildShrineTheme() {
final ThemeData base = ThemeData.light();
return base.copyWith(
Expand All @@ -68,6 +98,7 @@ ThemeData _buildShrineTheme() {
textTheme: _buildShrineTextTheme(base.textTheme),
primaryTextTheme: _buildShrineTextTheme(base.primaryTextTheme),
accentTextTheme: _buildShrineTextTheme(base.accentTextTheme),
iconTheme: _customIconTheme(base.iconTheme),
);
}

Expand All @@ -83,6 +114,10 @@ TextTheme _buildShrineTextTheme(TextTheme base) {
fontWeight: FontWeight.w400,
fontSize: 14.0,
),
body2: base.body2.copyWith(
fontWeight: FontWeight.w500,
fontSize: 16.0,
),
).apply(
fontFamily: 'Rubik',
displayColor: kShrineBrown900,
Expand Down
242 changes: 242 additions & 0 deletions mdc_100_series/lib/backdrop.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
// Copyright 2018 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:meta/meta.dart';

import 'model/product.dart';
import 'login.dart';

const double _kFlingVelocity = 2.0;

class _BackdropPanel extends StatelessWidget {
const _BackdropPanel({
Key key,
this.onTap,
this.child,
}) : super(key: key);

final VoidCallback onTap;
final Widget child;

@override
Widget build(BuildContext context) {
return Material(
elevation: 16.0,
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.only(topLeft: Radius.circular(64.0)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
height: 40.0,
alignment: AlignmentDirectional.centerStart,
),
),
Expanded(
child: child,
),
],
),
);
}
}

class _BackdropTitle extends AnimatedWidget {
final Widget frontTitle;
final Widget backTitle;

const _BackdropTitle({
Key key,
Listenable listenable,
this.frontTitle,
this.backTitle,
}) : super(key: key, listenable: listenable);

@override
Widget build(BuildContext context) {
final Animation<double> animation = this.listenable;
return DefaultTextStyle(
style: Theme.of(context).primaryTextTheme.title,
softWrap: false,
overflow: TextOverflow.ellipsis,
// Here, we do a custom cross fade between backTitle and frontTitle.
// This makes a smooth animation between the two texts.
child: Stack(
children: <Widget>[
Opacity(
opacity: CurvedAnimation(
parent: ReverseAnimation(animation),
curve: Interval(0.5, 1.0),
).value,
child: backTitle,
),
Opacity(
opacity: CurvedAnimation(
parent: animation,
curve: Interval(0.5, 1.0),
).value,
child: frontTitle,
),
],
),
);
}
}

/// Builds a Backdrop.
///
/// A Backdrop widget has two panels, front and back. The front panel is shown
/// by default, and slides down to show the back panel, from which a user
/// can make a selection. The user can also configure the titles for when the
/// front or back panel is showing.
class Backdrop extends StatefulWidget {
final Category currentCategory;
final Widget frontPanel;
final Widget backPanel;
final Widget frontTitle;
final Widget backTitle;

const Backdrop({
@required this.currentCategory,
@required this.frontPanel,
@required this.backPanel,
@required this.frontTitle,
@required this.backTitle,
}) : assert(currentCategory != null),
assert(frontPanel != null),
assert(backPanel != null),
assert(frontTitle != null),
assert(backTitle != null);

@override
_BackdropState createState() => _BackdropState();
}

class _BackdropState extends State<Backdrop>
with SingleTickerProviderStateMixin {
final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop');
AnimationController _controller;

@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 300),
value: 1.0,
vsync: this,
);
}

@override
void didUpdateWidget(Backdrop old) {
super.didUpdateWidget(old);
if (widget.currentCategory != old.currentCategory) {
setState(() {
_controller.fling(
velocity:
_backdropPanelVisible ? -_kFlingVelocity : _kFlingVelocity);
});
} else if (!_backdropPanelVisible) {
setState(() {
_controller.fling(velocity: _kFlingVelocity);
});
}
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

bool get _backdropPanelVisible {
final AnimationStatus status = _controller.status;
return status == AnimationStatus.completed ||
status == AnimationStatus.forward;
}

void _toggleBackdropPanelVisibility() {
_controller.fling(
velocity: _backdropPanelVisible ? -_kFlingVelocity : _kFlingVelocity);
}

Widget _buildStack(BuildContext context, BoxConstraints constraints) {
const double panelTitleHeight = 48.0;
final Size panelSize = constraints.biggest;
final double panelTop = panelSize.height - panelTitleHeight;

Animation<RelativeRect> panelAnimation = RelativeRectTween(
begin: RelativeRect.fromLTRB(
0.0, panelTop, 0.0, panelTop - panelSize.height),
end: RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
).animate(_controller.view);

return Container(
key: _backdropKey,
child: Stack(
children: <Widget>[
widget.backPanel,
PositionedTransition(
rect: panelAnimation,
child: _BackdropPanel(
onTap: _toggleBackdropPanelVisibility,
child: widget.frontPanel,
),
),
],
),
);
}

@override
Widget build(BuildContext context) {
var appBar = AppBar(
brightness: Brightness.light,
elevation: 0.0,
leading: IconButton(
onPressed: _toggleBackdropPanelVisibility,
icon: AnimatedIcon(
icon: AnimatedIcons.close_menu,
progress: _controller.view,
),
),
title: _BackdropTitle(
listenable: _controller.view,
frontTitle: widget.frontTitle,
backTitle: widget.backTitle,
),
actions: <Widget>[
new IconButton(
icon: const Icon(Icons.search),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (BuildContext context) => LoginPage()),
);
},
),
new IconButton(
icon: const Icon(Icons.tune),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (BuildContext context) => LoginPage()),
);
},
),
],
);
return Scaffold(
appBar: appBar,
body: LayoutBuilder(
builder: _buildStack,
),
);
}
}
33 changes: 6 additions & 27 deletions mdc_100_series/lib/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,17 @@

import 'package:flutter/material.dart';

import 'model/product.dart';
import 'model/data.dart';
import 'supplemental/asymmetric_view.dart';

class HomePage extends StatelessWidget {
final Category category;

const HomePage({this.category: Category.all});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
brightness: Brightness.light,
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () {
print('Menu button');
},
),
title: Text('SHRINE'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {
print('Search button');
},
),
IconButton(
icon: Icon(Icons.tune),
onPressed: () {
print('Filter button');
},
),
],
),
body: AsymmetricView(products: getAllProducts()),
);
return AsymmetricView(products: getProducts(category));
}
}
Loading