Commit ee802bfe authored by Ian Hickson's avatar Ian Hickson

Model ink splashes more physically

parent 366d078d
...@@ -58,26 +58,27 @@ class AnimationTiming { ...@@ -58,26 +58,27 @@ class AnimationTiming {
} }
} }
/// An animated variable with a concrete type /// An animated variable with a concrete type.
class AnimatedValue<T extends dynamic> extends AnimationTiming implements Animatable { class AnimatedValue<T extends dynamic> extends AnimationTiming implements Animatable {
AnimatedValue(this.begin, { this.end, Curve curve, Curve reverseCurve }) AnimatedValue(this.begin, { this.end, Curve curve, Curve reverseCurve })
: super(curve: curve, reverseCurve: reverseCurve) { : super(curve: curve, reverseCurve: reverseCurve) {
value = begin; value = begin;
} }
/// The current value of this variable /// The current value of this variable.
T value; T value;
/// The value this variable has at the beginning of the animation /// The value this variable has at the beginning of the animation.
T begin; T begin;
/// The value this variable has at the end of the animation /// The value this variable has at the end of the animation.
T end; T end;
/// Returns the value this variable has at the given animation clock value /// Returns the value this variable has at the given animation clock value.
T lerp(double t) => begin + (end - begin) * t; T lerp(double t) => begin + (end - begin) * t;
/// Updates the value of this variable according to the given animation clock value and direction /// Updates the value of this variable according to the given animation clock
/// value and direction.
void setProgress(double t, AnimationDirection direction) { void setProgress(double t, AnimationDirection direction) {
if (end != null) { if (end != null) {
t = transform(t, direction); t = transform(t, direction);
...@@ -93,7 +94,7 @@ class AnimatedValue<T extends dynamic> extends AnimationTiming implements Animat ...@@ -93,7 +94,7 @@ class AnimatedValue<T extends dynamic> extends AnimationTiming implements Animat
String toString() => 'AnimatedValue(begin=$begin, end=$end, value=$value)'; String toString() => 'AnimatedValue(begin=$begin, end=$end, value=$value)';
} }
/// An animated variable containing a color /// An animated variable containing a color.
/// ///
/// This class specializes the interpolation of AnimatedValue<Color> to be /// This class specializes the interpolation of AnimatedValue<Color> to be
/// appropriate for colors. /// appropriate for colors.
...@@ -104,9 +105,9 @@ class AnimatedColorValue extends AnimatedValue<Color> { ...@@ -104,9 +105,9 @@ class AnimatedColorValue extends AnimatedValue<Color> {
Color lerp(double t) => Color.lerp(begin, end, t); Color lerp(double t) => Color.lerp(begin, end, t);
} }
/// An animated variable containing a rectangle /// An animated variable containing a size.
/// ///
/// This class specializes the interpolation of AnimatedValue<Rect> to be /// This class specializes the interpolation of AnimatedValue<Size> to be
/// appropriate for rectangles. /// appropriate for rectangles.
class AnimatedSizeValue extends AnimatedValue<Size> { class AnimatedSizeValue extends AnimatedValue<Size> {
AnimatedSizeValue(Size begin, { Size end, Curve curve, Curve reverseCurve }) AnimatedSizeValue(Size begin, { Size end, Curve curve, Curve reverseCurve })
...@@ -115,7 +116,7 @@ class AnimatedSizeValue extends AnimatedValue<Size> { ...@@ -115,7 +116,7 @@ class AnimatedSizeValue extends AnimatedValue<Size> {
Size lerp(double t) => Size.lerp(begin, end, t); Size lerp(double t) => Size.lerp(begin, end, t);
} }
/// An animated variable containing a rectangle /// An animated variable containing a rectangle.
/// ///
/// This class specializes the interpolation of AnimatedValue<Rect> to be /// This class specializes the interpolation of AnimatedValue<Rect> to be
/// appropriate for rectangles. /// appropriate for rectangles.
...@@ -125,3 +126,15 @@ class AnimatedRectValue extends AnimatedValue<Rect> { ...@@ -125,3 +126,15 @@ class AnimatedRectValue extends AnimatedValue<Rect> {
Rect lerp(double t) => Rect.lerp(begin, end, t); Rect lerp(double t) => Rect.lerp(begin, end, t);
} }
/// An animated variable containing a int.
///
/// The inherited lerp() function doesn't work with ints because it multiplies
/// the begin and end types by a double, and int * double returns a double.
/// This class overrides the lerp() function to round off the result to an int.
class AnimatedIntValue extends AnimatedValue<int> {
AnimatedIntValue(int begin, { int end, Curve curve, Curve reverseCurve })
: super(begin, end: end, curve: curve, reverseCurve: reverseCurve);
int lerp(double t) => (begin + (end - begin) * t).round();
}
...@@ -14,11 +14,13 @@ class Colors { ...@@ -14,11 +14,13 @@ class Colors {
static const black = const Color(0xFF000000); static const black = const Color(0xFF000000);
static const black87 = const Color(0xDD000000); static const black87 = const Color(0xDD000000);
static const black54 = const Color(0x8A000000); static const black54 = const Color(0x8A000000);
static const black26 = const Color(0x42000000); // disabled radio buttons and text of disabled flat buttons in light theme (26% black) static const black45 = const Color(0x73000000); // mask color
static const black26 = const Color(0x42000000); // disabled radio buttons and text of disabled flat buttons in light theme
static const black12 = const Color(0x1F000000); // background of disabled raised buttons in light theme static const black12 = const Color(0x1F000000); // background of disabled raised buttons in light theme
static const white = const Color(0xFFFFFFFF); static const white = const Color(0xFFFFFFFF);
static const white70 = const Color(0xB3FFFFFF); static const white70 = const Color(0xB3FFFFFF);
static const white30 = const Color(0x4DFFFFFF); // disabled radio buttons and text of disabled flat buttons in dark theme (30% white) static const white30 = const Color(0x4DFFFFFF); // disabled radio buttons and text of disabled flat buttons in dark theme
static const white12 = const Color(0x1FFFFFFF); // background of disabled raised buttons in dark theme static const white12 = const Color(0x1FFFFFFF); // background of disabled raised buttons in dark theme
static const white10 = const Color(0x1AFFFFFF); static const white10 = const Color(0x1AFFFFFF);
......
...@@ -10,35 +10,39 @@ import 'ink_well.dart'; ...@@ -10,35 +10,39 @@ import 'ink_well.dart';
import 'theme.dart'; import 'theme.dart';
class DrawerItem extends StatelessComponent { class DrawerItem extends StatelessComponent {
const DrawerItem({ Key key, this.icon, this.child, this.onPressed, this.selected: false }) const DrawerItem({
: super(key: key); Key key,
this.icon,
this.child,
this.onPressed,
this.selected: false
}) : super(key: key);
final String icon; final String icon;
final Widget child; final Widget child;
final VoidCallback onPressed; final VoidCallback onPressed;
final bool selected; final bool selected;
ColorFilter _getIconColorFilter(ThemeData themeData) {
if (selected) {
if (themeData.brightness == ThemeBrightness.dark)
return new ColorFilter.mode(themeData.accentColor, TransferMode.srcATop);
return new ColorFilter.mode(themeData.primaryColor, TransferMode.srcATop);
}
return new ColorFilter.mode(Colors.black45, TransferMode.dstIn);
}
TextStyle _getTextStyle(ThemeData themeData) { TextStyle _getTextStyle(ThemeData themeData) {
TextStyle result = themeData.text.body2; TextStyle result = themeData.text.body2;
if (selected) if (selected) {
result = result.copyWith(color: themeData.primaryColor); if (themeData.brightness == ThemeBrightness.dark)
result = result.copyWith(color: themeData.accentColor);
else
result = result.copyWith(color: themeData.primaryColor);
}
return result; return result;
} }
Color _getBackgroundColor(ThemeData themeData, { bool highlight }) {
if (highlight)
return themeData.highlightColor;
if (selected)
return themeData.selectedColor;
return Colors.transparent;
}
ColorFilter _getColorFilter(ThemeData themeData) {
if (selected)
return new ColorFilter.mode(themeData.primaryColor, TransferMode.srcATop);
return new ColorFilter.mode(const Color(0x73000000), TransferMode.dstIn);
}
Widget build(BuildContext context) { Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context); ThemeData themeData = Theme.of(context);
...@@ -49,7 +53,7 @@ class DrawerItem extends StatelessComponent { ...@@ -49,7 +53,7 @@ class DrawerItem extends StatelessComponent {
padding: const EdgeDims.symmetric(horizontal: 16.0), padding: const EdgeDims.symmetric(horizontal: 16.0),
child: new Icon( child: new Icon(
icon: icon, icon: icon,
colorFilter: _getColorFilter(themeData) colorFilter: _getIconColorFilter(themeData)
) )
) )
); );
...@@ -70,8 +74,6 @@ class DrawerItem extends StatelessComponent { ...@@ -70,8 +74,6 @@ class DrawerItem extends StatelessComponent {
height: 48.0, height: 48.0,
child: new InkWell( child: new InkWell(
onTap: onPressed, onTap: onPressed,
defaultColor: _getBackgroundColor(themeData, highlight: false),
highlightColor: _getBackgroundColor(themeData, highlight: true),
child: new Row(flexChildren) child: new Row(flexChildren)
) )
); );
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'material_button.dart'; import 'material_button.dart';
import 'theme.dart'; import 'theme.dart';
...@@ -12,11 +11,27 @@ class FlatButton extends MaterialButton { ...@@ -12,11 +11,27 @@ class FlatButton extends MaterialButton {
FlatButton({ FlatButton({
Key key, Key key,
Widget child, Widget child,
ButtonColor textTheme,
Color textColor,
Color disabledTextColor,
this.color,
this.colorBrightness,
this.disabledColor,
VoidCallback onPressed VoidCallback onPressed
}) : super(key: key, }) : super(key: key,
child: child, child: child,
textTheme: textTheme,
textColor: textColor,
disabledTextColor: disabledTextColor,
onPressed: onPressed); onPressed: onPressed);
// These default to null, meaning transparent.
final Color color;
final Color disabledColor;
/// Controls the default text color if the text color isn't explicit set.
final ThemeBrightness colorBrightness;
_FlatButtonState createState() => new _FlatButtonState(); _FlatButtonState createState() => new _FlatButtonState();
} }
...@@ -24,19 +39,14 @@ class _FlatButtonState extends MaterialButtonState<FlatButton> { ...@@ -24,19 +39,14 @@ class _FlatButtonState extends MaterialButtonState<FlatButton> {
int get elevation => 0; int get elevation => 0;
Color getColor(BuildContext context, { bool highlight }) { Color getColor(BuildContext context) {
if (!config.enabled || !highlight) if (!config.enabled)
return null; return config.disabledColor;
switch (Theme.of(context).brightness) { return config.color;
case ThemeBrightness.light:
return Colors.grey[400];
case ThemeBrightness.dark:
return Colors.grey[200];
}
} }
ThemeBrightness getColorBrightness(BuildContext context) { ThemeBrightness getColorBrightness(BuildContext context) {
return Theme.of(context).brightness; return config.colorBrightness ?? Theme.of(context).brightness;
} }
} }
...@@ -19,12 +19,16 @@ class FloatingActionButton extends StatefulComponent { ...@@ -19,12 +19,16 @@ class FloatingActionButton extends StatefulComponent {
Key key, Key key,
this.child, this.child,
this.backgroundColor, this.backgroundColor,
this.elevation: 6,
this.highlightElevation: 12,
this.onPressed this.onPressed
}) : super(key: key); }) : super(key: key);
final Widget child; final Widget child;
final Color backgroundColor; final Color backgroundColor;
final VoidCallback onPressed; final VoidCallback onPressed;
final int elevation;
final int highlightElevation;
_FloatingActionButtonState createState() => new _FloatingActionButtonState(); _FloatingActionButtonState createState() => new _FloatingActionButtonState();
} }
...@@ -50,19 +54,17 @@ class _FloatingActionButtonState extends State<FloatingActionButton> { ...@@ -50,19 +54,17 @@ class _FloatingActionButtonState extends State<FloatingActionButton> {
return new Material( return new Material(
color: materialColor, color: materialColor,
type: MaterialType.circle, type: MaterialType.circle,
elevation: _highlight ? 12 : 6, elevation: _highlight ? config.highlightElevation : config.elevation,
child: new ClipOval( child: new Container(
child: new Container( width: _kSize,
width: _kSize, height: _kSize,
height: _kSize, child: new InkWell(
child: new InkWell( onTap: config.onPressed,
onTap: config.onPressed, onHighlightChanged: _handleHighlightChanged,
onHighlightChanged: _handleHighlightChanged, child: new Center(
child: new Center( child: new IconTheme(
child: new IconTheme( data: new IconThemeData(color: iconThemeColor),
data: new IconThemeData(color: iconThemeColor), child: config.child
child: config.child
)
) )
) )
) )
......
...@@ -6,6 +6,7 @@ import 'package:flutter/widgets.dart'; ...@@ -6,6 +6,7 @@ import 'package:flutter/widgets.dart';
import 'icon.dart'; import 'icon.dart';
import 'icon_theme_data.dart'; import 'icon_theme_data.dart';
import 'ink_well.dart';
class IconButton extends StatelessComponent { class IconButton extends StatelessComponent {
const IconButton({ const IconButton({
...@@ -22,11 +23,8 @@ class IconButton extends StatelessComponent { ...@@ -22,11 +23,8 @@ class IconButton extends StatelessComponent {
final VoidCallback onPressed; final VoidCallback onPressed;
Widget build(BuildContext context) { Widget build(BuildContext context) {
// TODO(abarth): We should use a radial reaction here so you can hit the return new InkResponse(
// 8.0 pixel padding as well as the icon.
return new GestureDetector(
onTap: onPressed, onTap: onPressed,
behavior: HitTestBehavior.opaque,
child: new Padding( child: new Padding(
padding: const EdgeDims.all(8.0), padding: const EdgeDims.all(8.0),
child: new Icon( child: new Icon(
......
...@@ -36,12 +36,16 @@ abstract class MaterialButton extends StatefulComponent { ...@@ -36,12 +36,16 @@ abstract class MaterialButton extends StatefulComponent {
MaterialButton({ MaterialButton({
Key key, Key key,
this.child, this.child,
this.textTheme,
this.textColor, this.textColor,
this.disabledTextColor,
this.onPressed this.onPressed
}) : super(key: key); }) : super(key: key);
final Widget child; final Widget child;
final ButtonColor textColor; final ButtonColor textTheme;
final Color textColor;
final Color disabledTextColor;
final VoidCallback onPressed; final VoidCallback onPressed;
bool get enabled => onPressed != null; bool get enabled => onPressed != null;
...@@ -57,12 +61,14 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> { ...@@ -57,12 +61,14 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> {
bool highlight = false; bool highlight = false;
int get elevation; int get elevation;
Color getColor(BuildContext context, { bool highlight }); Color getColor(BuildContext context);
ThemeBrightness getColorBrightness(BuildContext context); ThemeBrightness getColorBrightness(BuildContext context);
Color getTextColor(BuildContext context) { Color getTextColor(BuildContext context) {
if (config.enabled) { if (config.enabled) {
switch (config.textColor ?? ButtonTheme.of(context)) { if (config.textColor != null)
return config.textColor;
switch (config.textTheme ?? ButtonTheme.of(context)) {
case ButtonColor.accent: case ButtonColor.accent:
return Theme.of(context).accentColor; return Theme.of(context).accentColor;
case ButtonColor.normal: case ButtonColor.normal:
...@@ -74,6 +80,8 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> { ...@@ -74,6 +80,8 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> {
} }
} }
} }
if (config.disabledTextColor != null)
return config.disabledTextColor;
switch (getColorBrightness(context)) { switch (getColorBrightness(context)) {
case ThemeBrightness.light: case ThemeBrightness.light:
return Colors.black26; return Colors.black26;
...@@ -84,34 +92,45 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> { ...@@ -84,34 +92,45 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> {
void _handleHighlightChanged(bool value) { void _handleHighlightChanged(bool value) {
setState(() { setState(() {
// mostly just used by the RaisedButton subclass to change the elevation
highlight = value; highlight = value;
}); });
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget contents = new Container( Widget contents = new InkWell(
padding: new EdgeDims.symmetric(horizontal: 8.0), onTap: config.onPressed,
child: new Center( onHighlightChanged: _handleHighlightChanged,
widthFactor: 1.0, child: new Container(
child: config.child padding: new EdgeDims.symmetric(horizontal: 8.0),
child: new Center(
widthFactor: 1.0,
child: config.child
)
) )
); );
TextStyle style = Theme.of(context).text.button.copyWith(color: getTextColor(context));
int elevation = this.elevation;
Color color = getColor(context);
if (elevation > 0 || color != null) {
contents = new Material(
type: MaterialType.button,
color: getColor(context),
elevation: elevation,
textStyle: style,
child: contents
);
} else {
contents = new DefaultTextStyle(
style: style,
child: contents
);
}
return new Container( return new Container(
height: 36.0, height: 36.0,
constraints: new BoxConstraints(minWidth: 88.0), constraints: new BoxConstraints(minWidth: 88.0),
margin: new EdgeDims.all(8.0), margin: new EdgeDims.all(8.0),
child: new Material( child: contents
type: MaterialType.button,
elevation: elevation,
textStyle: Theme.of(context).text.button.copyWith(color: getTextColor(context)),
child: new InkWell(
onTap: config.enabled ? config.onPressed : null,
defaultColor: getColor(context, highlight: false),
highlightColor: getColor(context, highlight: true),
onHighlightChanged: _handleHighlightChanged,
child: contents
)
)
); );
} }
} }
...@@ -12,36 +12,56 @@ class RaisedButton extends MaterialButton { ...@@ -12,36 +12,56 @@ class RaisedButton extends MaterialButton {
RaisedButton({ RaisedButton({
Key key, Key key,
Widget child, Widget child,
this.color,
this.colorBrightness,
this.disabledColor,
this.elevation: 2,
this.highlightElevation: 8,
this.disabledElevation: 0,
VoidCallback onPressed VoidCallback onPressed
}) : super(key: key, }) : super(key: key,
child: child, child: child,
onPressed: onPressed); onPressed: onPressed);
final Color color;
final Color disabledColor;
/// Controls the default text color if the text color isn't explicit set.
final ThemeBrightness colorBrightness;
final int elevation;
final int highlightElevation;
final int disabledElevation;
_RaisedButtonState createState() => new _RaisedButtonState(); _RaisedButtonState createState() => new _RaisedButtonState();
} }
class _RaisedButtonState extends MaterialButtonState<RaisedButton> { class _RaisedButtonState extends MaterialButtonState<RaisedButton> {
int get elevation => config.enabled ? (highlight ? 8 : 2) : 0; int get elevation {
if (config.enabled) {
if (highlight)
return config.highlightElevation;
return config.elevation;
} else {
return config.disabledElevation;
}
}
Color getColor(BuildContext context, { bool highlight }) { Color getColor(BuildContext context) {
if (config.enabled) { if (config.enabled) {
if (config.color != null)
return config.color;
switch (Theme.of(context).brightness) { switch (Theme.of(context).brightness) {
case ThemeBrightness.light: case ThemeBrightness.light:
if (highlight) return Colors.grey[300];
return Colors.grey[350];
else
return Colors.grey[300];
break;
case ThemeBrightness.dark: case ThemeBrightness.dark:
Map<int, Color> swatch = Theme.of(context).primarySwatch ?? Colors.blue; Map<int, Color> swatch = Theme.of(context).primarySwatch ?? Colors.blue;
if (highlight) return swatch[600];
return swatch[700];
else
return swatch[600];
break;
} }
} else { } else {
if (config.disabledColor != null)
return config.disabledColor;
switch (Theme.of(context).brightness) { switch (Theme.of(context).brightness) {
case ThemeBrightness.light: case ThemeBrightness.light:
return Colors.black12; return Colors.black12;
...@@ -52,7 +72,7 @@ class _RaisedButtonState extends MaterialButtonState<RaisedButton> { ...@@ -52,7 +72,7 @@ class _RaisedButtonState extends MaterialButtonState<RaisedButton> {
} }
ThemeBrightness getColorBrightness(BuildContext context) { ThemeBrightness getColorBrightness(BuildContext context) {
return Theme.of(context).brightness; return config.colorBrightness ?? Theme.of(context).brightness;
} }
} }
...@@ -115,7 +115,10 @@ class ScaffoldState extends State<Scaffold> { ...@@ -115,7 +115,10 @@ class ScaffoldState extends State<Scaffold> {
} }
ScaffoldFeatureController<SnackBar> controller; ScaffoldFeatureController<SnackBar> controller;
controller = new ScaffoldFeatureController<SnackBar>._( controller = new ScaffoldFeatureController<SnackBar>._(
snackbar.withPerformance(_snackBarPerformance), // We provide a fallback key so that if back-to-back snackbars happen to
// match in structure, material ink splashes and highlights don't survive
// from one to the next.
snackbar.withPerformance(_snackBarPerformance, fallbackKey: new UniqueKey()),
new Completer(), new Completer(),
() { () {
assert(_snackBars.first == controller); assert(_snackBars.first == controller);
......
...@@ -5,14 +5,18 @@ ...@@ -5,14 +5,18 @@
import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'flat_button.dart';
import 'material.dart'; import 'material.dart';
import 'material_button.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart';
import 'typography.dart'; import 'typography.dart';
// https://www.google.com/design/spec/components/snackbars-toasts.html#snackbars-toasts-specs // https://www.google.com/design/spec/components/snackbars-toasts.html#snackbars-toasts-specs
const double _kSideMargins = 24.0; const double _kSideMargins = 24.0;
const double _kSingleLineVerticalPadding = 14.0; const double _kSingleLineVerticalPadding = 14.0;
const double _kMultiLineVerticalPadding = 24.0; const double _kMultiLineVerticalTopPadding = 24.0;
const double _kMultiLineVerticalSpaceBetweenTextAndButtons = 10.0;
const Color _kSnackBackground = const Color(0xFF323232); const Color _kSnackBackground = const Color(0xFF323232);
// TODO(ianh): We should check if the given text and actions are going to fit on // TODO(ianh): We should check if the given text and actions are going to fit on
...@@ -35,11 +39,11 @@ class SnackBarAction extends StatelessComponent { ...@@ -35,11 +39,11 @@ class SnackBarAction extends StatelessComponent {
final VoidCallback onPressed; final VoidCallback onPressed;
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new GestureDetector( return new Container(
onTap: onPressed, margin: const EdgeDims.only(left: _kSideMargins),
child: new Container( child: new FlatButton(
margin: const EdgeDims.only(left: _kSideMargins), onPressed: onPressed,
padding: const EdgeDims.symmetric(vertical: _kSingleLineVerticalPadding), textTheme: ButtonColor.accent,
child: new Text(label) child: new Text(label)
) )
); );
...@@ -77,6 +81,7 @@ class SnackBar extends StatelessComponent { ...@@ -77,6 +81,7 @@ class SnackBar extends StatelessComponent {
]; ];
if (actions != null) if (actions != null)
children.addAll(actions); children.addAll(actions);
ThemeData theme = Theme.of(context);
return new ClipRect( return new ClipRect(
child: new AlignTransition( child: new AlignTransition(
performance: performance, performance: performance,
...@@ -87,12 +92,20 @@ class SnackBar extends StatelessComponent { ...@@ -87,12 +92,20 @@ class SnackBar extends StatelessComponent {
color: _kSnackBackground, color: _kSnackBackground,
child: new Container( child: new Container(
margin: const EdgeDims.symmetric(horizontal: _kSideMargins), margin: const EdgeDims.symmetric(horizontal: _kSideMargins),
child: new DefaultTextStyle( child: new Theme(
style: new TextStyle(color: Theme.of(context).accentColor), data: new ThemeData(
brightness: ThemeBrightness.dark,
accentColor: theme.accentColor,
accentColorBrightness: theme.accentColorBrightness,
text: Typography.white
),
child: new FadeTransition( child: new FadeTransition(
performance: performance, performance: performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: _snackBarFadeCurve), opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: _snackBarFadeCurve),
child: new Row(children) child: new Row(
children,
alignItems: FlexAlignItems.center
)
) )
) )
) )
...@@ -110,9 +123,9 @@ class SnackBar extends StatelessComponent { ...@@ -110,9 +123,9 @@ class SnackBar extends StatelessComponent {
); );
} }
SnackBar withPerformance(Performance newPerformance) { SnackBar withPerformance(Performance newPerformance, { Key fallbackKey }) {
return new SnackBar( return new SnackBar(
key: key, key: key ?? fallbackKey,
content: content, content: content,
actions: actions, actions: actions,
duration: duration, duration: duration,
......
...@@ -10,11 +10,11 @@ import 'package:flutter/rendering.dart'; ...@@ -10,11 +10,11 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
import 'constants.dart';
import 'icon.dart'; import 'icon.dart';
import 'icon_theme.dart'; import 'icon_theme.dart';
import 'icon_theme_data.dart'; import 'icon_theme_data.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'material.dart';
import 'theme.dart'; import 'theme.dart';
typedef void TabSelectedIndexChanged(int selectedIndex); typedef void TabSelectedIndexChanged(int selectedIndex);
...@@ -403,6 +403,10 @@ class TabBarSelection { ...@@ -403,6 +403,10 @@ class TabBarSelection {
int _previousIndex = 0; int _previousIndex = 0;
} }
/// A tab strip, consisting of several TabLabels and a TabBarSelection.
/// The TabBarSelection can be used to link this to a TabBarView.
///
/// Tabs must always have an ancestor Material object.
class TabBar extends Scrollable { class TabBar extends Scrollable {
TabBar({ TabBar({
Key key, Key key,
...@@ -551,13 +555,13 @@ class _TabBarState extends ScrollableState<TabBar> { ...@@ -551,13 +555,13 @@ class _TabBarState extends ScrollableState<TabBar> {
Widget buildContent(BuildContext context) { Widget buildContent(BuildContext context) {
assert(config.labels != null && config.labels.isNotEmpty); assert(config.labels != null && config.labels.isNotEmpty);
assert(Material.of(context) != null);
ThemeData themeData = Theme.of(context); ThemeData themeData = Theme.of(context);
Color backgroundColor = themeData.primaryColor; Color backgroundColor = Material.of(context).color;
Color indicatorColor = themeData.accentColor; Color indicatorColor = themeData.accentColor;
if (indicatorColor == backgroundColor) { if (indicatorColor == backgroundColor)
indicatorColor = Colors.white; indicatorColor = Colors.white;
}
TextStyle textStyle = themeData.primaryTextTheme.body1; TextStyle textStyle = themeData.primaryTextTheme.body1;
IconThemeData iconTheme = themeData.primaryIconTheme; IconThemeData iconTheme = themeData.primaryIconTheme;
...@@ -571,7 +575,7 @@ class _TabBarState extends ScrollableState<TabBar> { ...@@ -571,7 +575,7 @@ class _TabBarState extends ScrollableState<TabBar> {
textAndIcons = true; textAndIcons = true;
} }
Widget content = new IconTheme( Widget contents = new IconTheme(
data: iconTheme, data: iconTheme,
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: textStyle, style: textStyle,
...@@ -594,23 +598,17 @@ class _TabBarState extends ScrollableState<TabBar> { ...@@ -594,23 +598,17 @@ class _TabBarState extends ScrollableState<TabBar> {
); );
if (config.isScrollable) { if (config.isScrollable) {
content = new SizeObserver( contents = new SizeObserver(
onSizeChanged: _handleViewportSizeChanged, onSizeChanged: _handleViewportSizeChanged,
child: new Viewport( child: new Viewport(
scrollDirection: ScrollDirection.horizontal, scrollDirection: ScrollDirection.horizontal,
scrollOffset: new Offset(scrollOffset, 0.0), scrollOffset: new Offset(scrollOffset, 0.0),
child: content child: contents
) )
); );
} }
return new AnimatedContainer( return contents;
decoration: new BoxDecoration(
backgroundColor: backgroundColor
),
duration: kThemeChangeDuration,
child: content
);
} }
} }
......
...@@ -27,15 +27,7 @@ class ThemeData { ...@@ -27,15 +27,7 @@ class ThemeData {
// Some users want the pre-multiplied color, others just want the opacity. // Some users want the pre-multiplied color, others just want the opacity.
hintColor = brightness == ThemeBrightness.dark ? const Color(0x42FFFFFF) : const Color(0x4C000000), hintColor = brightness == ThemeBrightness.dark ? const Color(0x42FFFFFF) : const Color(0x4C000000),
hintOpacity = brightness == ThemeBrightness.dark ? 0.26 : 0.30, hintOpacity = brightness == ThemeBrightness.dark ? 0.26 : 0.30,
// TODO(eseidel): Where are highlight and selected colors documented? highlightColor = brightness == ThemeBrightness.dark ? const Color(0x42FFFFFF) : const Color(0x1F000000),
// I flipped highlight/selected to match the News app (which is clearly not quite Material)
// Gmail has an interesting behavior of showing selected darker until
// you click on a different drawer item when the first one loses its
// selected color and becomes lighter, the ink then fills to make the new
// click dark to match the previous (resting) selected state. States
// revert when you cancel the tap.
highlightColor = const Color(0x33999999),
selectedColor = const Color(0x66999999),
text = brightness == ThemeBrightness.dark ? Typography.white : Typography.black { text = brightness == ThemeBrightness.dark ? Typography.white : Typography.black {
assert(brightness != null); assert(brightness != null);
...@@ -63,6 +55,13 @@ class ThemeData { ...@@ -63,6 +55,13 @@ class ThemeData {
/// The brightness of the overall theme of the application. Used by widgets /// The brightness of the overall theme of the application. Used by widgets
/// like buttons to determine what color to pick when not using the primary or /// like buttons to determine what color to pick when not using the primary or
/// accent color. /// accent color.
///
/// When the ThemeBrightness is dark, the canvas, card, and primary colors are
/// all dark. When the ThemeBrightness is light, the canvas and card colors
/// are bright, and the primary color's darkness varies as described by
/// primaryColorBrightness. The primaryColor does not contrast well with the
/// card and canvas colors when the brightness is dask; when the birghtness is
/// dark, use Colors.white or the accentColor for a contrasting color.
final ThemeBrightness brightness; final ThemeBrightness brightness;
final Map<int, Color> primarySwatch; final Map<int, Color> primarySwatch;
...@@ -71,8 +70,9 @@ class ThemeData { ...@@ -71,8 +70,9 @@ class ThemeData {
final Color dividerColor; final Color dividerColor;
final Color hintColor; final Color hintColor;
final Color highlightColor; final Color highlightColor;
final Color selectedColor;
final double hintOpacity; final double hintOpacity;
/// Text with a color that contrasts with the card and canvas colors.
final TextTheme text; final TextTheme text;
/// The background colour for major parts of the app (toolbars, tab bars, etc) /// The background colour for major parts of the app (toolbars, tab bars, etc)
...@@ -128,7 +128,6 @@ class ThemeData { ...@@ -128,7 +128,6 @@ class ThemeData {
(otherData.dividerColor == dividerColor) && (otherData.dividerColor == dividerColor) &&
(otherData.hintColor == hintColor) && (otherData.hintColor == hintColor) &&
(otherData.highlightColor == highlightColor) && (otherData.highlightColor == highlightColor) &&
(otherData.selectedColor == selectedColor) &&
(otherData.hintOpacity == hintOpacity) && (otherData.hintOpacity == hintOpacity) &&
(otherData.text == text) && (otherData.text == text) &&
(otherData.primaryColorBrightness == primaryColorBrightness) && (otherData.primaryColorBrightness == primaryColorBrightness) &&
...@@ -143,7 +142,6 @@ class ThemeData { ...@@ -143,7 +142,6 @@ class ThemeData {
value = 37 * value + dividerColor.hashCode; value = 37 * value + dividerColor.hashCode;
value = 37 * value + hintColor.hashCode; value = 37 * value + hintColor.hashCode;
value = 37 * value + highlightColor.hashCode; value = 37 * value + highlightColor.hashCode;
value = 37 * value + selectedColor.hashCode;
value = 37 * value + hintOpacity.hashCode; value = 37 * value + hintOpacity.hashCode;
value = 37 * value + text.hashCode; value = 37 * value + text.hashCode;
value = 37 * value + primaryColorBrightness.hashCode; value = 37 * value + primaryColorBrightness.hashCode;
......
...@@ -7,7 +7,7 @@ import 'package:flutter/widgets.dart'; ...@@ -7,7 +7,7 @@ import 'package:flutter/widgets.dart';
import 'constants.dart'; import 'constants.dart';
import 'icon_theme.dart'; import 'icon_theme.dart';
import 'icon_theme_data.dart'; import 'icon_theme_data.dart';
import 'shadows.dart'; import 'material.dart';
import 'tabs.dart'; import 'tabs.dart';
import 'theme.dart'; import 'theme.dart';
import 'typography.dart'; import 'typography.dart';
...@@ -36,9 +36,9 @@ class ToolBar extends StatelessComponent { ...@@ -36,9 +36,9 @@ class ToolBar extends StatelessComponent {
final TextTheme textTheme; final TextTheme textTheme;
final EdgeDims padding; final EdgeDims padding;
ToolBar withPadding(EdgeDims newPadding) { ToolBar withPadding(EdgeDims newPadding, { Key fallbackKey }) {
return new ToolBar( return new ToolBar(
key: key, key: key ?? fallbackKey,
left: left, left: left,
center: center, center: center,
right: right, right: right,
...@@ -67,52 +67,63 @@ class ToolBar extends StatelessComponent { ...@@ -67,52 +67,63 @@ class ToolBar extends StatelessComponent {
sideStyle ??= primaryTextTheme.body2; sideStyle ??= primaryTextTheme.body2;
} }
List<Widget> children = new List<Widget>(); final List<Widget> firstRow = <Widget>[];
if (left != null) if (left != null)
children.add(left); firstRow.add(left);
firstRow.add(
children.add(
new Flexible( new Flexible(
child: new Padding( child: new Padding(
child: center != null ? new DefaultTextStyle(child: center, style: centerStyle) : null, padding: new EdgeDims.only(left: 24.0),
padding: new EdgeDims.only(left: 24.0) child: center != null ? new DefaultTextStyle(style: centerStyle, child: center) : null
) )
) )
); );
if (right != null) if (right != null)
children.addAll(right); firstRow.addAll(right);
final List<Widget> columnChildren = <Widget>[ final List<Widget> rows = <Widget>[
new Container(height: kToolBarHeight, child: new Row(children)) new Container(
height: kToolBarHeight,
child: new DefaultTextStyle(
style: sideStyle,
child: new Row(firstRow)
)
)
]; ];
if (bottom != null) {
if (bottom != null) rows.add(
columnChildren.add(new DefaultTextStyle( new DefaultTextStyle(
style: centerStyle, style: centerStyle,
child: new Container(height: kExtendedToolBarHeight - kToolBarHeight, child: bottom) child: new Container(
)); height: kExtendedToolBarHeight - kToolBarHeight,
child: bottom
)
)
);
}
if (tabBar != null) if (tabBar != null)
columnChildren.add(tabBar); rows.add(tabBar);
EdgeDims combinedPadding = new EdgeDims.symmetric(horizontal: 8.0);
if (padding != null)
combinedPadding += padding;
Widget content = new AnimatedContainer( Widget contents = new Material(
duration: kThemeChangeDuration, color: color,
padding: new EdgeDims.symmetric(horizontal: 8.0), elevation: elevation,
decoration: new BoxDecoration( child: new Container(
backgroundColor: color, padding: combinedPadding,
boxShadow: elevationToShadow[elevation] child: new Column(
), rows,
child: new DefaultTextStyle( justifyContent: FlexJustifyContent.collapse
style: sideStyle, )
child: new Container(padding: padding, child: new Column(columnChildren, justifyContent: FlexJustifyContent.collapse))
) )
); );
if (iconThemeData != null) if (iconThemeData != null)
content = new IconTheme(data: iconThemeData, child: content); contents = new IconTheme(data: iconThemeData, child: contents);
return content;
return contents;
} }
} }
...@@ -386,8 +386,17 @@ abstract class State<T extends StatefulComponent> { ...@@ -386,8 +386,17 @@ abstract class State<T extends StatefulComponent> {
_element.markNeedsBuild(); _element.markNeedsBuild();
} }
/// Called when this object is removed from the tree. Override this to clean /// Called when this object is removed from the tree.
/// up any resources allocated by this object. /// The object might momentarily be reattached to the tree elsewhere.
///
/// Use this to clean up any links between this state and other
/// elements in the tree (e.g. if you have provided an ancestor with
/// a pointer to a descendant's renderObject).
void deactivate() { }
/// Called when this object is removed from the tree permanently.
/// Override this to clean up any resources allocated by this
/// object.
/// ///
/// If you override this, make sure to end your method with a call to /// If you override this, make sure to end your method with a call to
/// super.dispose(). /// super.dispose().
...@@ -405,6 +414,11 @@ abstract class State<T extends StatefulComponent> { ...@@ -405,6 +414,11 @@ abstract class State<T extends StatefulComponent> {
/// provides the set of inherited widgets for this location in the tree. /// provides the set of inherited widgets for this location in the tree.
Widget build(BuildContext context); Widget build(BuildContext context);
/// Called when an Inherited widget in the ancestor chain has changed. Usually
/// there is nothing to do here; whenever this is called, build() is also
/// called.
void dependenciesChanged(Type affectedWidgetType) { }
String toString() { String toString() {
final List<String> data = <String>[]; final List<String> data = <String>[];
debugFillDescription(data); debugFillDescription(data);
...@@ -540,6 +554,7 @@ abstract class BuildContext { ...@@ -540,6 +554,7 @@ abstract class BuildContext {
InheritedWidget inheritFromWidgetOfType(Type targetType); InheritedWidget inheritFromWidgetOfType(Type targetType);
Widget ancestorWidgetOfType(Type targetType); Widget ancestorWidgetOfType(Type targetType);
State ancestorStateOfType(Type targetType); State ancestorStateOfType(Type targetType);
RenderObject ancestorRenderObjectOfType(Type targetType);
void visitAncestorElements(bool visitor(Element element)); void visitAncestorElements(bool visitor(Element element));
void visitChildElements(void visitor(Element element)); void visitChildElements(void visitor(Element element));
} }
...@@ -777,7 +792,7 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -777,7 +792,7 @@ abstract class Element<T extends Widget> implements BuildContext {
assert(child._parent == this); assert(child._parent == this);
child._parent = null; child._parent = null;
child.detachRenderObject(); child.detachRenderObject();
_inactiveElements.add(child); _inactiveElements.add(child); // this eventually calls child.deactivate()
} }
void deactivate() { void deactivate() {
...@@ -839,13 +854,24 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -839,13 +854,24 @@ abstract class Element<T extends Widget> implements BuildContext {
return statefulAncestor?.state; return statefulAncestor?.state;
} }
RenderObject ancestorRenderObjectOfType(Type targetType) {
Element ancestor = _parent;
while (ancestor != null) {
if (ancestor is RenderObjectElement && ancestor.renderObject.runtimeType == targetType)
break;
ancestor = ancestor._parent;
}
RenderObjectElement renderObjectAncestor = ancestor;
return renderObjectAncestor?.renderObject;
}
void visitAncestorElements(bool visitor(Element element)) { void visitAncestorElements(bool visitor(Element element)) {
Element ancestor = _parent; Element ancestor = _parent;
while (ancestor != null && visitor(ancestor)) while (ancestor != null && visitor(ancestor))
ancestor = ancestor._parent; ancestor = ancestor._parent;
} }
void dependenciesChanged() { void dependenciesChanged(Type affectedWidgetType) {
assert(false); assert(false);
} }
...@@ -1024,7 +1050,7 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -1024,7 +1050,7 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
/// Called by rebuild() after the appropriate checks have been made. /// Called by rebuild() after the appropriate checks have been made.
void performRebuild(); void performRebuild();
void dependenciesChanged() { void dependenciesChanged(Type affectedWidgetType) {
markNeedsBuild(); markNeedsBuild();
} }
...@@ -1169,6 +1195,11 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>> ...@@ -1169,6 +1195,11 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>>
rebuild(); rebuild();
} }
void deactivate() {
_state.deactivate();
super.deactivate();
}
void unmount() { void unmount() {
super.unmount(); super.unmount();
_state.dispose(); _state.dispose();
...@@ -1183,6 +1214,11 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>> ...@@ -1183,6 +1214,11 @@ class StatefulComponentElement<T extends StatefulComponent, U extends State<T>>
_state = null; _state = null;
} }
void dependenciesChanged(Type affectedWidgetType) {
super.dependenciesChanged(affectedWidgetType);
_state.dependenciesChanged(affectedWidgetType);
}
void debugFillDescription(List<String> description) { void debugFillDescription(List<String> description) {
super.debugFillDescription(description); super.debugFillDescription(description);
if (state != null) if (state != null)
...@@ -1252,7 +1288,7 @@ class InheritedElement extends ProxyElement<InheritedWidget> { ...@@ -1252,7 +1288,7 @@ class InheritedElement extends ProxyElement<InheritedWidget> {
void notifyChildren(Element child) { void notifyChildren(Element child) {
if (child._dependencies != null && if (child._dependencies != null &&
child._dependencies.contains(ourRuntimeType)) { child._dependencies.contains(ourRuntimeType)) {
child.dependenciesChanged(); child.dependenciesChanged(ourRuntimeType);
} }
if (child.runtimeType != ourRuntimeType) if (child.runtimeType != ourRuntimeType)
child.visitChildren(notifyChildren); child.visitChildren(notifyChildren);
......
// 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 'framework.dart';
/// Return true to cancel the notification bubbling.
typedef bool NotificationListenerCallback<T extends Notification>(T notification);
abstract class Notification {
void dispatch(BuildContext target) {
target.visitAncestorElements((Element element) {
if (element is StatelessComponentElement &&
element.widget is NotificationListener) {
final NotificationListener widget = element.widget;
if (widget._dispatch(this))
return false;
}
return true;
});
}
}
class NotificationListener<T extends Notification> extends StatelessComponent {
NotificationListener({
Key key,
this.child,
this.onNotification
}) : super(key: key);
final Widget child;
final NotificationListenerCallback<T> onNotification;
bool _dispatch(Notification notification) {
if (onNotification != null && notification is T)
return onNotification(notification);
return false;
}
Widget build(BuildContext context) => child;
}
/// Indicates that the layout of one of the descendants of the object receiving
/// this notification has changed in some way, and that therefore any
/// assumptions about that layout are no longer valid.
///
/// Useful if, for instance, you're trying to align multiple descendants.
///
/// Be aware that in the widgets library, only the [Scrollable] classes dispatch
/// this notification. (Transitions, in particular, do not.) Changing one's
/// layout in one's build function does not cause this notification to be
/// dispatched automatically. If an ancestor expects to be notified for any
/// layout change, make sure you only use widgets that either never change
/// layout, or that do notify their ancestors when appropriate.
class LayoutChangedNotification extends Notification { }
...@@ -16,6 +16,7 @@ import 'framework.dart'; ...@@ -16,6 +16,7 @@ import 'framework.dart';
import 'gesture_detector.dart'; import 'gesture_detector.dart';
import 'homogeneous_viewport.dart'; import 'homogeneous_viewport.dart';
import 'mixed_viewport.dart'; import 'mixed_viewport.dart';
import 'notification_listener.dart';
import 'page_storage.dart'; import 'page_storage.dart';
// The gesture velocity properties are pixels/second, config min,max limits are pixels/ms // The gesture velocity properties are pixels/second, config min,max limits are pixels/ms
...@@ -50,6 +51,8 @@ abstract class Scrollable extends StatefulComponent { ...@@ -50,6 +51,8 @@ abstract class Scrollable extends StatefulComponent {
final ScrollListener onScrollEnd; final ScrollListener onScrollEnd;
final SnapOffsetCallback snapOffsetCallback; final SnapOffsetCallback snapOffsetCallback;
final double snapAlignmentOffset; final double snapAlignmentOffset;
ScrollableState createState();
} }
abstract class ScrollableState<T extends Scrollable> extends State<T> { abstract class ScrollableState<T extends Scrollable> extends State<T> {
...@@ -180,6 +183,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -180,6 +183,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
_scrollOffset = newScrollOffset; _scrollOffset = newScrollOffset;
}); });
PageStorage.of(context)?.writeState(context, _scrollOffset); PageStorage.of(context)?.writeState(context, _scrollOffset);
new ScrollNotification(this, _scrollOffset).dispatch(context);
dispatchOnScroll(); dispatchOnScroll();
} }
...@@ -271,6 +275,12 @@ ScrollableState findScrollableAncestor(BuildContext context) { ...@@ -271,6 +275,12 @@ ScrollableState findScrollableAncestor(BuildContext context) {
return result; return result;
} }
class ScrollNotification extends Notification {
ScrollNotification(this.scrollable, this.position);
final ScrollableState scrollable;
final double position;
}
Future ensureWidgetIsVisible(BuildContext context, { Duration duration, Curve curve }) { Future ensureWidgetIsVisible(BuildContext context, { Duration duration, Curve curve }) {
assert(context.findRenderObject() is RenderBox); assert(context.findRenderObject() is RenderBox);
// TODO(abarth): This function doesn't handle nested scrollable widgets. // TODO(abarth): This function doesn't handle nested scrollable widgets.
......
...@@ -25,6 +25,7 @@ export 'src/widgets/mimic.dart'; ...@@ -25,6 +25,7 @@ export 'src/widgets/mimic.dart';
export 'src/widgets/mixed_viewport.dart'; export 'src/widgets/mixed_viewport.dart';
export 'src/widgets/modal_barrier.dart'; export 'src/widgets/modal_barrier.dart';
export 'src/widgets/navigator.dart'; export 'src/widgets/navigator.dart';
export 'src/widgets/notification_listener.dart';
export 'src/widgets/overlay.dart'; export 'src/widgets/overlay.dart';
export 'src/widgets/page_storage.dart'; export 'src/widgets/page_storage.dart';
export 'src/widgets/placeholder.dart'; export 'src/widgets/placeholder.dart';
......
...@@ -2,6 +2,10 @@ import 'package:flutter/animation.dart'; ...@@ -2,6 +2,10 @@ import 'package:flutter/animation.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
void main() { void main() {
test("Check for a time dilation being in effect", () {
expect(timeDilation, equals(1.0));
});
test("Can cancel queued callback", () { test("Can cancel queued callback", () {
int secondId; int secondId;
......
...@@ -11,16 +11,18 @@ void main() { ...@@ -11,16 +11,18 @@ void main() {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
DateTime currentValue; DateTime currentValue;
Widget widget = new Block(<Widget>[ Widget widget = new Material(
new DatePicker( child: new Block(<Widget>[
selectedDate: new DateTime.utc(2015, 6, 9, 7, 12), new DatePicker(
firstDate: new DateTime.utc(2013), selectedDate: new DateTime.utc(2015, 6, 9, 7, 12),
lastDate: new DateTime.utc(2018), firstDate: new DateTime.utc(2013),
onChanged: (DateTime dateTime) { lastDate: new DateTime.utc(2018),
currentValue = dateTime; onChanged: (DateTime dateTime) {
} currentValue = dateTime;
) }
]); )
])
);
tester.pumpWidget(widget); tester.pumpWidget(widget);
......
...@@ -11,18 +11,22 @@ import 'test_matchers.dart'; ...@@ -11,18 +11,22 @@ import 'test_matchers.dart';
Key firstKey = new Key('first'); Key firstKey = new Key('first');
Key secondKey = new Key('second'); Key secondKey = new Key('second');
final Map<String, RouteBuilder> routes = <String, RouteBuilder>{ final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
'/': (RouteArguments args) => new Block([ '/': (RouteArguments args) => new Material(
new Container(height: 100.0, width: 100.0), child: new Block([
new Card(child: new Hero(tag: 'a', child: new Container(height: 100.0, width: 100.0, key: firstKey))), new Container(height: 100.0, width: 100.0),
new Container(height: 100.0, width: 100.0), new Card(child: new Hero(tag: 'a', child: new Container(height: 100.0, width: 100.0, key: firstKey))),
new FlatButton(child: new Text('button'), onPressed: () => Navigator.pushNamed(args.context, '/two')), new Container(height: 100.0, width: 100.0),
]), new FlatButton(child: new Text('button'), onPressed: () => Navigator.pushNamed(args.context, '/two')),
'/two': (RouteArguments args) => new Block([ ])
new Container(height: 150.0, width: 150.0), ),
new Card(child: new Hero(tag: 'a', child: new Container(height: 150.0, width: 150.0, key: secondKey))), '/two': (RouteArguments args) => new Material(
new Container(height: 150.0, width: 150.0), child: new Block([
new FlatButton(child: new Text('button'), onPressed: () => Navigator.pop(args.context)), new Container(height: 150.0, width: 150.0),
]), new Card(child: new Hero(tag: 'a', child: new Container(height: 150.0, width: 150.0, key: secondKey))),
new Container(height: 150.0, width: 150.0),
new FlatButton(child: new Text('button'), onPressed: () => Navigator.pop(args.context)),
])
),
}; };
void main() { void main() {
......
...@@ -10,10 +10,12 @@ import 'package:test/test.dart'; ...@@ -10,10 +10,12 @@ import 'package:test/test.dart';
TabBarSelection selection; TabBarSelection selection;
Widget buildFrame({ List<String> tabs, bool isScrollable: false }) { Widget buildFrame({ List<String> tabs, bool isScrollable: false }) {
return new TabBar( return new Material(
labels: tabs.map((String tab) => new TabLabel(text: tab)).toList(), child: new TabBar(
selection: selection, labels: tabs.map((String tab) => new TabLabel(text: tab)).toList(),
isScrollable: isScrollable selection: selection,
isScrollable: isScrollable
)
); );
} }
......
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