From dc4a1638eeb7f1cf401b2bbc4684f689824a9c92 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Tue, 27 Sep 2016 15:44:11 -0700 Subject: [PATCH] Specialize AlertDialog and SimpleDialog (#6098) We were trying to cram too much functionality in to the Dialog widget. Now we have AlertDialog and SimpleDialog to cover to two different kinds of dialogs in the spec. --- .../flutter_gallery/lib/demo/dialog_demo.dart | 51 ++--- .../lib/demo/full_screen_dialog_demo.dart | 2 +- .../demo/persistent_bottom_sheet_demo.dart | 2 +- .../flutter_gallery/lib/gallery/updates.dart | 2 +- examples/stocks/lib/stock_home.dart | 2 +- examples/stocks/lib/stock_settings.dart | 2 +- packages/flutter/lib/src/material/about.dart | 2 +- .../lib/src/material/date_picker_dialog.dart | 3 +- packages/flutter/lib/src/material/dialog.dart | 211 +++++++++++++++--- .../lib/src/material/time_picker_dialog.dart | 3 +- .../flutter/test/material/dialog_test.dart | 2 +- 11 files changed, 209 insertions(+), 73 deletions(-) diff --git a/examples/flutter_gallery/lib/demo/dialog_demo.dart b/examples/flutter_gallery/lib/demo/dialog_demo.dart index 8a8c2c03917..75f1c50b208 100644 --- a/examples/flutter_gallery/lib/demo/dialog_demo.dart +++ b/examples/flutter_gallery/lib/demo/dialog_demo.dart @@ -69,10 +69,10 @@ class DialogDemoState extends State { _selectedTime = new TimeOfDay(hour: now.hour, minute: now.minute); } - void showDemoDialog/**/({ BuildContext context, Dialog dialog }) { + void showDemoDialog/**/({ BuildContext context, Widget child }) { showDialog/**/( context: context, - child: dialog + child: child ) .then((dynamic/*=T*/ value) { // The value passed to Navigator.pop() or null. if (value != null) { @@ -101,7 +101,7 @@ class DialogDemoState extends State { onPressed: () { showDemoDialog/**/( context: context, - dialog: new Dialog( + child: new AlertDialog( content: new Text( _alertWithoutTitleText, style: dialogTextStyle @@ -125,7 +125,7 @@ class DialogDemoState extends State { onPressed: () { showDemoDialog/**/( context: context, - dialog: new Dialog( + child: new AlertDialog( title: new Text('Use Google\'s location service?'), content: new Text( _alertWithTitleText, @@ -150,30 +150,27 @@ class DialogDemoState extends State { onPressed: () { showDemoDialog/**/( context: context, - dialog: new Dialog( + child: new SimpleDialog( title: new Text('Set backup account'), - contentPadding: const EdgeInsets.only(top: 12.0, bottom: 16.0), - content: new Column( - children: [ - new DialogDemoItem( - icon: Icons.account_circle, - color: theme.primaryColor, - text: 'username@gmail.com', - onPressed: () { Navigator.pop(context, 'username@gmail.com'); } - ), - new DialogDemoItem( - icon: Icons.account_circle, - color: theme.primaryColor, - text: 'user02@gmail.com', - onPressed: () { Navigator.pop(context, 'user02@gmail.com'); } - ), - new DialogDemoItem( - icon: Icons.add_circle, - text: 'add account', - color: theme.disabledColor - ) - ] - ) + children: [ + new DialogDemoItem( + icon: Icons.account_circle, + color: theme.primaryColor, + text: 'username@gmail.com', + onPressed: () { Navigator.pop(context, 'username@gmail.com'); } + ), + new DialogDemoItem( + icon: Icons.account_circle, + color: theme.primaryColor, + text: 'user02@gmail.com', + onPressed: () { Navigator.pop(context, 'user02@gmail.com'); } + ), + new DialogDemoItem( + icon: Icons.add_circle, + text: 'add account', + color: theme.disabledColor + ) + ] ) ); } diff --git a/examples/flutter_gallery/lib/demo/full_screen_dialog_demo.dart b/examples/flutter_gallery/lib/demo/full_screen_dialog_demo.dart index 53bb2cb6914..dd2146bcac9 100644 --- a/examples/flutter_gallery/lib/demo/full_screen_dialog_demo.dart +++ b/examples/flutter_gallery/lib/demo/full_screen_dialog_demo.dart @@ -115,7 +115,7 @@ class FullScreenDialogDemoState extends State { showDialog( context: context, - child: new Dialog( + child: new AlertDialog( content: new Text( 'Discard new event?', style: dialogTextStyle diff --git a/examples/flutter_gallery/lib/demo/persistent_bottom_sheet_demo.dart b/examples/flutter_gallery/lib/demo/persistent_bottom_sheet_demo.dart index 76c7c4887e0..c6ed2a12440 100644 --- a/examples/flutter_gallery/lib/demo/persistent_bottom_sheet_demo.dart +++ b/examples/flutter_gallery/lib/demo/persistent_bottom_sheet_demo.dart @@ -55,7 +55,7 @@ class _PersistentBottomSheetDemoState extends State { void _showMessage() { showDialog( context: context, - child: new Dialog( + child: new AlertDialog( content: new Text('You tapped the floating action button.'), actions: [ new FlatButton( diff --git a/examples/flutter_gallery/lib/gallery/updates.dart b/examples/flutter_gallery/lib/gallery/updates.dart index 36f245274b6..9f00a166570 100644 --- a/examples/flutter_gallery/lib/gallery/updates.dart +++ b/examples/flutter_gallery/lib/gallery/updates.dart @@ -49,7 +49,7 @@ class UpdaterState extends State { final ThemeData theme = Theme.of(context); final TextStyle dialogTextStyle = theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color); - return new Dialog( + return new AlertDialog( title: new Text('Update Flutter Gallery?'), content: new Text('A newer version is available.', style: dialogTextStyle), actions: [ diff --git a/examples/stocks/lib/stock_home.dart b/examples/stocks/lib/stock_home.dart index 10f6f9df1f1..5f65f3851f0 100644 --- a/examples/stocks/lib/stock_home.dart +++ b/examples/stocks/lib/stock_home.dart @@ -19,7 +19,7 @@ enum StockHomeTab { market, portfolio } class _NotImplementedDialog extends StatelessWidget { @override Widget build(BuildContext context) { - return new Dialog( + return new AlertDialog( title: new Text('Not Implemented'), content: new Text('This feature has not yet been implemented.'), actions: [ diff --git a/examples/stocks/lib/stock_settings.dart b/examples/stocks/lib/stock_settings.dart index 7de0713ec0a..2c05c6104ca 100644 --- a/examples/stocks/lib/stock_settings.dart +++ b/examples/stocks/lib/stock_settings.dart @@ -67,7 +67,7 @@ class StockSettingsState extends State { case StockMode.pessimistic: showDialog( context: context, - child: new Dialog( + child: new AlertDialog( title: new Text("Change mode?"), content: new Text("Optimistic mode means everything is awesome. Are you sure you can handle that?"), actions: [ diff --git a/packages/flutter/lib/src/material/about.dart b/packages/flutter/lib/src/material/about.dart index febe537294e..bf19b3a6ba1 100644 --- a/packages/flutter/lib/src/material/about.dart +++ b/packages/flutter/lib/src/material/about.dart @@ -273,7 +273,7 @@ class AboutDialog extends StatelessWidget { ]; if (children != null) body.addAll(children); - return new Dialog( + return new AlertDialog( content: new Block( children: body ), diff --git a/packages/flutter/lib/src/material/date_picker_dialog.dart b/packages/flutter/lib/src/material/date_picker_dialog.dart index 708dcd31d25..79c34092246 100644 --- a/packages/flutter/lib/src/material/date_picker_dialog.dart +++ b/packages/flutter/lib/src/material/date_picker_dialog.dart @@ -51,7 +51,8 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { @override Widget build(BuildContext context) { - return new Dialog( + // TODO(abarth): Use Dialog directly. + return new AlertDialog( content: new DatePicker( selectedDate: _selectedDate, firstDate: config.firstDate, diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart index 3a27856b600..3769672e75c 100644 --- a/packages/flutter/lib/src/material/dialog.dart +++ b/packages/flutter/lib/src/material/dialog.dart @@ -12,13 +12,17 @@ import 'colors.dart'; import 'material.dart'; import 'theme.dart'; -/// A material design dialog +/// A material design dialog. /// -/// Typically passed as the child widget to [showDialog], which displays the -/// 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] +/// * [SimpleDialog] /// * [showDialog] /// * class Dialog extends StatelessWidget { @@ -26,6 +30,71 @@ class Dialog extends StatelessWidget { /// /// Typically used in conjunction with [showDialog]. Dialog({ + 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( + margin: new EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0), + child: new ConstrainedBox( + constraints: new BoxConstraints(minWidth: 280.0), + 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: +/// +/// * [SimpleDialog] +/// * [Dialog] +/// * [showDialog] +/// * +class AlertDialog extends StatelessWidget { + /// Creates an alert dialog. + /// + /// Typically used in conjunction with [showDialog]. + AlertDialog({ Key key, this.title, this.titlePadding, @@ -67,18 +136,6 @@ class Dialog extends StatelessWidget { /// These widgets will be wrapped in a [ButtonBar]. final List actions; - Color _getColor(BuildContext context) { - Brightness brightness = Theme.of(context).brightness; - switch (brightness) { - case Brightness.light: - return Colors.white; - case Brightness.dark: - return Colors.grey[800]; - } - assert(brightness != null); - return null; - } - @override Widget build(BuildContext context) { final List children = new List(); @@ -96,13 +153,11 @@ class Dialog extends StatelessWidget { if (content != null) { children.add(new Flexible( fit: FlexFit.loose, - child: new ScrollableViewport( - 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 - ) + 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 ) ) )); @@ -117,22 +172,104 @@ class Dialog extends StatelessWidget { )); } - return new Center( - child: new Container( - margin: new EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0), + 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: +/// +/// * [AlertDialog] +/// * [Dialog] +/// * [showDialog] +/// * +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 children; + + /// Padding around the content. + /// + /// Uses material design default if none is supplied. + final EdgeInsets contentPadding; + + @override + Widget build(BuildContext context) { + final List body = []; + + 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, child: new ConstrainedBox( - constraints: new BoxConstraints(minWidth: 280.0), - child: new Material( - elevation: 24, - color: _getColor(context), - type: MaterialType.card, - child: new IntrinsicWidth( - child: new Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: children - ) - ) + constraints: const BoxConstraints(minWidth: 280.0), + child: new Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: body ) ) ) diff --git a/packages/flutter/lib/src/material/time_picker_dialog.dart b/packages/flutter/lib/src/material/time_picker_dialog.dart index 967d4c9d727..14fa541bf4d 100644 --- a/packages/flutter/lib/src/material/time_picker_dialog.dart +++ b/packages/flutter/lib/src/material/time_picker_dialog.dart @@ -47,7 +47,8 @@ class _TimePickerDialogState extends State<_TimePickerDialog> { @override Widget build(BuildContext context) { - return new Dialog( + // TODO(abarth): Use Dialog directly. + return new AlertDialog( content: new TimePicker( selectedTime: _selectedTime, onChanged: _handleTimeChanged diff --git a/packages/flutter/test/material/dialog_test.dart b/packages/flutter/test/material/dialog_test.dart index 3067d2a9f65..8993afab62b 100644 --- a/packages/flutter/test/material/dialog_test.dart +++ b/packages/flutter/test/material/dialog_test.dart @@ -20,7 +20,7 @@ void main() { onPressed: () { showDialog( context: context, - child: new Dialog( + child: new AlertDialog( content: new Container( height: 5000.0, width: 300.0,