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';
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'scrollbar.dart';
// TODO(abarth): These constants probably belong somewhere more general.
const TextStyle _kCupertinoDialogTitleStyle = const TextStyle(
fontFamily: '.SF UI Display',
inherit: false,
fontSize: 17.5,
fontSize: 17.5,
fontWeight: FontWeight.w600,
color: CupertinoColors.black,
height: 1.25,
......@@ -23,7 +24,7 @@ const TextStyle _kCupertinoDialogTitleStyle = const TextStyle(
const TextStyle _kCupertinoDialogContentStyle = const TextStyle(
fontFamily: '.SF UI Text',
inherit: false,
fontSize: 12.4,
fontSize: 12.4,
fontWeight: FontWeight.w500,
color: CupertinoColors.black,
height: 1.35,
......@@ -33,7 +34,7 @@ const TextStyle _kCupertinoDialogContentStyle = const TextStyle(
const TextStyle _kCupertinoDialogActionStyle = const TextStyle(
fontFamily: '.SF UI Text',
inherit: false,
fontSize: 16.8,
fontSize: 16.8,
fontWeight: FontWeight.w400,
color: CupertinoColors.activeBlue,
textBaseline: TextBaseline.alphabetic,
......@@ -78,7 +79,7 @@ class CupertinoDialog extends StatelessWidget {
borderRadius: const BorderRadius.all(const Radius.circular(12.0)),
child: new DecoratedBox(
// 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.
decoration: _kCupertinoDialogBackFill,
child: new BackdropFilter(
......@@ -116,6 +117,7 @@ class CupertinoAlertDialog extends StatelessWidget {
this.title,
this.content,
this.actions,
this.scrollController,
}) : super(key: key);
/// The (optional) title of the dialog is displayed in a large font at the top
......@@ -136,15 +138,24 @@ class CupertinoAlertDialog extends StatelessWidget {
/// Typically this is a list of [CupertinoDialogAction] widgets.
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
Widget build(BuildContext context) {
final List<Widget> children = <Widget>[];
children.add(const SizedBox(height: 18.0));
const double edgePadding = 20.0;
final List<Widget> titleContentGroup = <Widget>[];
if (title != null) {
children.add(new Padding(
padding: const EdgeInsets.only(left: 20.0, right: 20.0, bottom: 2.0),
titleContentGroup.add(new Padding(
padding: new EdgeInsets.only(
left: edgePadding,
right: edgePadding,
bottom: content == null ? edgePadding : 1.0,
top: edgePadding,
),
child: new DefaultTextStyle(
style: _kCupertinoDialogTitleStyle,
textAlign: TextAlign.center,
......@@ -154,20 +165,37 @@ class CupertinoAlertDialog extends StatelessWidget {
}
if (content != null) {
children.add(new Flexible(
fit: FlexFit.loose,
child: new Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
titleContentGroup.add(
new Padding(
padding: new EdgeInsets.only(
left: edgePadding,
right: edgePadding,
bottom: edgePadding,
top: title == null ? edgePadding : 1.0,
),
child: new DefaultTextStyle(
style: _kCupertinoDialogContentStyle,
textAlign: TextAlign.center,
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) {
children.add(new _CupertinoButtonBar(
......@@ -175,17 +203,19 @@ class CupertinoAlertDialog extends StatelessWidget {
));
}
return new CupertinoDialog(
child: new Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children,
return new Padding(
padding: const EdgeInsets.symmetric(vertical: edgePadding),
child: new CupertinoDialog(
child: new Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children,
),
),
);
}
}
/// A button typically used in a [CupertinoAlertDialog].
///
/// See also:
......@@ -208,7 +238,7 @@ class CupertinoDialogAction extends StatelessWidget {
/// Set to true if button is the default choice in the dialog.
///
/// Default buttons are bolded.
/// Default buttons are bold.
final bool isDefaultAction;
/// Whether this action destroys an object.
......@@ -229,22 +259,29 @@ class CupertinoDialogAction extends StatelessWidget {
Widget build(BuildContext context) {
TextStyle style = _kCupertinoDialogActionStyle;
if (isDefaultAction)
if (isDefaultAction) {
style = style.copyWith(fontWeight: FontWeight.w600);
}
if (isDestructiveAction)
if (isDestructiveAction) {
style = style.copyWith(color: CupertinoColors.destructiveRed);
}
if (!enabled)
if (!enabled) {
style = style.copyWith(color: style.color.withOpacity(0.5));
}
final double textScaleFactor = MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0;
return new GestureDetector(
onTap: onPressed,
behavior: HitTestBehavior.opaque,
child: new Center(
child: new Container(
alignment: Alignment.center,
padding: new EdgeInsets.all(10.0 * textScaleFactor),
child: new DefaultTextStyle(
style: style,
child: child,
textAlign: TextAlign.center,
),
),
);
......@@ -271,18 +308,22 @@ class _CupertinoButtonBar extends StatelessWidget {
for (Widget child in children) {
// 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));
}
return new CustomPaint(
painter: new _CupertinoButtonBarPainter(children.length),
child: new SizedBox(
height: _kButtonBarHeight,
child: new Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: buttons
child: new UnconstrainedBox(
constrainedAxis: Axis.horizontal,
child: new ConstrainedBox(
constraints: const BoxConstraints(minHeight: _kButtonBarHeight),
child: new Row(children: buttons),
),
)
),
);
}
}
......
......@@ -434,12 +434,7 @@ class _DialogRoute<T> extends PopupRoute<T> {
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return new MediaQuery.removePadding(
context: context,
removeTop: true,
removeBottom: true,
removeLeft: true,
removeRight: true,
return new SafeArea(
child: new Builder(
builder: (BuildContext context) {
return theme != null ? new Theme(data: theme, child: child) : child;
......
......@@ -98,6 +98,69 @@ void main() {
expect(widget.style.fontWeight, equals(FontWeight.w600));
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) {
......
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