Use Navigator to drive SnackBar

Now SnackBar is an ephemeral route that uses a Placeholder to put itself into
the Scaffold.

Fixes #673
This commit is contained in:
Adam Barth 2015-10-04 14:22:35 -07:00
parent 48142d6872
commit 2eec30111a
11 changed files with 112 additions and 110 deletions

View file

@ -58,11 +58,9 @@ class FeedFragment extends StatefulComponent {
}
class FeedFragmentState extends State<FeedFragment> {
final GlobalKey<PlaceholderState> _snackBarPlaceholderKey = new GlobalKey<PlaceholderState>();
FitnessMode _fitnessMode = FitnessMode.feed;
PerformanceStatus _snackBarStatus = PerformanceStatus.dismissed;
bool _isShowingSnackBar = false;
void _handleFitnessModeChange(FitnessMode value) {
setState(() {
_fitnessMode = value;
@ -119,15 +117,17 @@ class FeedFragmentState extends State<FeedFragment> {
);
}
FitnessItem _undoItem;
void _handleItemDismissed(FitnessItem item) {
config.onItemDeleted(item);
setState(() {
_undoItem = item;
_isShowingSnackBar = true;
_snackBarStatus = PerformanceStatus.forward;
});
showSnackBar(
navigator: config.navigator,
placeholderKey: _snackBarPlaceholderKey,
content: new Text("Item deleted."),
actions: [new SnackBarAction(label: "UNDO", onPressed: () {
config.onItemCreated(item);
config.navigator.pop();
})]
);
}
Widget buildChart() {
@ -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() {
showDialog(config.navigator, (NavigatorState navigator) => new AddItemDialog(navigator)).then((routeName) {
if (routeName != null)
@ -240,7 +221,7 @@ class FeedFragmentState extends State<FeedFragment> {
return new Scaffold(
toolbar: buildToolBar(),
body: buildBody(),
snackBar: buildSnackBar(),
snackBar: new Placeholder(key: _snackBarPlaceholderKey),
floatingActionButton: buildFloatingActionButton()
);
}

View file

@ -5,7 +5,6 @@
library fitness;
import 'package:playfair/playfair.dart' as playfair;
import 'package:sky/animation.dart';
import 'package:sky/material.dart';
import 'package:sky/painting.dart';
import 'package:sky/widgets.dart';

View file

@ -112,9 +112,10 @@ class MeasurementFragment extends StatefulComponent {
}
class MeasurementFragmentState extends State<MeasurementFragment> {
final GlobalKey<PlaceholderState> _snackBarPlaceholderKey = new GlobalKey<PlaceholderState>();
String _weight = "";
DateTime _when = new DateTime.now();
String _errorMessage = null;
void _handleSave() {
double parsedWeight;
@ -122,9 +123,11 @@ class MeasurementFragmentState extends State<MeasurementFragment> {
parsedWeight = double.parse(_weight);
} on FormatException catch(e) {
print("Exception $e");
setState(() {
_errorMessage = "Save failed";
});
showSnackBar(
navigator: config.navigator,
placeholderKey: _snackBarPlaceholderKey,
content: new Text('Save failed')
);
}
config.onCreated(new Measurement(when: _when, weight: parsedWeight));
config.navigator.pop();
@ -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) {
return new Scaffold(
toolbar: buildToolBar(),
body: buildBody(context),
snackBar: buildSnackBar()
snackBar: new Placeholder(key: _snackBarPlaceholderKey)
);
}
}

View file

@ -8,7 +8,6 @@ import 'dart:async';
import 'dart:math' as math;
import 'dart:sky' as sky;
import 'package:sky/animation.dart';
import 'package:sky/gestures.dart';
import 'package:sky/material.dart';
import 'package:sky/painting.dart';

View file

@ -6,8 +6,6 @@ part of stocks;
typedef void ModeUpdater(StockMode mode);
const Duration _kSnackbarSlideDuration = const Duration(milliseconds: 200);
class StockHome extends StatefulComponent {
StockHome(this.navigator, this.stocks, this.symbols, this.stockMode, this.modeUpdater);
@ -22,12 +20,10 @@ class StockHome extends StatefulComponent {
class StockHomeState extends State<StockHome> {
final GlobalKey<PlaceholderState> _snackBarPlaceholderKey = new GlobalKey<PlaceholderState>();
bool _isSearching = false;
String _searchQuery;
PerformanceStatus _snackBarStatus = PerformanceStatus.dismissed;
bool _isSnackBarShowing = false;
void _handleSearchBegin() {
config.navigator.pushState(this, (_) {
setState(() {
@ -217,28 +213,18 @@ class StockHomeState extends State<StockHome> {
}
void _handleUndo() {
setState(() {
_isSnackBarShowing = false;
});
}
GlobalKey snackBarKey = new GlobalKey(label: 'snackbar');
Widget buildSnackBar() {
if (_snackBarStatus == PerformanceStatus.dismissed)
return null;
return new SnackBar(
showing: _isSnackBarShowing,
content: new Text("Stock purchased!"),
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
onDismissed: () { setState(() { _snackBarStatus = PerformanceStatus.dismissed; }); }
);
config.navigator.pop();
}
void _handleStockPurchased() {
setState(() {
_isSnackBarShowing = true;
_snackBarStatus = PerformanceStatus.forward;
});
showSnackBar(
navigator: config.navigator,
placeholderKey: _snackBarPlaceholderKey,
content: new Text("Stock purchased!"),
actions: [
new SnackBarAction(label: "UNDO", onPressed: _handleUndo)
]
);
}
Widget buildFloatingActionButton() {
@ -253,7 +239,7 @@ class StockHomeState extends State<StockHome> {
return new Scaffold(
toolbar: _isSearching ? buildSearchBar() : buildToolBar(),
body: buildTabNavigator(),
snackBar: buildSnackBar(),
snackBar: new Placeholder(key: _snackBarPlaceholderKey),
floatingActionButton: buildFloatingActionButton()
);
}

View file

@ -50,7 +50,7 @@ typedef void GlobalKeyRemoveListener(GlobalKey key);
/// 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
/// 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
/// Constructs a LabeledGlobalKey, which is a GlobalKey with a label used for debugging.
@ -96,9 +96,9 @@ abstract class GlobalKey extends Key {
Element get _currentElement => _registry[this];
BuildContext get currentContext => _currentElement;
Widget get currentWidget => _currentElement?.widget;
State get currentState {
T get currentState {
Element element = _currentElement;
if (element is StatefulComponentElement)
if (element is StatefulComponentElement<dynamic, T>)
return element.state;
return null;
}

View file

@ -107,10 +107,6 @@ class NavigatorState extends State<Navigator> {
void pop([dynamic result]) {
setState(() {
while (currentRoute.ephemeral) {
currentRoute.didPop(null);
_currentPosition -= 1;
}
assert(_currentPosition > 0);
currentRoute.didPop(result);
_currentPosition -= 1;

View file

@ -0,0 +1,30 @@
// 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);
}
}

View file

@ -137,7 +137,7 @@ class MenuRoute extends Route {
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 opaque => false;
Duration get transitionDuration => _kMenuDuration;

View file

@ -6,21 +6,20 @@ import 'package:sky/animation.dart';
import 'package:sky/gestures.dart';
import 'package:sky/material.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/framework.dart';
import 'package:sky/src/widgets/gesture_detector.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/transitions.dart';
typedef void SnackBarDismissedCallback();
const Duration _kSlideInDuration = const Duration(milliseconds: 200);
const double kSnackHeight = 52.0;
const double kSideMargins = 24.0;
const double kVerticalPadding = 14.0;
const Color kSnackBackground = const Color(0xFF323232);
const double _kSnackHeight = 52.0;
const double _kSideMargins = 24.0;
const double _kVerticalPadding = 14.0;
const Color _kSnackBackground = const Color(0xFF323232);
class SnackBarAction extends StatelessComponent {
SnackBarAction({Key key, this.label, this.onPressed }) : super(key: key) {
@ -34,70 +33,60 @@ class SnackBarAction extends StatelessComponent {
return new GestureDetector(
onTap: onPressed,
child: new Container(
margin: const EdgeDims.only(left: kSideMargins),
padding: const EdgeDims.symmetric(vertical: kVerticalPadding),
margin: const EdgeDims.only(left: _kSideMargins),
padding: const EdgeDims.symmetric(vertical: _kVerticalPadding),
child: new Text(label)
)
);
}
}
class SnackBar extends AnimatedComponent {
class SnackBar extends StatelessComponent {
SnackBar({
Key key,
this.content,
this.actions,
bool showing,
this.onDismissed
}) : super(key: key, direction: showing ? AnimationDirection.forward : AnimationDirection.reverse, duration: _kSlideInDuration) {
this.performance
}) : super(key: key) {
assert(content != null);
}
final Widget content;
final List<SnackBarAction> actions;
final SnackBarDismissedCallback onDismissed;
SnackBarState createState() => new SnackBarState();
}
class SnackBarState extends AnimatedState<SnackBar> {
void handleDismissed() {
if (config.onDismissed != null)
config.onDismissed();
}
final PerformanceView performance;
Widget build(BuildContext context) {
List<Widget> children = [
new Flexible(
child: new Container(
margin: const EdgeDims.symmetric(vertical: kVerticalPadding),
margin: const EdgeDims.symmetric(vertical: _kVerticalPadding),
child: new DefaultTextStyle(
style: Typography.white.subhead,
child: config.content
child: content
)
)
)
];
if (config.actions != null)
children.addAll(config.actions);
if (actions != null)
children.addAll(actions);
return new SquashTransition(
performance: performance.view,
performance: performance,
height: new AnimatedValue<double>(
0.0,
end: kSnackHeight,
end: _kSnackHeight,
curve: easeIn,
reverseCurve: easeOut
),
child: new ClipRect(
child: new OverflowBox(
minHeight: kSnackHeight,
maxHeight: kSnackHeight,
minHeight: _kSnackHeight,
maxHeight: _kSnackHeight,
child: new Material(
level: 2,
color: kSnackBackground,
color: _kSnackBackground,
type: MaterialType.canvas,
child: new Container(
margin: const EdgeDims.symmetric(horizontal: kSideMargins),
margin: const EdgeDims.symmetric(horizontal: _kSideMargins),
child: new DefaultTextStyle(
style: new TextStyle(color: Theme.of(context).accentColor),
child: new Row(children)
@ -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);
}

View file

@ -37,6 +37,7 @@ export 'src/widgets/material_button.dart';
export 'src/widgets/mimic.dart';
export 'src/widgets/mixed_viewport.dart';
export 'src/widgets/navigator.dart';
export 'src/widgets/placeholder.dart';
export 'src/widgets/popup_menu.dart';
export 'src/widgets/popup_menu_item.dart';
export 'src/widgets/progress_indicator.dart';