Unverified Commit 12279613 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Make Cupertino alert dialog honor text scale factor (#14346)

This updates the CupertinoAlertDialog to respect text scale factor more properly. Before this, it would scale, but would clip the action buttons at large scales, and would draw in the safe area. It also didn't match the iOS alert because the content didn't scroll. Now it does those properly.

I didn't address the fact that buttons should lay out properly (Issue #14345), but that's probably pretty low priority.

Fixes #12484
parent 4a1eeed6
...@@ -7,13 +7,14 @@ import 'package:flutter/foundation.dart'; ...@@ -7,13 +7,14 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
import 'scrollbar.dart';
// TODO(abarth): These constants probably belong somewhere more general. // TODO(abarth): These constants probably belong somewhere more general.
const TextStyle _kCupertinoDialogTitleStyle = const TextStyle( const TextStyle _kCupertinoDialogTitleStyle = const TextStyle(
fontFamily: '.SF UI Display', fontFamily: '.SF UI Display',
inherit: false, inherit: false,
fontSize: 17.5, fontSize: 17.5,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: CupertinoColors.black, color: CupertinoColors.black,
height: 1.25, height: 1.25,
...@@ -23,7 +24,7 @@ const TextStyle _kCupertinoDialogTitleStyle = const TextStyle( ...@@ -23,7 +24,7 @@ const TextStyle _kCupertinoDialogTitleStyle = const TextStyle(
const TextStyle _kCupertinoDialogContentStyle = const TextStyle( const TextStyle _kCupertinoDialogContentStyle = const TextStyle(
fontFamily: '.SF UI Text', fontFamily: '.SF UI Text',
inherit: false, inherit: false,
fontSize: 12.4, fontSize: 12.4,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: CupertinoColors.black, color: CupertinoColors.black,
height: 1.35, height: 1.35,
...@@ -33,7 +34,7 @@ const TextStyle _kCupertinoDialogContentStyle = const TextStyle( ...@@ -33,7 +34,7 @@ const TextStyle _kCupertinoDialogContentStyle = const TextStyle(
const TextStyle _kCupertinoDialogActionStyle = const TextStyle( const TextStyle _kCupertinoDialogActionStyle = const TextStyle(
fontFamily: '.SF UI Text', fontFamily: '.SF UI Text',
inherit: false, inherit: false,
fontSize: 16.8, fontSize: 16.8,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: CupertinoColors.activeBlue, color: CupertinoColors.activeBlue,
textBaseline: TextBaseline.alphabetic, textBaseline: TextBaseline.alphabetic,
...@@ -78,7 +79,7 @@ class CupertinoDialog extends StatelessWidget { ...@@ -78,7 +79,7 @@ class CupertinoDialog extends StatelessWidget {
borderRadius: const BorderRadius.all(const Radius.circular(12.0)), borderRadius: const BorderRadius.all(const Radius.circular(12.0)),
child: new DecoratedBox( child: new DecoratedBox(
// To get the effect, 2 white fills are needed. One blended with the // To get the effect, 2 white fills are needed. One blended with the
// background before applying the blur and one overlayed on top of // background before applying the blur and one overlaid on top of
// the blur. // the blur.
decoration: _kCupertinoDialogBackFill, decoration: _kCupertinoDialogBackFill,
child: new BackdropFilter( child: new BackdropFilter(
...@@ -116,6 +117,7 @@ class CupertinoAlertDialog extends StatelessWidget { ...@@ -116,6 +117,7 @@ class CupertinoAlertDialog extends StatelessWidget {
this.title, this.title,
this.content, this.content,
this.actions, this.actions,
this.scrollController,
}) : super(key: key); }) : super(key: key);
/// The (optional) title of the dialog is displayed in a large font at the top /// The (optional) title of the dialog is displayed in a large font at the top
...@@ -136,15 +138,24 @@ class CupertinoAlertDialog extends StatelessWidget { ...@@ -136,15 +138,24 @@ class CupertinoAlertDialog extends StatelessWidget {
/// Typically this is a list of [CupertinoDialogAction] widgets. /// Typically this is a list of [CupertinoDialogAction] widgets.
final List<Widget> actions; final List<Widget> actions;
/// A scroll controller that can be used to control the scrolling of the message
/// in the dialog.
///
/// Defaults to null, and is typically not needed, since most alert messages are short.
final ScrollController scrollController;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<Widget> children = <Widget>[]; const double edgePadding = 20.0;
final List<Widget> titleContentGroup = <Widget>[];
children.add(const SizedBox(height: 18.0));
if (title != null) { if (title != null) {
children.add(new Padding( titleContentGroup.add(new Padding(
padding: const EdgeInsets.only(left: 20.0, right: 20.0, bottom: 2.0), padding: new EdgeInsets.only(
left: edgePadding,
right: edgePadding,
bottom: content == null ? edgePadding : 1.0,
top: edgePadding,
),
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: _kCupertinoDialogTitleStyle, style: _kCupertinoDialogTitleStyle,
textAlign: TextAlign.center, textAlign: TextAlign.center,
...@@ -154,20 +165,37 @@ class CupertinoAlertDialog extends StatelessWidget { ...@@ -154,20 +165,37 @@ class CupertinoAlertDialog extends StatelessWidget {
} }
if (content != null) { if (content != null) {
children.add(new Flexible( titleContentGroup.add(
fit: FlexFit.loose, new Padding(
child: new Padding( padding: new EdgeInsets.only(
padding: const EdgeInsets.symmetric(horizontal: 20.0), left: edgePadding,
right: edgePadding,
bottom: edgePadding,
top: title == null ? edgePadding : 1.0,
),
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: _kCupertinoDialogContentStyle, style: _kCupertinoDialogContentStyle,
textAlign: TextAlign.center, textAlign: TextAlign.center,
child: content, child: content,
), ),
), ),
)); );
} }
children.add(const SizedBox(height: 22.0)); final List<Widget> children = <Widget>[];
if (titleContentGroup.isNotEmpty) {
children.add(
new Flexible(
child: new CupertinoScrollbar(
child: new ListView(
controller: scrollController,
shrinkWrap: true,
children: titleContentGroup,
),
),
),
);
}
if (actions != null) { if (actions != null) {
children.add(new _CupertinoButtonBar( children.add(new _CupertinoButtonBar(
...@@ -175,17 +203,19 @@ class CupertinoAlertDialog extends StatelessWidget { ...@@ -175,17 +203,19 @@ class CupertinoAlertDialog extends StatelessWidget {
)); ));
} }
return new CupertinoDialog( return new Padding(
child: new Column( padding: const EdgeInsets.symmetric(vertical: edgePadding),
mainAxisSize: MainAxisSize.min, child: new CupertinoDialog(
crossAxisAlignment: CrossAxisAlignment.stretch, child: new Column(
children: children, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children,
),
), ),
); );
} }
} }
/// A button typically used in a [CupertinoAlertDialog]. /// A button typically used in a [CupertinoAlertDialog].
/// ///
/// See also: /// See also:
...@@ -208,7 +238,7 @@ class CupertinoDialogAction extends StatelessWidget { ...@@ -208,7 +238,7 @@ class CupertinoDialogAction extends StatelessWidget {
/// Set to true if button is the default choice in the dialog. /// Set to true if button is the default choice in the dialog.
/// ///
/// Default buttons are bolded. /// Default buttons are bold.
final bool isDefaultAction; final bool isDefaultAction;
/// Whether this action destroys an object. /// Whether this action destroys an object.
...@@ -229,22 +259,29 @@ class CupertinoDialogAction extends StatelessWidget { ...@@ -229,22 +259,29 @@ class CupertinoDialogAction extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
TextStyle style = _kCupertinoDialogActionStyle; TextStyle style = _kCupertinoDialogActionStyle;
if (isDefaultAction) if (isDefaultAction) {
style = style.copyWith(fontWeight: FontWeight.w600); style = style.copyWith(fontWeight: FontWeight.w600);
}
if (isDestructiveAction) if (isDestructiveAction) {
style = style.copyWith(color: CupertinoColors.destructiveRed); style = style.copyWith(color: CupertinoColors.destructiveRed);
}
if (!enabled) if (!enabled) {
style = style.copyWith(color: style.color.withOpacity(0.5)); style = style.copyWith(color: style.color.withOpacity(0.5));
}
final double textScaleFactor = MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0;
return new GestureDetector( return new GestureDetector(
onTap: onPressed, onTap: onPressed,
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: new Center( child: new Container(
alignment: Alignment.center,
padding: new EdgeInsets.all(10.0 * textScaleFactor),
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: style, style: style,
child: child, child: child,
textAlign: TextAlign.center,
), ),
), ),
); );
...@@ -271,18 +308,22 @@ class _CupertinoButtonBar extends StatelessWidget { ...@@ -271,18 +308,22 @@ class _CupertinoButtonBar extends StatelessWidget {
for (Widget child in children) { for (Widget child in children) {
// TODO(abarth): Listen for the buttons being highlighted. // TODO(abarth): Listen for the buttons being highlighted.
// TODO(gspencer): These buttons don't lay out in the same way as iOS.
// When they get wide, they should stack vertically instead of in a row.
// When they get really big, the vertically-stacked buttons should scroll
// (separately from the top message).
buttons.add(new Expanded(child: child)); buttons.add(new Expanded(child: child));
} }
return new CustomPaint( return new CustomPaint(
painter: new _CupertinoButtonBarPainter(children.length), painter: new _CupertinoButtonBarPainter(children.length),
child: new SizedBox( child: new UnconstrainedBox(
height: _kButtonBarHeight, constrainedAxis: Axis.horizontal,
child: new Row( child: new ConstrainedBox(
crossAxisAlignment: CrossAxisAlignment.stretch, constraints: const BoxConstraints(minHeight: _kButtonBarHeight),
children: buttons child: new Row(children: buttons),
), ),
) ),
); );
} }
} }
......
...@@ -434,12 +434,7 @@ class _DialogRoute<T> extends PopupRoute<T> { ...@@ -434,12 +434,7 @@ class _DialogRoute<T> extends PopupRoute<T> {
@override @override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return new MediaQuery.removePadding( return new SafeArea(
context: context,
removeTop: true,
removeBottom: true,
removeLeft: true,
removeRight: true,
child: new Builder( child: new Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
return theme != null ? new Theme(data: theme, child: child) : child; return theme != null ? new Theme(data: theme, child: child) : child;
......
...@@ -98,6 +98,69 @@ void main() { ...@@ -98,6 +98,69 @@ void main() {
expect(widget.style.fontWeight, equals(FontWeight.w600)); expect(widget.style.fontWeight, equals(FontWeight.w600));
expect(widget.style.color.red, greaterThan(widget.style.color.blue)); expect(widget.style.color.red, greaterThan(widget.style.color.blue));
}); });
testWidgets('Message is scrollable, has correct padding with large text sizes',
(WidgetTester tester) async {
final ScrollController scrollController = new ScrollController(keepScrollOffset: true);
await tester.pumpWidget(
new MaterialApp(home: new Material(
child: new Center(
child: new Builder(builder: (BuildContext context) {
return new RaisedButton(
onPressed: () {
showDialog<Null>(
context: context,
child: new Builder(builder: (BuildContext context) {
return new MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 3.0),
child: new CupertinoAlertDialog(
title: const Text('The Title'),
content: new Text('Very long content ' * 20),
actions: <Widget>[
const CupertinoDialogAction(
child: const Text('Cancel'),
),
const CupertinoDialogAction(
isDestructiveAction: true,
child: const Text('OK'),
),
],
scrollController: scrollController,
),
);
}),
);
},
child: const Text('Go'),
);
}),
),
)),
);
await tester.tap(find.text('Go'));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(scrollController.offset, 0.0);
scrollController.jumpTo(100.0);
expect(scrollController.offset, 100.0);
// Find the actual dialog box. The first decorated box is the popup barrier.
expect(tester.getSize(find.byType(DecoratedBox).at(1)), equals(const Size(270.0, 560.0)));
// Check sizes/locations of the text.
expect(tester.getSize(find.text('The Title')), equals(const Size(230.0, 198.0)));
expect(tester.getSize(find.text('Cancel')), equals(const Size(75.0, 300.0)));
expect(tester.getSize(find.text('OK')), equals(const Size(75.0, 100.0)));
expect(tester.getTopLeft(find.text('The Title')), equals(const Offset(285.0, 40.0)));
// The Cancel and OK buttons have different Y values because "Cancel" is
// wrapping (as it should with large text sizes like this).
expect(tester.getTopLeft(find.text('Cancel')), equals(const Offset(295.0, 250.0)));
expect(tester.getTopLeft(find.text('OK')), equals(const Offset(430.0, 350.0)));
});
} }
Widget boilerplate(Widget child) { Widget boilerplate(Widget child) {
......
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