snack_bar.dart 6.25 KB
Newer Older
1 2 3 4
// 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.

5
import 'package:flutter/widgets.dart';
6

7
import 'button.dart';
8
import 'flat_button.dart';
9
import 'material.dart';
10
import 'scaffold.dart';
11
import 'theme_data.dart';
12
import 'theme.dart';
13
import 'typography.dart';
Matt Perry's avatar
Matt Perry committed
14

Hixie's avatar
Hixie committed
15
// https://www.google.com/design/spec/components/snackbars-toasts.html#snackbars-toasts-specs
16
const double _kSideMargins = 24.0;
Hixie's avatar
Hixie committed
17
const double _kSingleLineVerticalPadding = 14.0;
18 19
const double _kMultiLineVerticalTopPadding = 24.0;
const double _kMultiLineVerticalSpaceBetweenTextAndButtons = 10.0;
20
const Color _kSnackBackground = const Color(0xFF323232);
Matt Perry's avatar
Matt Perry committed
21

Hixie's avatar
Hixie committed
22 23 24 25 26 27
// TODO(ianh): We should check if the given text and actions are going to fit on
// one line or not, and if they are, use the single-line layout, and if not, use
// the multiline layout. See link above.

// TODO(ianh): Implement the Tablet version of snackbar if we're "on a tablet".

28
const Duration _kSnackBarTransitionDuration = const Duration(milliseconds: 250);
Hixie's avatar
Hixie committed
29 30
const Duration kSnackBarShortDisplayDuration = const Duration(milliseconds: 1500);
const Duration kSnackBarMediumDisplayDuration = const Duration(milliseconds: 2750);
31
const Curve _snackBarHeightCurve = Curves.fastOutSlowIn;
32
const Curve _snackBarFadeCurve = const Interval(0.72, 1.0, curve: Curves.fastOutSlowIn);
Hixie's avatar
Hixie committed
33

34 35 36 37 38
/// A button for a [SnackBar], known as an "action".
///
/// Snack bar actions are always enabled. If you want to disable a snack bar
/// action, simply don't include it in the snack bar.
///
39 40
/// Snack bar actions can only be pressed once. Subsequent presses are ignored.
///
41 42 43 44
/// See also:
///
///  * [SnackBar]
///  * <https://www.google.com/design/spec/components/snackbars-toasts.html>
45
class SnackBarAction extends StatefulWidget {
46
  SnackBarAction({Key key, this.label, this.onPressed }) : super(key: key) {
47
    assert(label != null);
48
    assert(onPressed != null);
49 50
  }

51
  /// The button label.
52
  final String label;
53 54

  /// The callback to be invoked when the button is pressed. Must be non-null.
55 56 57
  ///
  /// This callback will be invoked at most once each time this action is
  /// displayed in a [SnackBar].
58
  final VoidCallback onPressed;
59

60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
  @override
  _SnackBarActionState createState() => new _SnackBarActionState();
}

class _SnackBarActionState extends State<SnackBarAction> {
  bool _haveTriggeredAction = false;

  void _handlePressed() {
    if (_haveTriggeredAction)
      return;
    setState(() {
      _haveTriggeredAction = true;
    });
    config.onPressed();
  }

76
  @override
Hixie's avatar
Hixie committed
77
  Widget build(BuildContext context) {
78
    return new Container(
79
      margin: const EdgeInsets.only(left: _kSideMargins),
80
      child: new FlatButton(
81
        onPressed: _haveTriggeredAction ? null : _handlePressed,
82
        textTheme: ButtonColor.accent,
83
        child: new Text(config.label)
84 85 86 87
      )
    );
  }
}
88

89 90 91 92 93 94
/// A lightweight message with an optional action which briefly displays at the
/// bottom of the screen.
///
/// Displayed with the Scaffold.of().showSnackBar() API.
///
/// See also:
95
///
96 97
///  * [Scaffold.of] and [ScaffoldState.showSnackBar]
///  * [SnackBarAction]
98
///  * <https://www.google.com/design/spec/components/snackbars-toasts.html>
99
class SnackBar extends StatelessWidget {
Hixie's avatar
Hixie committed
100
  SnackBar({
101 102
    Key key,
    this.content,
103
    this.action,
Hixie's avatar
Hixie committed
104
    this.duration: kSnackBarShortDisplayDuration,
105
    this.animation
106
  }) : super(key: key) {
107 108 109
    assert(content != null);
  }

110
  final Widget content;
111
  final SnackBarAction action;
Hixie's avatar
Hixie committed
112
  final Duration duration;
113
  final Animation<double> animation;
114

115
  @override
116
  Widget build(BuildContext context) {
117
    assert(animation != null);
Hixie's avatar
Hixie committed
118
    List<Widget> children = <Widget>[
119 120
      new Flexible(
        child: new Container(
121
          margin: const EdgeInsets.symmetric(vertical: _kSingleLineVerticalPadding),
122
          child: new DefaultTextStyle(
123
            style: Typography.white.subhead,
124
            child: content
125 126 127
          )
        )
      )
128
    ];
129 130
    if (action != null)
      children.add(action);
131
    CurvedAnimation heightAnimation = new CurvedAnimation(parent: animation, curve: _snackBarHeightCurve);
132
    CurvedAnimation fadeAnimation = new CurvedAnimation(parent: animation, curve: _snackBarFadeCurve, reverseCurve: const Step(0.0));
133
    ThemeData theme = Theme.of(context);
134 135 136
    return new ClipRect(
      child: new AnimatedBuilder(
        animation: heightAnimation,
137
        builder: (BuildContext context, Widget child) {
138
          return new Align(
139
            alignment: FractionalOffset.topLeft,
140 141 142
            heightFactor: heightAnimation.value,
            child: child
          );
143
        },
Hixie's avatar
Hixie committed
144 145
        child: new Semantics(
          container: true,
146 147 148
          child: new Dismissable(
            key: new Key('dismissable'),
            direction: DismissDirection.down,
149
            resizeDuration: null,
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
            onDismissed: (DismissDirection direction) {
              Scaffold.of(context).removeCurrentSnackBar();
            },
            child: new Material(
              elevation: 6,
              color: _kSnackBackground,
              child: new Container(
                margin: const EdgeInsets.symmetric(horizontal: _kSideMargins),
                child: new Theme(
                  data: new ThemeData(
                    brightness: ThemeBrightness.dark,
                    accentColor: theme.accentColor,
                    accentColorBrightness: theme.accentColorBrightness,
                    textTheme: Typography.white
                  ),
                  child: new FadeTransition(
                    opacity: fadeAnimation,
                    child: new Row(
                      children: children,
                      crossAxisAlignment: CrossAxisAlignment.center
                    )
Hixie's avatar
Hixie committed
171
                  )
172 173 174 175 176
                )
              )
            )
          )
        )
177 178
      )
    );
179
  }
180

Hixie's avatar
Hixie committed
181
  // API for Scaffold.addSnackBar():
182

183 184
  static AnimationController createAnimationController() {
    return new AnimationController(
185
      duration: _kSnackBarTransitionDuration,
Hixie's avatar
Hixie committed
186 187 188
      debugLabel: 'SnackBar'
    );
  }
189

190
  SnackBar withAnimation(Animation<double> newAnimation, { Key fallbackKey }) {
Hixie's avatar
Hixie committed
191
    return new SnackBar(
192
      key: key ?? fallbackKey,
Hixie's avatar
Hixie committed
193
      content: content,
194
      action: action,
Hixie's avatar
Hixie committed
195
      duration: duration,
196
      animation: newAnimation
Hixie's avatar
Hixie committed
197 198
    );
  }
199
}