Commit 5a359dce authored by Adam Barth's avatar Adam Barth

Merge pull request #1484 from abarth/snackbar_route

Use Navigator to drive SnackBar
parents 706b272e 2eec3011
...@@ -58,11 +58,9 @@ class FeedFragment extends StatefulComponent { ...@@ -58,11 +58,9 @@ class FeedFragment extends StatefulComponent {
} }
class FeedFragmentState extends State<FeedFragment> { class FeedFragmentState extends State<FeedFragment> {
final GlobalKey<PlaceholderState> _snackBarPlaceholderKey = new GlobalKey<PlaceholderState>();
FitnessMode _fitnessMode = FitnessMode.feed; FitnessMode _fitnessMode = FitnessMode.feed;
PerformanceStatus _snackBarStatus = PerformanceStatus.dismissed;
bool _isShowingSnackBar = false;
void _handleFitnessModeChange(FitnessMode value) { void _handleFitnessModeChange(FitnessMode value) {
setState(() { setState(() {
_fitnessMode = value; _fitnessMode = value;
...@@ -119,15 +117,17 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -119,15 +117,17 @@ class FeedFragmentState extends State<FeedFragment> {
); );
} }
FitnessItem _undoItem;
void _handleItemDismissed(FitnessItem item) { void _handleItemDismissed(FitnessItem item) {
config.onItemDeleted(item); config.onItemDeleted(item);
setState(() { showSnackBar(
_undoItem = item; navigator: config.navigator,
_isShowingSnackBar = true; placeholderKey: _snackBarPlaceholderKey,
_snackBarStatus = PerformanceStatus.forward; content: new Text("Item deleted."),
}); actions: [new SnackBarAction(label: "UNDO", onPressed: () {
config.onItemCreated(item);
config.navigator.pop();
})]
);
} }
Widget buildChart() { Widget buildChart() {
...@@ -198,25 +198,6 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -198,25 +198,6 @@ class FeedFragmentState extends State<FeedFragment> {
} }
} }
void _handleUndo() {
config.onItemCreated(_undoItem);
setState(() {
_undoItem = null;
_isShowingSnackBar = false;
});
}
Widget buildSnackBar() {
if (_snackBarStatus == PerformanceStatus.dismissed)
return null;
return new SnackBar(
showing: _isShowingSnackBar,
content: new Text("Item deleted."),
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
onDismissed: () { setState(() { _snackBarStatus = PerformanceStatus.dismissed; }); }
);
}
void _handleActionButtonPressed() { void _handleActionButtonPressed() {
showDialog(config.navigator, (NavigatorState navigator) => new AddItemDialog(navigator)).then((routeName) { showDialog(config.navigator, (NavigatorState navigator) => new AddItemDialog(navigator)).then((routeName) {
if (routeName != null) if (routeName != null)
...@@ -240,7 +221,7 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -240,7 +221,7 @@ class FeedFragmentState extends State<FeedFragment> {
return new Scaffold( return new Scaffold(
toolbar: buildToolBar(), toolbar: buildToolBar(),
body: buildBody(), body: buildBody(),
snackBar: buildSnackBar(), snackBar: new Placeholder(key: _snackBarPlaceholderKey),
floatingActionButton: buildFloatingActionButton() floatingActionButton: buildFloatingActionButton()
); );
} }
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
library fitness; library fitness;
import 'package:playfair/playfair.dart' as playfair; import 'package:playfair/playfair.dart' as playfair;
import 'package:sky/animation.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/painting.dart'; import 'package:sky/painting.dart';
import 'package:sky/widgets.dart'; import 'package:sky/widgets.dart';
......
...@@ -112,9 +112,10 @@ class MeasurementFragment extends StatefulComponent { ...@@ -112,9 +112,10 @@ class MeasurementFragment extends StatefulComponent {
} }
class MeasurementFragmentState extends State<MeasurementFragment> { class MeasurementFragmentState extends State<MeasurementFragment> {
final GlobalKey<PlaceholderState> _snackBarPlaceholderKey = new GlobalKey<PlaceholderState>();
String _weight = ""; String _weight = "";
DateTime _when = new DateTime.now(); DateTime _when = new DateTime.now();
String _errorMessage = null;
void _handleSave() { void _handleSave() {
double parsedWeight; double parsedWeight;
...@@ -122,9 +123,11 @@ class MeasurementFragmentState extends State<MeasurementFragment> { ...@@ -122,9 +123,11 @@ class MeasurementFragmentState extends State<MeasurementFragment> {
parsedWeight = double.parse(_weight); parsedWeight = double.parse(_weight);
} on FormatException catch(e) { } on FormatException catch(e) {
print("Exception $e"); print("Exception $e");
setState(() { showSnackBar(
_errorMessage = "Save failed"; navigator: config.navigator,
}); placeholderKey: _snackBarPlaceholderKey,
content: new Text('Save failed')
);
} }
config.onCreated(new Measurement(when: _when, weight: parsedWeight)); config.onCreated(new Measurement(when: _when, weight: parsedWeight));
config.navigator.pop(); config.navigator.pop();
...@@ -195,18 +198,11 @@ class MeasurementFragmentState extends State<MeasurementFragment> { ...@@ -195,18 +198,11 @@ class MeasurementFragmentState extends State<MeasurementFragment> {
); );
} }
Widget buildSnackBar() {
if (_errorMessage == null)
return null;
// TODO(jackson): This doesn't show up, unclear why.
return new SnackBar(content: new Text(_errorMessage), showing: true);
}
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return new Scaffold(
toolbar: buildToolBar(), toolbar: buildToolBar(),
body: buildBody(context), body: buildBody(context),
snackBar: buildSnackBar() snackBar: new Placeholder(key: _snackBarPlaceholderKey)
); );
} }
} }
...@@ -8,7 +8,6 @@ import 'dart:async'; ...@@ -8,7 +8,6 @@ import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:sky' as sky; import 'dart:sky' as sky;
import 'package:sky/animation.dart';
import 'package:sky/gestures.dart'; import 'package:sky/gestures.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/painting.dart'; import 'package:sky/painting.dart';
......
...@@ -6,8 +6,6 @@ part of stocks; ...@@ -6,8 +6,6 @@ part of stocks;
typedef void ModeUpdater(StockMode mode); typedef void ModeUpdater(StockMode mode);
const Duration _kSnackbarSlideDuration = const Duration(milliseconds: 200);
class StockHome extends StatefulComponent { class StockHome extends StatefulComponent {
StockHome(this.navigator, this.stocks, this.symbols, this.stockMode, this.modeUpdater); StockHome(this.navigator, this.stocks, this.symbols, this.stockMode, this.modeUpdater);
...@@ -22,12 +20,10 @@ class StockHome extends StatefulComponent { ...@@ -22,12 +20,10 @@ class StockHome extends StatefulComponent {
class StockHomeState extends State<StockHome> { class StockHomeState extends State<StockHome> {
final GlobalKey<PlaceholderState> _snackBarPlaceholderKey = new GlobalKey<PlaceholderState>();
bool _isSearching = false; bool _isSearching = false;
String _searchQuery; String _searchQuery;
PerformanceStatus _snackBarStatus = PerformanceStatus.dismissed;
bool _isSnackBarShowing = false;
void _handleSearchBegin() { void _handleSearchBegin() {
config.navigator.pushState(this, (_) { config.navigator.pushState(this, (_) {
setState(() { setState(() {
...@@ -217,30 +213,20 @@ class StockHomeState extends State<StockHome> { ...@@ -217,30 +213,20 @@ class StockHomeState extends State<StockHome> {
} }
void _handleUndo() { void _handleUndo() {
setState(() { config.navigator.pop();
_isSnackBarShowing = false;
});
} }
GlobalKey snackBarKey = new GlobalKey(label: 'snackbar'); void _handleStockPurchased() {
Widget buildSnackBar() { showSnackBar(
if (_snackBarStatus == PerformanceStatus.dismissed) navigator: config.navigator,
return null; placeholderKey: _snackBarPlaceholderKey,
return new SnackBar(
showing: _isSnackBarShowing,
content: new Text("Stock purchased!"), content: new Text("Stock purchased!"),
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)], actions: [
onDismissed: () { setState(() { _snackBarStatus = PerformanceStatus.dismissed; }); } new SnackBarAction(label: "UNDO", onPressed: _handleUndo)
]
); );
} }
void _handleStockPurchased() {
setState(() {
_isSnackBarShowing = true;
_snackBarStatus = PerformanceStatus.forward;
});
}
Widget buildFloatingActionButton() { Widget buildFloatingActionButton() {
return new FloatingActionButton( return new FloatingActionButton(
child: new Icon(type: 'content/add', size: 24), child: new Icon(type: 'content/add', size: 24),
...@@ -253,7 +239,7 @@ class StockHomeState extends State<StockHome> { ...@@ -253,7 +239,7 @@ class StockHomeState extends State<StockHome> {
return new Scaffold( return new Scaffold(
toolbar: _isSearching ? buildSearchBar() : buildToolBar(), toolbar: _isSearching ? buildSearchBar() : buildToolBar(),
body: buildTabNavigator(), body: buildTabNavigator(),
snackBar: buildSnackBar(), snackBar: new Placeholder(key: _snackBarPlaceholderKey),
floatingActionButton: buildFloatingActionButton() floatingActionButton: buildFloatingActionButton()
); );
} }
......
...@@ -50,7 +50,7 @@ typedef void GlobalKeyRemoveListener(GlobalKey key); ...@@ -50,7 +50,7 @@ typedef void GlobalKeyRemoveListener(GlobalKey key);
/// A GlobalKey is one that must be unique across the entire application. It is /// A GlobalKey is one that must be unique across the entire application. It is
/// used by components that need to communicate with other components across the /// used by components that need to communicate with other components across the
/// application's element tree. /// application's element tree.
abstract class GlobalKey extends Key { abstract class GlobalKey<T extends State> extends Key {
const GlobalKey.constructor() : super.constructor(); // so that subclasses can call us, since the Key() factory constructor shadows the implicit constructor const GlobalKey.constructor() : super.constructor(); // so that subclasses can call us, since the Key() factory constructor shadows the implicit constructor
/// Constructs a LabeledGlobalKey, which is a GlobalKey with a label used for debugging. /// Constructs a LabeledGlobalKey, which is a GlobalKey with a label used for debugging.
...@@ -96,9 +96,9 @@ abstract class GlobalKey extends Key { ...@@ -96,9 +96,9 @@ abstract class GlobalKey extends Key {
Element get _currentElement => _registry[this]; Element get _currentElement => _registry[this];
BuildContext get currentContext => _currentElement; BuildContext get currentContext => _currentElement;
Widget get currentWidget => _currentElement?.widget; Widget get currentWidget => _currentElement?.widget;
State get currentState { T get currentState {
Element element = _currentElement; Element element = _currentElement;
if (element is StatefulComponentElement) if (element is StatefulComponentElement<dynamic, T>)
return element.state; return element.state;
return null; return null;
} }
......
...@@ -114,10 +114,6 @@ class NavigatorState extends State<Navigator> { ...@@ -114,10 +114,6 @@ class NavigatorState extends State<Navigator> {
void pop([dynamic result]) { void pop([dynamic result]) {
setState(() { setState(() {
while (currentRoute.ephemeral) {
currentRoute.didPop(null);
_currentPosition -= 1;
}
assert(_currentPosition > 0); assert(_currentPosition > 0);
currentRoute.didPop(result); currentRoute.didPop(result);
_currentPosition -= 1; _currentPosition -= 1;
......
// 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:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart';
class Placeholder extends StatefulComponent {
Placeholder({ Key key }) : super(key: key);
PlaceholderState createState() => new PlaceholderState();
}
class PlaceholderState extends State<Placeholder> {
Widget get child => _child;
Widget _child;
void set child(Widget child) {
if (_child == child)
return;
setState(() {
_child = child;
});
}
Widget build(BuildContext context) {
if (_child != null)
return child;
return new SizedBox(width: 0.0, height: 0.0);
}
}
...@@ -137,7 +137,7 @@ class MenuRoute extends Route { ...@@ -137,7 +137,7 @@ class MenuRoute extends Route {
return result; return result;
} }
bool get ephemeral => false; // we could make this true, but then we'd have to use popRoute(), not pop(), in menus bool get ephemeral => true;
bool get modal => true; bool get modal => true;
bool get opaque => false; bool get opaque => false;
Duration get transitionDuration => _kMenuDuration; Duration get transitionDuration => _kMenuDuration;
......
...@@ -6,21 +6,20 @@ import 'package:sky/animation.dart'; ...@@ -6,21 +6,20 @@ import 'package:sky/animation.dart';
import 'package:sky/gestures.dart'; import 'package:sky/gestures.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/painting.dart'; import 'package:sky/painting.dart';
import 'package:sky/src/widgets/animated_component.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/gesture_detector.dart';
import 'package:sky/src/widgets/material.dart'; import 'package:sky/src/widgets/material.dart';
import 'package:sky/src/widgets/navigator.dart';
import 'package:sky/src/widgets/placeholder.dart';
import 'package:sky/src/widgets/theme.dart'; import 'package:sky/src/widgets/theme.dart';
import 'package:sky/src/widgets/transitions.dart'; import 'package:sky/src/widgets/transitions.dart';
typedef void SnackBarDismissedCallback();
const Duration _kSlideInDuration = const Duration(milliseconds: 200); const Duration _kSlideInDuration = const Duration(milliseconds: 200);
const double kSnackHeight = 52.0; const double _kSnackHeight = 52.0;
const double kSideMargins = 24.0; const double _kSideMargins = 24.0;
const double kVerticalPadding = 14.0; const double _kVerticalPadding = 14.0;
const Color kSnackBackground = const Color(0xFF323232); const Color _kSnackBackground = const Color(0xFF323232);
class SnackBarAction extends StatelessComponent { class SnackBarAction extends StatelessComponent {
SnackBarAction({Key key, this.label, this.onPressed }) : super(key: key) { SnackBarAction({Key key, this.label, this.onPressed }) : super(key: key) {
...@@ -34,70 +33,60 @@ class SnackBarAction extends StatelessComponent { ...@@ -34,70 +33,60 @@ class SnackBarAction extends StatelessComponent {
return new GestureDetector( return new GestureDetector(
onTap: onPressed, onTap: onPressed,
child: new Container( child: new Container(
margin: const EdgeDims.only(left: kSideMargins), margin: const EdgeDims.only(left: _kSideMargins),
padding: const EdgeDims.symmetric(vertical: kVerticalPadding), padding: const EdgeDims.symmetric(vertical: _kVerticalPadding),
child: new Text(label) child: new Text(label)
) )
); );
} }
} }
class SnackBar extends AnimatedComponent { class SnackBar extends StatelessComponent {
SnackBar({ SnackBar({
Key key, Key key,
this.content, this.content,
this.actions, this.actions,
bool showing, this.performance
this.onDismissed }) : super(key: key) {
}) : super(key: key, direction: showing ? AnimationDirection.forward : AnimationDirection.reverse, duration: _kSlideInDuration) {
assert(content != null); assert(content != null);
} }
final Widget content; final Widget content;
final List<SnackBarAction> actions; final List<SnackBarAction> actions;
final SnackBarDismissedCallback onDismissed; final PerformanceView performance;
SnackBarState createState() => new SnackBarState();
}
class SnackBarState extends AnimatedState<SnackBar> {
void handleDismissed() {
if (config.onDismissed != null)
config.onDismissed();
}
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Widget> children = [ List<Widget> children = [
new Flexible( new Flexible(
child: new Container( child: new Container(
margin: const EdgeDims.symmetric(vertical: kVerticalPadding), margin: const EdgeDims.symmetric(vertical: _kVerticalPadding),
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: Typography.white.subhead, style: Typography.white.subhead,
child: config.content child: content
) )
) )
) )
]; ];
if (config.actions != null) if (actions != null)
children.addAll(config.actions); children.addAll(actions);
return new SquashTransition( return new SquashTransition(
performance: performance.view, performance: performance,
height: new AnimatedValue<double>( height: new AnimatedValue<double>(
0.0, 0.0,
end: kSnackHeight, end: _kSnackHeight,
curve: easeIn, curve: easeIn,
reverseCurve: easeOut reverseCurve: easeOut
), ),
child: new ClipRect( child: new ClipRect(
child: new OverflowBox( child: new OverflowBox(
minHeight: kSnackHeight, minHeight: _kSnackHeight,
maxHeight: kSnackHeight, maxHeight: _kSnackHeight,
child: new Material( child: new Material(
level: 2, level: 2,
color: kSnackBackground, color: _kSnackBackground,
type: MaterialType.canvas, type: MaterialType.canvas,
child: new Container( child: new Container(
margin: const EdgeDims.symmetric(horizontal: kSideMargins), margin: const EdgeDims.symmetric(horizontal: _kSideMargins),
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: new TextStyle(color: Theme.of(context).accentColor), style: new TextStyle(color: Theme.of(context).accentColor),
child: new Row(children) child: new Row(children)
...@@ -109,3 +98,28 @@ class SnackBarState extends AnimatedState<SnackBar> { ...@@ -109,3 +98,28 @@ class SnackBarState extends AnimatedState<SnackBar> {
); );
} }
} }
class _SnackBarRoute extends Route {
_SnackBarRoute({ this.content, this.actions });
final Widget content;
final List<SnackBarAction> actions;
bool get hasContent => false;
bool get ephemeral => true;
bool get modal => false;
Duration get transitionDuration => _kSlideInDuration;
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) => null;
}
void showSnackBar({ NavigatorState navigator, GlobalKey<PlaceholderState> placeholderKey, Widget content, List<SnackBarAction> actions }) {
Route route = new _SnackBarRoute();
SnackBar snackBar = new SnackBar(
content: content,
actions: actions,
performance: route.performance
);
placeholderKey.currentState.child = snackBar;
navigator.push(route);
}
...@@ -37,6 +37,7 @@ export 'src/widgets/material_button.dart'; ...@@ -37,6 +37,7 @@ export 'src/widgets/material_button.dart';
export 'src/widgets/mimic.dart'; export 'src/widgets/mimic.dart';
export 'src/widgets/mixed_viewport.dart'; export 'src/widgets/mixed_viewport.dart';
export 'src/widgets/navigator.dart'; export 'src/widgets/navigator.dart';
export 'src/widgets/placeholder.dart';
export 'src/widgets/popup_menu.dart'; export 'src/widgets/popup_menu.dart';
export 'src/widgets/popup_menu_item.dart'; export 'src/widgets/popup_menu_item.dart';
export 'src/widgets/progress_indicator.dart'; export 'src/widgets/progress_indicator.dart';
......
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