dialog.dart 9.72 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 6
import 'dart:async';

7
import 'package:flutter/widgets.dart';
8

9
import 'button.dart';
10
import 'button_bar.dart';
11 12
import 'colors.dart';
import 'material.dart';
13
import 'theme.dart';
14

15
/// A material design dialog.
16
///
17 18 19 20
/// This dialog widget does not have any opinion about the contents of the
/// dialog. Rather than using this widget directly, consider using [AlertDialog]
/// or [SimpleDialog], which implement specific kinds of material design
/// dialogs.
21 22
///
/// See also:
Ian Hickson's avatar
Ian Hickson committed
23
///
24 25 26
///  * [AlertDialog], for dialogs that have a message and some buttons.
///  * [SimpleDialog], for dialogs that offer a variety of options.
///  * [showDialog], which actually displays the dialog and returns its result.
27
///  * <https://material.google.com/components/dialogs.html>
28
class Dialog extends StatelessWidget {
29 30 31
  /// Creates a dialog.
  ///
  /// Typically used in conjunction with [showDialog].
32
  Dialog({
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
    Key key,
    this.child,
  }) : super(key: key);

  final Widget child;

  Color _getColor(BuildContext context) {
    Brightness brightness = Theme.of(context).brightness;
    assert(brightness != null);
    switch (brightness) {
      case Brightness.light:
        return Colors.white;
      case Brightness.dark:
        return Colors.grey[800];
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new Container(
55
        margin: const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0),
56
        child: new ConstrainedBox(
57
          constraints: const BoxConstraints(minWidth: 280.0),
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
          child: new Material(
            elevation: 24,
            color: _getColor(context),
            type: MaterialType.card,
            child: child
          )
        )
      )
    );
  }
}

/// A material design alert dialog.
///
/// An alert dialog informs the user about situations that require
/// acknowledgement. An alert dialog has an optional title and an optional list
/// of actions. The title is displayed above the content and the actions are
/// displayed below the content.
///
/// If the content is too large to fit on the screen vertically, the dialog will
/// display the title and the actions and let the content overflow. Consider
/// using a scrolling widget, such as [Block], for [content] to avoid overflow.
///
/// For dialogs that offer the user a choice between several options, consider
/// using a [SimpleDialog].
///
/// Typically passed as the child widget to [showDialog], which displays the
/// dialog.
///
/// See also:
///
89 90 91
///  * [SimpleDialog], which handles the scrolling of the contents but has no [actions].
///  * [Dialog], on which [AlertDialog] and [SimpleDialog] are based.
///  * [showDialog], which actually displays the dialog and returns its result.
92 93 94 95 96 97
///  * <https://material.google.com/components/dialogs.html#dialogs-alerts>
class AlertDialog extends StatelessWidget {
  /// Creates an alert dialog.
  ///
  /// Typically used in conjunction with [showDialog].
  AlertDialog({
98
    Key key,
99
    this.title,
100
    this.titlePadding,
101
    this.content,
102
    this.contentPadding,
103
    this.actions
104
  }) : super(key: key);
105 106 107

  /// The (optional) title of the dialog is displayed in a large font at the top
  /// of the dialog.
Ian Hickson's avatar
Ian Hickson committed
108 109
  ///
  /// Typically a [Text] widget.
110 111
  final Widget title;

112 113 114 115
  /// Padding around the title.
  ///
  /// Uses material design default if none is supplied. If there is no title, no
  /// padding will be provided.
116
  final EdgeInsets titlePadding;
117

118 119
  /// The (optional) content of the dialog is displayed in the center of the
  /// dialog in a lighter font.
Ian Hickson's avatar
Ian Hickson committed
120 121 122 123
  ///
  /// Typically, this is a [Block] containing the contents of the dialog. Using
  /// a [Block] ensures that the contents can scroll if they are too big to fit
  /// on the display.
124 125
  final Widget content;

126 127 128
  /// Padding around the content.
  ///
  /// Uses material design default if none is supplied.
129
  final EdgeInsets contentPadding;
130

131 132
  /// The (optional) set of actions that are displayed at the bottom of the
  /// dialog.
Ian Hickson's avatar
Ian Hickson committed
133 134 135 136
  ///
  /// Typically this is a list of [FlatButton] widgets.
  ///
  /// These widgets will be wrapped in a [ButtonBar].
137 138
  final List<Widget> actions;

139
  @override
140
  Widget build(BuildContext context) {
141
    final List<Widget> children = new List<Widget>();
142 143

    if (title != null) {
144 145
      children.add(new Padding(
        padding: titlePadding ?? new EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0),
146
        child: new DefaultTextStyle(
147
          style: Theme.of(context).textTheme.title,
148 149 150 151 152 153
          child: title
        )
      ));
    }

