Commit 68425979 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add CupertinoAlertDialog (#7395)

Fixes #7375
parent cd34593c
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
library cupertino; library cupertino;
export 'src/cupertino/activity_indicator.dart'; export 'src/cupertino/activity_indicator.dart';
export 'src/cupertino/dialog.dart';
export 'src/cupertino/slider.dart'; export 'src/cupertino/slider.dart';
export 'src/cupertino/switch.dart'; export 'src/cupertino/switch.dart';
export 'src/cupertino/thumb_painter.dart'; export 'src/cupertino/thumb_painter.dart';
// Copyright 2017 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 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
// TODO(abarth): These constants probably belong somewhere more general.
const TextStyle _kCupertinoDialogTitleStyle = const TextStyle(
fontFamily: '.SF UI Display',
inherit: false,
fontSize: 16.0,
fontWeight: FontWeight.bold,
color: const Color(0xFF000000),
height: 1.35,
textBaseline: TextBaseline.alphabetic,
);
const TextStyle _kCupertinoDialogContentStyle = const TextStyle(
fontFamily: '.SF UI Text',
inherit: false,
fontSize: 12.0,
fontWeight: FontWeight.w400,
color: const Color(0xFF000000),
height: 1.35,
textBaseline: TextBaseline.alphabetic,
);
const TextStyle _kCupertinoDialogActionStyle = const TextStyle(
fontFamily: '.SF UI Text',
inherit: false,
fontSize: 16.0,
fontWeight: FontWeight.w400,
color: const Color(0xFF027AFF),
textBaseline: TextBaseline.alphabetic,
);
const double _kCupertinoDialogWidth = 270.0;
const BoxDecoration _kCupertinoDialogDecoration = const BoxDecoration(
// TODO(abarth): Rather than being opaque, this decoration should actually be
// partially transparent and have a subtle background blur effect.
backgroundColor: const Color(0xFFF8F8F8),
borderRadius: const BorderRadius.all(const Radius.circular(15.0)),
);
/// An iOS-style dialog.
///
/// This dialog widget does not have any opinion about the contents of the
/// dialog. Rather than using this widget directly, consider using
/// [CupertinoAlertDialog], which implement a specific kind of dialog.
///
/// See also:
///
/// * [CupertinoAlertDialog], which is a dialog with title, contents, and
/// actions.
/// * <https://developer.apple.com/ios/human-interface-guidelines/ui-views/alerts/>
class CupertinoDialog extends StatelessWidget {
/// Creates an iOS-style dialog.
CupertinoDialog({
Key key,
this.child,
}) : super(key: key);
/// The widget below this widget in the tree.
final Widget child;
@override
Widget build(BuildContext context) {
return new Center(
child: new Container(
margin: const EdgeInsets.all(10.0),
width: _kCupertinoDialogWidth,
decoration: _kCupertinoDialogDecoration,
child: child,
),
);
}
}
/// An iOS-style 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.
///
/// Typically passed as the child widget to [showDialog], which displays the
/// dialog.
///
/// See also:
///
/// * [CupertinoDialog], which is a generic iOS-style dialog.
/// * <https://developer.apple.com/ios/human-interface-guidelines/ui-views/alerts/>
class CupertinoAlertDialog extends StatelessWidget {
/// Creates an iOS-style alert dialog.
CupertinoAlertDialog({
Key key,
this.title,
this.content,
this.actions,
}) : 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;
/// The (optional) content of the dialog is displayed in the center of the
/// dialog in a lighter font.
///
/// Typically a [Text] widget.
final Widget content;
/// The (optional) set of actions that are displayed at the bottom of the
/// dialog.
///
/// Typically this is a list of [CupertinoDialogAction] widgets.
final List<Widget> actions;
@override
Widget build(BuildContext context) {
final List<Widget> children = <Widget>[];
children.add(new SizedBox(height: 20.0));
if (title != null) {
children.add(new Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: new DefaultTextStyle(
style: _kCupertinoDialogTitleStyle,
textAlign: TextAlign.center,
child: title,
),
));
}
if (content != null) {
children.add(new Flexible(
fit: FlexFit.loose,
child: new Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: new DefaultTextStyle(
style: _kCupertinoDialogContentStyle,
textAlign: TextAlign.center,
child: content,
),
),
));
}
children.add(new SizedBox(height: 20.0));
if (actions != null) {
children.add(new _CupertinoButtonBar(
children: actions,
));
}
return new CupertinoDialog(
child: new Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children,
),
);
}
}
const Color _kDestructiveActionColor = const Color(0xFFFF3B30);
/// A button typically used in a [CupertinoAlertDialog].
///
/// See also:
///
/// * [CupertinoAlertDialog]
class CupertinoDialogAction extends StatelessWidget {
/// Creates an action for an iOS-style dialog.
CupertinoDialogAction({
this.onPressed,
this.isDestructive: false,
@required this.child,
}) {
assert(child != null);
}
/// The callback that is called when the button is tapped or otherwise activated.
///
/// If this is set to null, the button will be disabled.
final VoidCallback onPressed;
/// Whether this action destroys an object.
///
/// For example, an action that deletes an email is destructive.
final bool isDestructive;
/// The widget below this widget in the tree.
///
/// Typically a [Text] widget.
final Widget child;
/// Whether the button is enabled or disabled. Buttons are disabled by default. To
/// enable a button, set its [onPressed] property to a non-null value.
bool get enabled => onPressed != null;
@override
Widget build(BuildContext context) {
TextStyle style = _kCupertinoDialogActionStyle;
if (isDestructive)
style = style.copyWith(color: _kDestructiveActionColor);
if (!enabled)
style = style.copyWith(color: style.color.withOpacity(0.5));
return new GestureDetector(
onTap: onPressed,
child: new Center(
child: new DefaultTextStyle(
style: style,
child: child,
),
),
);
}
}
const double _kButtonBarHeight = 45.0;
// This color isn't correct. Instead, we should carve a hole in the dialog and
// show more of the background.
const Color _kButtonDividerColor = const Color(0xFFD5D5D5);
class _CupertinoButtonBar extends StatelessWidget {
_CupertinoButtonBar({
Key key,
this.children,
}) : super(key: key);
final List<Widget> children;
@override
Widget build(BuildContext context) {
final List<Widget> buttons = <Widget>[];
for (Widget child in children) {
// TODO(abarth): Listen for the buttons being highlighted.
buttons.add(new Expanded(child: child));
}
return new CustomPaint(
painter: new _CupertinoButtonBarPainter(children.length),
child: new SizedBox(
height: _kButtonBarHeight,
child: new Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: buttons
),
)
);
}
}
class _CupertinoButtonBarPainter extends CustomPainter {
_CupertinoButtonBarPainter(this.count);
final int count;
@override
void paint(Canvas canvas, Size size) {
final Paint paint = new Paint()
..color = _kButtonDividerColor;
canvas.drawLine(Point.origin, new Point(size.width, 0.0), paint);
for (int i = 1; i < count; ++i) {
// TODO(abarth): Hide the divider when one of the adjacent buttons is
// highlighted.
final double x = size.width * i / count;
canvas.drawLine(new Point(x, 0.0), new Point(x, size.height), paint);
}
}
@override
bool shouldRepaint(_CupertinoButtonBarPainter other) => count != other.count;
}
...@@ -34,6 +34,7 @@ class Dialog extends StatelessWidget { ...@@ -34,6 +34,7 @@ class Dialog extends StatelessWidget {
this.child, this.child,
}) : super(key: key); }) : super(key: key);
/// The widget below this widget in the tree.
final Widget child; final Widget child;
Color _getColor(BuildContext context) { Color _getColor(BuildContext context) {
...@@ -137,8 +138,8 @@ class AlertDialog extends StatelessWidget { ...@@ -137,8 +138,8 @@ class AlertDialog extends StatelessWidget {
padding: titlePadding ?? new EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0), padding: titlePadding ?? new EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0),
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: Theme.of(context).textTheme.title, style: Theme.of(context).textTheme.title,
child: title child: title,
) ),
)); ));
} }
...@@ -149,17 +150,17 @@ class AlertDialog extends StatelessWidget { ...@@ -149,17 +150,17 @@ class AlertDialog extends StatelessWidget {
padding: contentPadding ?? const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0), padding: contentPadding ?? const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: Theme.of(context).textTheme.subhead, style: Theme.of(context).textTheme.subhead,
child: content child: content,
) ),
) ),
)); ));
} }
if (actions != null) { if (actions != null) {
children.add(new ButtonTheme.bar( children.add(new ButtonTheme.bar(
child: new ButtonBar( child: new ButtonBar(
children: actions children: actions,
) ),
)); ));
} }
...@@ -168,9 +169,9 @@ class AlertDialog extends StatelessWidget { ...@@ -168,9 +169,9 @@ class AlertDialog extends StatelessWidget {
child: new Column( child: new Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: children children: children,
) ),
) ),
); );
} }
} }
......
...@@ -78,30 +78,30 @@ class TextTheme { ...@@ -78,30 +78,30 @@ class TextTheme {
button = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic); button = const TextStyle(fontFamily: 'Roboto', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic);
const TextTheme._blackCupertino() const TextTheme._blackCupertino()
: display4 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.black54, textBaseline: TextBaseline.alphabetic), : display4 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
display3 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 56.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), display3 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 56.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
display2 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 45.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), display2 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 45.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
display1 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 34.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), display1 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 34.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
headline = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 24.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic), headline = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 24.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
title = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 20.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic), title = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 20.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
subhead = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 16.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic), subhead = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 16.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
body2 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic), body2 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
body1 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic), body1 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w400, color: Colors.black87, textBaseline: TextBaseline.alphabetic),
caption = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 12.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), caption = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 12.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
button = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic); button = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.black87, textBaseline: TextBaseline.alphabetic);
const TextTheme._whiteCupertino() const TextTheme._whiteCupertino()
: display4 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.white70, textBaseline: TextBaseline.alphabetic), : display4 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
display3 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 56.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), display3 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 56.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
display2 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 45.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), display2 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 45.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
display1 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 34.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), display1 = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 34.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
headline = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 24.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic), headline = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 24.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic),
title = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 20.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic), title = const TextStyle(fontFamily: '.SF UI Display', inherit: false, fontSize: 20.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic),
subhead = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 16.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic), subhead = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 16.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic),
body2 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic), body2 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic),
body1 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic), body1 = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w400, color: Colors.white, textBaseline: TextBaseline.alphabetic),
caption = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 12.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic), caption = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 12.0, fontWeight: FontWeight.w400, color: Colors.white70, textBaseline: TextBaseline.alphabetic),
button = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic); button = const TextStyle(fontFamily: '.SF UI Text', inherit: false, fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.white, textBaseline: TextBaseline.alphabetic);
/// Extremely large text. /// Extremely large text.
/// ///
......
// Copyright 2017 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 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Alert dialog control test', (WidgetTester tester) async {
bool didDelete = false;
await tester.pumpWidget(new MaterialApp(
home: new Material(
child: new Center(
child: new Builder(
builder: (BuildContext context) {
return new RaisedButton(
onPressed: () {
showDialog(
context: context,
child: new CupertinoAlertDialog(
title: new Text('The title'),
content: new Text('The content'),
actions: <Widget>[
new CupertinoDialogAction(
child: new Text('Cancel'),
),
new CupertinoDialogAction(
isDestructive: true,
onPressed: () {
didDelete = true;
Navigator.pop(context);
},
child: new Text('Delete'),
),
],
),
);
},
child: new Text('Go'),
);
},
),
),
),
));
await tester.tap(find.text('Go'));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(didDelete, isFalse);
await tester.tap(find.text('Delete'));
expect(didDelete, isTrue);
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(find.text('Delete'), findsNothing);
});
testWidgets('Dialog action styles', (WidgetTester tester) async {
await tester.pumpWidget(new CupertinoDialogAction(
isDestructive: true,
child: new Text('Ok'),
));
DefaultTextStyle widget = tester.widget(find.byType(DefaultTextStyle));
expect(widget.style.color.red, greaterThan(widget.style.color.blue));
expect(widget.style.color.alpha, lessThan(255));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment