dialog.dart 11 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
import 'package:meta/meta.dart';
9

10
import 'button.dart';
11
import 'button_bar.dart';
12
import 'colors.dart';
13
import 'ink_well.dart';
14
import 'material.dart';
15
import 'theme.dart';
16

17
/// A material design dialog.
18
///
19 20 21 22
/// 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.
23 24
///
/// See also:
Ian Hickson's avatar
Ian Hickson committed
25
///
26 27 28
///  * [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.
29
///  * <https://material.google.com/components/dialogs.html>
30
class Dialog extends StatelessWidget {
31 32 33
  /// Creates a dialog.
  ///
  /// Typically used in conjunction with [showDialog].
34
  Dialog({
35 36 37 38
    Key key,
    this.child,
  }) : super(key: key);

39
  /// The widget below this widget in the tree.
40 41 42
  final Widget child;

  Color _getColor(BuildContext context) {
43
    return Theme.of(context).dialogBackgroundColor;
44 45 46 47 48 49
  }

  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new Container(
50
        margin: const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0),
51
        child: new ConstrainedBox(
52
          constraints: const BoxConstraints(minWidth: 280.0),
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
          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
74 75
/// using a scrolling widget, such as [ScrollList], for [content] to avoid
/// overflow.
76 77 78 79 80 81 82 83 84
///
/// 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:
///
85 86 87
///  * [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.
88 89 90 91 92 93
///  * <https://material.google.com/components/dialogs.html#dialogs-alerts>
class AlertDialog extends StatelessWidget {
  /// Creates an alert dialog.
  ///
  /// Typically used in conjunction with [showDialog].
  AlertDialog({
94
    Key key,
95
    this.title,
96
    this.titlePadding,
97
    this.content,
98
    this.contentPadding,
99
    this.actions
100
  }) : super(key: key);
101 102 103

  /// 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
104 105
  ///
  /// Typically a [Text] widget.
106 107
  final Widget title;

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

114 115
  /// 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
116
  ///
117 118 119
  /// Typically, this is a [ScrollList] containing the contents of the dialog.
  /// Using a [ScrollList] ensures that the contents can scroll if they are too
  /// big to fit on the display.
120 121
  final Widget content;

122 123 124
  /// Padding around the content.
  ///
  /// Uses material design default if none is supplied.
125
  final EdgeInsets contentPadding;
126

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

135
  @override
136
  Widget build(BuildContext context) {
137
    final List<Widget> children = new List<Widget>();
138 139

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

    if (content != null) {
150
      children.add(new Flexible(
151 152 153 154
        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,
155 156 157
            child: content,
          ),
        ),
158 159 160
      ));
    }

161
    if (actions != null) {
162
      children.add(new ButtonTheme.bar(
163
        child: new ButtonBar(
164 165
          children: actions,
        ),
166
      ));
167
    }
168

169 170 171 172 173
    return new Dialog(
      child: new IntrinsicWidth(
        child: new Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.stretch,
174 175 176
          children: children,
        ),
      ),
177 178 179 180
    );
  }
}

181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
/// An option used in a [SimpleDialog].
///
/// A simple dialog offers the user a choice between several options. This
/// widget is commonly used to represent each of the options. If the user
/// selects this option, the widget will call the [onPressed] callback, which
/// typically uses [Navigator.pop] to close the dialog.
///
/// See also:
///
///  * [SimpleDialog], for a dialog in which to use this widget.
///  * [showDialog], which actually displays the dialog and returns its result.
///  * [FlatButton], which are commonly used as actions in other kinds of
///    dialogs, such as [AlertDialog]s.
///  * <https://material.google.com/components/dialogs.html#dialogs-simple-dialogs>
class SimpleDialogOption extends StatelessWidget {
  /// Creates an option for a [SimpleDialog].
  SimpleDialogOption({
    Key key,
    this.onPressed,
    this.child,
  }) : super(key: key);

  /// The callback that is called when this option is selected.
  ///
  /// If this is set to null, the option cannot be selected.
  final VoidCallback onPressed;

  /// The widget below this widget in the tree.
  ///
  /// Typically a [Text] widget.
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return new InkWell(
      onTap: onPressed,
      child: new Padding(
        padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 24.0),
        child: child
      ),
    );
  }
}

225 226 227 228 229 230 231 232 233 234 235 236 237
/// 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:
///
238
///  * [SimpleDialogOption], which are options used in this type of dialog.
239 240 241
///  * [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.
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
///  * <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;

267 268
  /// The (optional) content of the dialog is displayed in a
  /// [SingleChildScrollView] underneath the title.
269
  ///
270
  /// Typically a list of [SimpleDialogOption]s.
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
  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(
294
        child: new SingleChildScrollView(
295
          padding: contentPadding ?? const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0),
296
          child: new BlockBody(children: children),
297 298 299 300 301 302 303
        )
      ));
    }

    return new Dialog(
      child: new IntrinsicWidth(
        stepWidth: 56.0,
304
        child: new ConstrainedBox(
305 306 307 308
          constraints: const BoxConstraints(minWidth: 280.0),
          child: new Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.stretch,
309
            children: body,
310 311 312
          )
        )
      )
313
    );
314 315
  }
}
316

Hixie's avatar
Hixie committed
317 318
class _DialogRoute<T> extends PopupRoute<T> {
  _DialogRoute({
319 320
    this.child,
    this.theme,
321
  });
322

Adam Barth's avatar
Adam Barth committed
323
  final Widget child;
324
  final ThemeData theme;
325

326
  @override
327
  Duration get transitionDuration => const Duration(milliseconds: 150);
328 329

  @override
Hixie's avatar
Hixie committed
330
  bool get barrierDismissable => true;
331 332

  @override
333 334
  Color get barrierColor => Colors.black54;

335
  @override
336
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation) {
337
    return theme != null ? new Theme(data: theme, child: child) : child;
338
  }
339

340
  @override
341
  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation, Widget child) {
342 343 344 345 346
    return new FadeTransition(
      opacity: new CurvedAnimation(
        parent: animation,
        curve: Curves.easeOut
      ),
347 348
      child: child
    );
349 350 351
  }
}

352 353 354 355 356 357 358 359 360
/// 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:
361 362 363
///  * [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.
364
///  * <https://material.google.com/components/dialogs.html>
365
Future<T> showDialog<T>({
366 367 368
  @required BuildContext context,
  @required Widget child
}) {
369
  return Navigator.push(context, new _DialogRoute<T>(
370 371 372
    child: child,
    theme: Theme.of(context, shadowThemeOnly: true),
  ));
373
}