    if (content != null) {
154 155
      children.add(new Flexible(
        fit: FlexFit.loose,
156 157 158 159 160
        child: new Padding(
          padding: contentPadding ?? const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),
          child: new DefaultTextStyle(
            style: Theme.of(context).textTheme.subhead,
            child: content
161
          )
162 163 164 165
        )
      ));
    }

166
    if (actions != null) {
167
      children.add(new ButtonTheme.bar(
168 169
        child: new ButtonBar(
          children: actions
170 171
        )
      ));
172
    }
173

174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
    return new Dialog(
      child: new IntrinsicWidth(
        child: new Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: children
        )
      )
    );
  }
}

/// A simple material design dialog.
///
/// A simple dialog offers the user a choice between several options. A simple
/// dialog has an optional title that is displayed above the choices.
///
/// For dialogs that inform the user about a situation, consider using an
/// [AlertDialog].
///
/// Typically passed as the child widget to [showDialog], which displays the
/// dialog.
///
/// See also:
///
199 200 201
///  * [AlertDialog], for dialogs that have a row of buttons below the body.
///  * [Dialog], on which [SimpleDialog] and [AlertDialog] are based.
///  * [showDialog], which actually displays the dialog and returns its result.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
///  * <https://material.google.com/components/dialogs.html#dialogs-simple-dialogs>
class SimpleDialog extends StatelessWidget {
  /// Creates a simple dialog.
  ///
  /// Typically used in conjunction with [showDialog].
  SimpleDialog({
    Key key,
    this.title,
    this.titlePadding,
    this.children,
    this.contentPadding,
  }) : super(key: key);

  /// The (optional) title of the dialog is displayed in a large font at the top
  /// of the dialog.
  ///
  /// Typically a [Text] widget.
  final Widget title;

  /// Padding around the title.
  ///
  /// Uses material design default if none is supplied. If there is no title, no
  /// padding will be provided.
  final EdgeInsets titlePadding;

  /// The (optional) content of the dialog is displayed in a [Block] underneath
  /// the title.
  ///
  /// The children are assumed to have 8.0 pixels of vertical and 24.0 pixels of
  /// horizontal padding internally.
  final List<Widget> children;

  /// Padding around the content.
  ///
  /// Uses material design default if none is supplied.
  final EdgeInsets contentPadding;

  @override
  Widget build(BuildContext context) {
    final List<Widget> body = <Widget>[];

    if (title != null) {
      body.add(new Padding(
        padding: titlePadding ?? const EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 0.0),
        child: new DefaultTextStyle(
          style: Theme.of(context).textTheme.title,
          child: title
        )
      ));
    }

    if (children != null) {
      body.add(new Flexible(
        fit: FlexFit.loose,
        child: new Block(
          padding: contentPadding ?? const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0),
          children: children
        )
      ));
    }

    return new Dialog(
      child: new IntrinsicWidth(
        stepWidth: 56.0,
266
        child: new ConstrainedBox(
267 268 269 270 271
          constraints: const BoxConstraints(minWidth: 280.0),
          child: new Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: body
272 273 274
          )
        )
      )
275
    );
276 277
  }
}
278

Hixie's avatar
Hixie committed
279 280
class _DialogRoute<T> extends PopupRoute<T> {
  _DialogRoute({
281 282
    this.child,
    this.theme,
283
  });
284

Adam Barth's avatar
Adam Barth committed
285
  final Widget child;
286
  final ThemeData theme;
287

288
  @override
289
  Duration get transitionDuration => const Duration(milliseconds: 150);
290 291

  @override
Hixie's avatar
Hixie committed
292
  bool get barrierDismissable => true;
293 294

  @override
295 296
  Color get barrierColor => Colors.black54;

297
  @override
298
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation) {
299
    return theme != null ? new Theme(data: theme, child: child) : child;
300
  }
301

302
  @override
303
  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation, Widget child) {
304 305 306 307 308
    return new FadeTransition(
      opacity: new CurvedAnimation(
        parent: animation,
        curve: Curves.easeOut
      ),
309 310
      child: child
    );
311 312 313
  }
}

314 315 316 317 318 319 320 321 322
/// Displays a dialog above the current contents of the app.
///
/// This function typically receives a [Dialog] widget as its child argument.
/// Content below the dialog is dimmed with a [ModalBarrier].
///
/// Returns a `Future` that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the dialog was closed.
///
/// See also:
323 324 325
///  * [SimpleDialog], which handles the scrolling of the contents but has no [actions].
///  * [AlertDialog], for dialogs that have a row of buttons below the body.
///  * [Dialog], on which [SimpleDialog] and [AlertDialog] are based.
326
///  * <https://material.google.com/components/dialogs.html>
327
Future<dynamic/*=T*/> showDialog/*<T>*/({ BuildContext context, Widget child }) {
328
  return Navigator.push(context, new _DialogRoute<dynamic/*=T*/>(
329 330 331
    child: child,
    theme: Theme.of(context, shadowThemeOnly: true),
  ));
332
}