|
- // 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 'dart:async';
-
- import 'package:flutter/foundation.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/widgets.dart';
-
- // Examples can assume:
- // enum Department { treasury, state }
- // BuildContext context;
-
- /// A material design dialog.
- ///
- /// 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.
- ///
- /// See also:
- ///
- /// * [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.
- /// * <https://material.io/design/components/dialogs.html>
- class Dialog extends StatelessWidget {
- /// Creates a dialog.
- ///
- /// Typically used in conjunction with [showDialog].
- const Dialog({
- Key key,
- this.backgroundColor,
- this.elevation,
- this.insetAnimationDuration = const Duration(milliseconds: 100),
- this.insetAnimationCurve = Curves.decelerate,
- this.shape,
- this.child,
- }) : super(key: key);
-
- /// {@template flutter.material.dialog.backgroundColor}
- /// The background color of the surface of this [Dialog].
- ///
- /// This sets the [Material.color] on this [Dialog]'s [Material].
- ///
- /// If `null`, [ThemeData.cardColor] is used.
- /// {@endtemplate}
- final Color backgroundColor;
-
- /// {@template flutter.material.dialog.elevation}
- /// The z-coordinate of this [Dialog].
- ///
- /// If null then [DialogTheme.elevation] is used, and if that's null then the
- /// dialog's elevation is 24.0.
- /// {@endtemplate}
- /// {@macro flutter.material.material.elevation}
- final double elevation;
-
- /// The duration of the animation to show when the system keyboard intrudes
- /// into the space that the dialog is placed in.
- ///
- /// Defaults to 100 milliseconds.
- final Duration insetAnimationDuration;
-
- /// The curve to use for the animation shown when the system keyboard intrudes
- /// into the space that the dialog is placed in.
- ///
- /// Defaults to [Curves.fastOutSlowIn].
- final Curve insetAnimationCurve;
-
- /// {@template flutter.material.dialog.shape}
- /// The shape of this dialog's border.
- ///
- /// Defines the dialog's [Material.shape].
- ///
- /// The default shape is a [RoundedRectangleBorder] with a radius of 2.0.
- /// {@endtemplate}
- final ShapeBorder shape;
-
- /// The widget below this widget in the tree.
- ///
- /// {@macro flutter.widgets.child}
- final Widget child;
-
- static const RoundedRectangleBorder _defaultDialogShape =
- RoundedRectangleBorder(
- borderRadius: BorderRadius.all(Radius.circular(2.0)));
- static const double _defaultElevation = 24.0;
-
- @override
- Widget build(BuildContext context) {
- final DialogTheme dialogTheme = DialogTheme.of(context);
- return AnimatedPadding(
- padding: MediaQuery.of(context).viewInsets +
- const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0),
- duration: insetAnimationDuration,
- curve: insetAnimationCurve,
- child: MediaQuery.removeViewInsets(
- removeLeft: true,
- removeTop: true,
- removeRight: true,
- removeBottom: true,
- context: context,
- child: Center(
- child: ConstrainedBox(
- constraints: const BoxConstraints(minWidth: 280.0),
- child: Material(
- color: backgroundColor ??
- dialogTheme.backgroundColor ??
- Theme.of(context).dialogBackgroundColor,
- elevation:
- elevation ?? dialogTheme.elevation ?? _defaultElevation,
- shape: shape ?? dialogTheme.shape ?? _defaultDialogShape,
- 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, which is
- /// rarely desired. Consider using a scrolling widget for [content], such as
- /// [SingleChildScrollView], to avoid overflow. (However, be aware that since
- /// [AlertDialog] tries to size itself using the intrinsic dimensions of its
- /// children, widgets such as [ListView], [GridView], and [CustomScrollView],
- /// which use lazy viewports, will not work. If this is a problem, consider
- /// using [Dialog] directly.)
- ///
- /// 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.
- ///
- /// {@tool sample}
- ///
- /// This snippet shows a method in a [State] which, when called, displays a dialog box
- /// and returns a [Future] that completes when the dialog is dismissed.
- ///
- /// ```dart
- /// Future<void> _neverSatisfied() async {
- /// return showDialog<void>(
- /// context: context,
- /// barrierDismissible: false, // user must tap button!
- /// builder: (BuildContext context) {
- /// return AlertDialog(
- /// title: Text('Rewind and remember'),
- /// content: SingleChildScrollView(
- /// child: ListBody(
- /// children: <Widget>[
- /// Text('You will never be satisfied.'),
- /// Text('You\’re like me. I’m never satisfied.'),
- /// ],
- /// ),
- /// ),
- /// actions: <Widget>[
- /// FlatButton(
- /// child: Text('Regret'),
- /// onPressed: () {
- /// Navigator.of(context).pop();
- /// },
- /// ),
- /// ],
- /// );
- /// },
- /// );
- /// }
- /// ```
- /// {@end-tool}
- ///
- /// See also:
- ///
- /// * [SimpleDialog], which handles the scrolling of the contents but has no [actions].
- /// * [Dialog], on which [AlertDialog] and [SimpleDialog] are based.
- /// * [CupertinoAlertDialog], an iOS-styled alert dialog.
- /// * [showDialog], which actually displays the dialog and returns its result.
- /// * <https://material.io/design/components/dialogs.html#alert-dialog>
- class AlertDialog extends StatelessWidget {
- /// Creates an alert dialog.
- ///
- /// Typically used in conjunction with [showDialog].
- ///
- /// The [contentPadding] must not be null. The [titlePadding] defaults to
- /// null, which implies a default that depends on the values of the other
- /// properties. See the documentation of [titlePadding] for details.
- const AlertDialog({
- Key key,
- this.title,
- this.titlePadding,
- this.titleTextStyle,
- this.content,
- this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),
- this.contentTextStyle,
- this.actions,
- this.backgroundColor,
- this.elevation,
- this.semanticLabel,
- this.shape,
- }) : assert(contentPadding != null),
- 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.
- ///
- /// If there is no title, no padding will be provided. Otherwise, this padding
- /// is used.
- ///
- /// This property defaults to providing 24 pixels on the top, left, and right
- /// of the title. If the [content] is not null, then no bottom padding is
- /// provided (but see [contentPadding]). If it _is_ null, then an extra 20
- /// pixels of bottom padding is added to separate the [title] from the
- /// [actions].
- final EdgeInsetsGeometry titlePadding;
-
- /// Style for the text in the [title] of this [AlertDialog].
- ///
- /// If null, [DialogTheme.titleTextStyle] is used, if that's null, defaults to
- /// [ThemeData.textTheme.title].
- final TextStyle titleTextStyle;
-
- /// The (optional) content of the dialog is displayed in the center of the
- /// dialog in a lighter font.
- ///
- /// Typically this is a [SingleChildScrollView] that contains the dialog's
- /// message. As noted in the [AlertDialog] documentation, it's important
- /// to use a [SingleChildScrollView] if there's any risk that the content
- /// will not fit.
- final Widget content;
-
- /// Padding around the content.
- ///
- /// If there is no content, no padding will be provided. Otherwise, padding of
- /// 20 pixels is provided above the content to separate the content from the
- /// title, and padding of 24 pixels is provided on the left, right, and bottom
- /// to separate the content from the other edges of the dialog.
- final EdgeInsetsGeometry contentPadding;
-
- /// Style for the text in the [content] of this [AlertDialog].
- ///
- /// If null, [DialogTheme.contentTextStyle] is used, if that's null, defaults
- /// to [ThemeData.textTheme.subhead].
- final TextStyle contentTextStyle;
-
- /// The (optional) set of actions that are displayed at the bottom of the
- /// dialog.
- ///
- /// Typically this is a list of [FlatButton] widgets.
- ///
- /// These widgets will be wrapped in a [ButtonBar], which introduces 8 pixels
- /// of padding on each side.
- ///
- /// If the [title] is not null but the [content] _is_ null, then an extra 20
- /// pixels of padding is added above the [ButtonBar] to separate the [title]
- /// from the [actions].
- final List<Widget> actions;
-
- /// {@macro flutter.material.dialog.backgroundColor}
- final Color backgroundColor;
-
- /// {@macro flutter.material.dialog.elevation}
- /// {@macro flutter.material.material.elevation}
- final double elevation;
-
- /// The semantic label of the dialog used by accessibility frameworks to
- /// announce screen transitions when the dialog is opened and closed.
- ///
- /// If this label is not provided, a semantic label will be inferred from the
- /// [title] if it is not null. If there is no title, the label will be taken
- /// from [MaterialLocalizations.alertDialogLabel].
- ///
- /// See also:
- ///
- /// * [SemanticsConfiguration.isRouteName], for a description of how this
- /// value is used.
- final String semanticLabel;
-
- /// {@macro flutter.material.dialog.shape}
- final ShapeBorder shape;
-
- @override
- Widget build(BuildContext context) {
- assert(debugCheckHasMaterialLocalizations(context));
- final ThemeData theme = Theme.of(context);
- final DialogTheme dialogTheme = DialogTheme.of(context);
- final List<Widget> children = <Widget>[];
- String label = semanticLabel;
-
- if (title != null) {
- children.add(Padding(
- padding: titlePadding ??
- EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0),
- child: DefaultTextStyle(
- style: titleTextStyle ??
- dialogTheme.titleTextStyle ??
- theme.textTheme.title,
- child: Semantics(
- child: title,
- namesRoute: true,
- container: true,
- ),
- ),
- ));
- } else {
- switch (defaultTargetPlatform) {
- case TargetPlatform.iOS:
- label = semanticLabel;
- break;
- case TargetPlatform.android:
- case TargetPlatform.fuchsia:
- label = semanticLabel ??
- MaterialLocalizations.of(context)?.alertDialogLabel;
- }
- }
-
- if (content != null) {
- children.add(Flexible(
- child: Padding(
- padding: contentPadding,
- child: DefaultTextStyle(
- style: contentTextStyle ??
- dialogTheme.contentTextStyle ??
- theme.textTheme.subhead,
- child: content,
- ),
- ),
- ));
- }
-
- if (actions != null) {
- // children.add(ButtonTheme.bar(
- // child: ButtonBar(
- // children: actions,
- // ),
- // ));
-
- children.add(ButtonBarTheme(
- data: ButtonBarTheme.of(context),
- child: ButtonBar(
- children: actions,
- ),
- ));
- }
-
- Widget dialogChild = IntrinsicWidth(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: children,
- ),
- );
-
- if (label != null)
- dialogChild = Semantics(
- namesRoute: true,
- label: label,
- child: dialogChild,
- );
-
- return Dialog(
- backgroundColor: backgroundColor,
- elevation: elevation,
- shape: shape,
- child: dialogChild,
- );
- }
- }
-
- /// 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.
- ///
- /// The padding on a [SimpleDialogOption] is configured to combine with the
- /// default [SimpleDialog.contentPadding] so that each option ends up 8 pixels
- /// from the other vertically, with 20 pixels of spacing between the dialog's
- /// title and the first option, and 24 pixels of spacing between the last option
- /// and the bottom of the dialog.
- ///
- /// {@tool sample}
- ///
- /// ```dart
- /// SimpleDialogOption(
- /// onPressed: () { Navigator.pop(context, Department.treasury); },
- /// child: const Text('Treasury department'),
- /// )
- /// ```
- /// {@end-tool}
- ///
- /// 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.io/design/components/dialogs.html#simple-dialog>
- class SimpleDialogOption extends StatelessWidget {
- /// Creates an option for a [SimpleDialog].
- const 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.
- ///
- /// When used in a [SimpleDialog], this will typically call [Navigator.pop]
- /// with a value for [showDialog] to complete its future with.
- final VoidCallback onPressed;
-
- /// The widget below this widget in the tree.
- ///
- /// Typically a [Text] widget.
- final Widget child;
-
- @override
- Widget build(BuildContext context) {
- return InkWell(
- onTap: onPressed,
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 24.0),
- child: child,
- ),
- );
- }
- }
-
- /// 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.
- ///
- /// Choices are normally represented using [SimpleDialogOption] widgets. If
- /// other widgets are used, see [contentPadding] for notes regarding the
- /// conventions for obtaining the spacing expected by Material Design.
- ///
- /// 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.
- ///
- /// {@tool sample}
- ///
- /// In this example, the user is asked to select between two options. These
- /// options are represented as an enum. The [showDialog] method here returns
- /// a [Future] that completes to a value of that enum. If the user cancels
- /// the dialog (e.g. by hitting the back button on Android, or tapping on the
- /// mask behind the dialog) then the future completes with the null value.
- ///
- /// The return value in this example is used as the index for a switch statement.
- /// One advantage of using an enum as the return value and then using that to
- /// drive a switch statement is that the analyzer will flag any switch statement
- /// that doesn't mention every value in the enum.
- ///
- /// ```dart
- /// Future<void> _askedToLead() async {
- /// switch (await showDialog<Department>(
- /// context: context,
- /// builder: (BuildContext context) {
- /// return SimpleDialog(
- /// title: const Text('Select assignment'),
- /// children: <Widget>[
- /// SimpleDialogOption(
- /// onPressed: () { Navigator.pop(context, Department.treasury); },
- /// child: const Text('Treasury department'),
- /// ),
- /// SimpleDialogOption(
- /// onPressed: () { Navigator.pop(context, Department.state); },
- /// child: const Text('State department'),
- /// ),
- /// ],
- /// );
- /// }
- /// )) {
- /// case Department.treasury:
- /// // Let's go.
- /// // ...
- /// break;
- /// case Department.state:
- /// // ...
- /// break;
- /// }
- /// }
- /// ```
- /// {@end-tool}
- ///
- /// See also:
- ///
- /// * [SimpleDialogOption], which are options used in this type of dialog.
- /// * [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.
- /// * <https://material.io/design/components/dialogs.html#simple-dialog>
- class SimpleDialog extends StatelessWidget {
- /// Creates a simple dialog.
- ///
- /// Typically used in conjunction with [showDialog].
- ///
- /// The [titlePadding] and [contentPadding] arguments must not be null.
- const SimpleDialog({
- Key key,
- this.title,
- this.titlePadding = const EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 0.0),
- this.children,
- this.contentPadding = const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0),
- this.backgroundColor,
- this.elevation,
- this.semanticLabel,
- this.shape,
- }) : assert(titlePadding != null),
- assert(contentPadding != null),
- 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.
- ///
- /// If there is no title, no padding will be provided.
- ///
- /// By default, this provides the recommend Material Design padding of 24
- /// pixels around the left, top, and right edges of the title.
- ///
- /// See [contentPadding] for the conventions regarding padding between the
- /// [title] and the [children].
- final EdgeInsetsGeometry titlePadding;
-
- /// The (optional) content of the dialog is displayed in a
- /// [SingleChildScrollView] underneath the title.
- ///
- /// Typically a list of [SimpleDialogOption]s.
- final List<Widget> children;
-
- /// Padding around the content.
- ///
- /// By default, this is 12 pixels on the top and 16 pixels on the bottom. This
- /// is intended to be combined with children that have 24 pixels of padding on
- /// the left and right, and 8 pixels of padding on the top and bottom, so that
- /// the content ends up being indented 20 pixels from the title, 24 pixels
- /// from the bottom, and 24 pixels from the sides.
- ///
- /// The [SimpleDialogOption] widget uses such padding.
- ///
- /// If there is no [title], the [contentPadding] should be adjusted so that
- /// the top padding ends up being 24 pixels.
- final EdgeInsetsGeometry contentPadding;
-
- /// {@macro flutter.material.dialog.backgroundColor}
- final Color backgroundColor;
-
- /// {@macro flutter.material.dialog.elevation}
- /// {@macro flutter.material.material.elevation}
- final double elevation;
-
- /// The semantic label of the dialog used by accessibility frameworks to
- /// announce screen transitions when the dialog is opened and closed.
- ///
- /// If this label is not provided, a semantic label will be inferred from the
- /// [title] if it is not null. If there is no title, the label will be taken
- /// from [MaterialLocalizations.dialogLabel].
- ///
- /// See also:
- ///
- /// * [SemanticsConfiguration.isRouteName], for a description of how this
- /// value is used.
- final String semanticLabel;
-
- /// {@macro flutter.material.dialog.shape}
- final ShapeBorder shape;
-
- @override
- Widget build(BuildContext context) {
- assert(debugCheckHasMaterialLocalizations(context));
- final List<Widget> body = <Widget>[];
- String label = semanticLabel;
-
- if (title != null) {
- body.add(Padding(
- padding: titlePadding,
- child: DefaultTextStyle(
- style: Theme.of(context).textTheme.title,
- child: Semantics(namesRoute: true, child: title),
- ),
- ));
- } else {
- switch (defaultTargetPlatform) {
- case TargetPlatform.iOS:
- label = semanticLabel;
- break;
- case TargetPlatform.android:
- case TargetPlatform.fuchsia:
- label =
- semanticLabel ?? MaterialLocalizations.of(context)?.dialogLabel;
- }
- }
-
- if (children != null) {
- body.add(Flexible(
- child: SingleChildScrollView(
- padding: contentPadding,
- child: ListBody(children: children),
- ),
- ));
- }
-
- Widget dialogChild = IntrinsicWidth(
- stepWidth: 56.0,
- child: ConstrainedBox(
- constraints: const BoxConstraints(minWidth: 280.0),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: body,
- ),
- ),
- );
-
- if (label != null)
- dialogChild = Semantics(
- namesRoute: true,
- label: label,
- child: dialogChild,
- );
- return Dialog(
- backgroundColor: backgroundColor,
- elevation: elevation,
- shape: shape,
- child: dialogChild,
- );
- }
- }
-
- Widget _buildMaterialDialogTransitions(
- BuildContext context,
- Animation<double> animation,
- Animation<double> secondaryAnimation,
- Widget child) {
- return FadeTransition(
- opacity: CurvedAnimation(
- parent: animation,
- curve: Curves.easeOut,
- ),
- child: child,
- );
- }
-
- /// Displays a Material dialog above the current contents of the app, with
- /// Material entrance and exit animations, modal barrier color, and modal
- /// barrier behavior (dialog is dismissible with a tap on the barrier).
- ///
- /// This function takes a `builder` which typically builds a [Dialog] widget.
- /// Content below the dialog is dimmed with a [ModalBarrier]. The widget
- /// returned by the `builder` does not share a context with the location that
- /// `showDialog` is originally called from. Use a [StatefulBuilder] or a
- /// custom [StatefulWidget] if the dialog needs to update dynamically.
- ///
- /// The `context` argument is used to look up the [Navigator] and [Theme] for
- /// the dialog. It is only used when the method is called. Its corresponding
- /// widget can be safely removed from the tree before the dialog is closed.
- ///
- /// The `child` argument is deprecated, and should be replaced with `builder`.
- ///
- /// Returns a [Future] that resolves to the value (if any) that was passed to
- /// [Navigator.pop] when the dialog was closed.
- ///
- /// The dialog route created by this method is pushed to the root navigator.
- /// If the application has multiple [Navigator] objects, it may be necessary to
- /// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
- /// dialog rather than just `Navigator.pop(context, result)`.
- ///
- /// See also:
- ///
- /// * [AlertDialog], for dialogs that have a row of buttons below a body.
- /// * [SimpleDialog], which handles the scrolling of the contents and does
- /// not show buttons below its body.
- /// * [Dialog], on which [SimpleDialog] and [AlertDialog] are based.
- /// * [showCupertinoDialog], which displays an iOS-style dialog.
- /// * [showGeneralDialog], which allows for customization of the dialog popup.
- /// * <https://material.io/design/components/dialogs.html>
- Future<T> showDialog<T>({
- @required
- BuildContext context,
- bool barrierDismissible = true,
- @Deprecated(
- 'Instead of using the "child" argument, return the child from a closure '
- 'provided to the "builder" argument. This will ensure that the BuildContext '
- 'is appropriate for widgets built in the dialog.')
- Widget child,
- WidgetBuilder builder,
- }) {
- assert(child == null || builder == null);
- assert(debugCheckHasMaterialLocalizations(context));
-
- final ThemeData theme = Theme.of(context, shadowThemeOnly: true);
- return showGeneralDialog(
- context: context,
- pageBuilder: (BuildContext buildContext, Animation<double> animation,
- Animation<double> secondaryAnimation) {
- final Widget pageChild = child ?? Builder(builder: builder);
- return WillPopScope(onWillPop: () {
- return new Future.value(false);
- }, child: SafeArea(
- child: Builder(builder: (BuildContext context) {
- return theme != null
- ? Theme(data: theme, child: pageChild)
- : pageChild;
- }),
- ));
- },
- barrierDismissible: barrierDismissible,
- barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
- transitionDuration: const Duration(milliseconds: 150),
- transitionBuilder: _buildMaterialDialogTransitions,
- );
- }
|