Skip to content

Commit 672859a

Browse files
authored
feat: add icon to AlertDialog (#104920)
1 parent e649210 commit 672859a

File tree

5 files changed

+398
-23
lines changed

5 files changed

+398
-23
lines changed

dev/tools/gen_defaults/lib/dialog_template.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ class _TokenDefaultsM3 extends DialogTheme {
2525
late final ColorScheme _colors = Theme.of(context).colorScheme;
2626
late final TextTheme _textTheme = Theme.of(context).textTheme;
2727
28+
@override
29+
Color? get iconColor => _colors.secondary;
30+
2831
// TODO(darrenaustin): overlay should be handled by Material widget: https://github.com/flutter/flutter/issues/9160
2932
@override
3033
Color? get backgroundColor => ElevationOverlay.colorWithOverlay(${componentColor("md.comp.dialog.container")}, _colors.primary, ${elevation("md.comp.dialog.container")});

packages/flutter/lib/src/material/dialog.dart

Lines changed: 101 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -257,16 +257,19 @@ class AlertDialog extends StatelessWidget {
257257
///
258258
/// Typically used in conjunction with [showDialog].
259259
///
260-
/// The [contentPadding] must not be null. The [titlePadding] defaults to
261-
/// null, which implies a default that depends on the values of the other
262-
/// properties. See the documentation of [titlePadding] for details.
260+
/// The [titlePadding] and [contentPadding] default to null, which implies a
261+
/// default that depends on the values of the other properties. See the
262+
/// documentation of [titlePadding] and [contentPadding] for details.
263263
const AlertDialog({
264264
super.key,
265+
this.icon,
266+
this.iconPadding,
267+
this.iconColor,
265268
this.title,
266269
this.titlePadding,
267270
this.titleTextStyle,
268271
this.content,
269-
this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),
272+
this.contentPadding,
270273
this.contentTextStyle,
271274
this.actions,
272275
this.actionsPadding,
@@ -283,11 +286,35 @@ class AlertDialog extends StatelessWidget {
283286
this.shape,
284287
this.alignment,
285288
this.scrollable = false,
286-
}) : assert(contentPadding != null),
287-
assert(clipBehavior != null);
289+
}) : assert(clipBehavior != null);
290+
291+
/// An optional icon to display at the top of the dialog.
292+
///
293+
/// Typically, an [Icon] widget. Providing an icon centers the [title]'s text.
294+
final Widget? icon;
295+
296+
/// Color for the [Icon] in the [icon] of this [AlertDialog].
297+
///
298+
/// If null, [DialogTheme.iconColor] is used. If that is null, defaults to
299+
/// color scheme's [ColorScheme.secondary] if [ThemeData.useMaterial3] is
300+
/// true, black otherwise.
301+
final Color? iconColor;
302+
303+
/// Padding around the [icon].
304+
///
305+
/// If there is no [icon], no padding will be provided. Otherwise, this
306+
/// padding is used.
307+
///
308+
/// This property defaults to providing 24 pixels on the top, left, and right
309+
/// of the [icon]. If [title] is _not_ null, 16 pixels of bottom padding is
310+
/// added to separate the [icon] from the [title]. If the [title] is null and
311+
/// [content] is _not_ null, then no bottom padding is provided (but see
312+
/// [contentPadding]). In any other case 24 pixels of bottom padding is
313+
/// added.
314+
final EdgeInsetsGeometry? iconPadding;
288315

289316
/// The (optional) title of the dialog is displayed in a large font at the top
290-
/// of the dialog.
317+
/// of the dialog, below the (optional) [icon].
291318
///
292319
/// Typically a [Text] widget.
293320
final Widget? title;
@@ -321,11 +348,17 @@ class AlertDialog extends StatelessWidget {
321348

322349
/// Padding around the content.
323350
///
324-
/// If there is no content, no padding will be provided. Otherwise, padding of
325-
/// 20 pixels is provided above the content to separate the content from the
326-
/// title, and padding of 24 pixels is provided on the left, right, and bottom
327-
/// to separate the content from the other edges of the dialog.
328-
final EdgeInsetsGeometry contentPadding;
351+
/// If there is no [content], no padding will be provided. Otherwise, this
352+
/// padding is used.
353+
///
354+
/// This property defaults to providing a padding of 20 pixels above the
355+
/// [content] to separate the [content] from the [title], and 24 pixels on the
356+
/// left, right, and bottom to separate the [content] from the other edges of
357+
/// the dialog.
358+
///
359+
/// If [ThemeData.useMaterial3] is true, the top padding separating the
360+
/// content from the title defaults to 16 pixels instead of 20 pixels.
361+
final EdgeInsetsGeometry? contentPadding;
329362

330363
/// Style for the text in the [content] of this [AlertDialog].
331364
///
@@ -508,21 +541,55 @@ class AlertDialog extends StatelessWidget {
508541
final double paddingScaleFactor = _paddingScaleFactor(MediaQuery.of(context).textScaleFactor);
509542
final TextDirection? textDirection = Directionality.maybeOf(context);
510543

544+
Widget? iconWidget;
511545
Widget? titleWidget;
512546
Widget? contentWidget;
513547
Widget? actionsWidget;
548+
549+
if (icon != null) {
550+
final bool belowIsTitle = title != null;
551+
final bool belowIsContent = !belowIsTitle && content != null;
552+
final EdgeInsets defaultIconPadding = EdgeInsets.only(
553+
left: 24.0,
554+
top: 24.0,
555+
right: 24.0,
556+
bottom: belowIsTitle ? 16.0 : belowIsContent ? 0.0 : 24.0,
557+
);
558+
final EdgeInsets effectiveIconPadding = iconPadding?.resolve(textDirection) ?? defaultIconPadding;
559+
iconWidget = Padding(
560+
padding: EdgeInsets.only(
561+
left: effectiveIconPadding.left * paddingScaleFactor,
562+
right: effectiveIconPadding.right * paddingScaleFactor,
563+
top: effectiveIconPadding.top * paddingScaleFactor,
564+
bottom: effectiveIconPadding.bottom,
565+
),
566+
child: IconTheme(
567+
data: IconThemeData(
568+
color: iconColor ?? dialogTheme.iconColor ?? defaults.iconColor,
569+
),
570+
child: icon!,
571+
),
572+
);
573+
}
574+
514575
if (title != null) {
515-
final EdgeInsets defaultTitlePadding = EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0);
576+
final EdgeInsets defaultTitlePadding = EdgeInsets.only(
577+
left: 24.0,
578+
top: icon == null ? 24.0 : 0.0,
579+
right: 24.0,
580+
bottom: content == null ? 20.0 : 0.0,
581+
);
516582
final EdgeInsets effectiveTitlePadding = titlePadding?.resolve(textDirection) ?? defaultTitlePadding;
517583
titleWidget = Padding(
518584
padding: EdgeInsets.only(
519585
left: effectiveTitlePadding.left * paddingScaleFactor,
520586
right: effectiveTitlePadding.right * paddingScaleFactor,
521-
top: effectiveTitlePadding.top * paddingScaleFactor,
587+
top: icon == null ? effectiveTitlePadding.top * paddingScaleFactor : effectiveTitlePadding.top,
522588
bottom: effectiveTitlePadding.bottom,
523589
),
524590
child: DefaultTextStyle(
525591
style: titleTextStyle ?? dialogTheme.titleTextStyle ?? defaults.titleTextStyle!,
592+
textAlign: icon == null ? TextAlign.start : TextAlign.center,
526593
child: Semantics(
527594
// For iOS platform, the focus always lands on the title.
528595
// Set nameRoute to false to avoid title being announce twice.
@@ -535,12 +602,20 @@ class AlertDialog extends StatelessWidget {
535602
}
536603

537604
if (content != null) {
538-
final EdgeInsets effectiveContentPadding = contentPadding.resolve(textDirection);
605+
final EdgeInsets defaultContentPadding = EdgeInsets.only(
606+
left: 24.0,
607+
top: theme.useMaterial3 ? 16.0 : 20.0,
608+
right: 24.0,
609+
bottom: 24.0,
610+
);
611+
final EdgeInsets effectiveContentPadding = contentPadding?.resolve(textDirection) ?? defaultContentPadding;
539612
contentWidget = Padding(
540613
padding: EdgeInsets.only(
541614
left: effectiveContentPadding.left * paddingScaleFactor,
542615
right: effectiveContentPadding.right * paddingScaleFactor,
543-
top: title == null ? effectiveContentPadding.top * paddingScaleFactor : effectiveContentPadding.top,
616+
top: title == null && icon == null
617+
? effectiveContentPadding.top * paddingScaleFactor
618+
: effectiveContentPadding.top,
544619
bottom: effectiveContentPadding.bottom,
545620
),
546621
child: DefaultTextStyle(
@@ -580,6 +655,7 @@ class AlertDialog extends StatelessWidget {
580655
mainAxisSize: MainAxisSize.min,
581656
crossAxisAlignment: CrossAxisAlignment.stretch,
582657
children: <Widget>[
658+
if (icon != null) iconWidget!,
583659
if (title != null) titleWidget!,
584660
if (content != null) contentWidget!,
585661
],
@@ -591,6 +667,7 @@ class AlertDialog extends StatelessWidget {
591667
];
592668
} else {
593669
columnChildren = <Widget>[
670+
if (icon != null) iconWidget!,
594671
if (title != null) titleWidget!,
595672
if (content != null) Flexible(child: contentWidget!),
596673
if (actions != null) actionsWidget!,
@@ -1187,6 +1264,7 @@ double _paddingScaleFactor(double textScaleFactor) {
11871264
class _DefaultsM2 extends DialogTheme {
11881265
_DefaultsM2(this.context)
11891266
: _textTheme = Theme.of(context).textTheme,
1267+
_iconTheme = Theme.of(context).iconTheme,
11901268
super(
11911269
alignment: Alignment.center,
11921270
elevation: 24.0,
@@ -1195,6 +1273,10 @@ class _DefaultsM2 extends DialogTheme {
11951273

11961274
final BuildContext context;
11971275
final TextTheme _textTheme;
1276+
final IconThemeData _iconTheme;
1277+
1278+
@override
1279+
Color? get iconColor => _iconTheme.color;
11981280

11991281
@override
12001282
Color? get backgroundColor => Theme.of(context).dialogBackgroundColor;
@@ -1228,6 +1310,9 @@ class _TokenDefaultsM3 extends DialogTheme {
12281310
late final ColorScheme _colors = Theme.of(context).colorScheme;
12291311
late final TextTheme _textTheme = Theme.of(context).textTheme;
12301312

1313+
@override
1314+
Color? get iconColor => _colors.secondary;
1315+
12311316
// TODO(darrenaustin): overlay should be handled by Material widget: https://github.com/flutter/flutter/issues/9160
12321317
@override
12331318
Color? get backgroundColor => ElevationOverlay.colorWithOverlay(_colors.surface, _colors.primary, 6.0);

packages/flutter/lib/src/material/dialog_theme.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class DialogTheme with Diagnosticable {
3232
this.elevation,
3333
this.shape,
3434
this.alignment,
35+
this.iconColor,
3536
this.titleTextStyle,
3637
this.contentTextStyle,
3738
this.actionsPadding,
@@ -60,13 +61,17 @@ class DialogTheme with Diagnosticable {
6061
/// Overrides the default value for [AlertDialog.actionsPadding].
6162
final EdgeInsetsGeometry? actionsPadding;
6263

64+
/// Used to configure the [IconTheme] for the [AlertDialog.icon] widget.
65+
final Color? iconColor;
66+
6367
/// Creates a copy of this object but with the given fields replaced with the
6468
/// new values.
6569
DialogTheme copyWith({
6670
Color? backgroundColor,
6771
double? elevation,
6872
ShapeBorder? shape,
6973
AlignmentGeometry? alignment,
74+
Color? iconColor,
7075
TextStyle? titleTextStyle,
7176
TextStyle? contentTextStyle,
7277
EdgeInsetsGeometry? actionsPadding,
@@ -76,6 +81,7 @@ class DialogTheme with Diagnosticable {
7681
elevation: elevation ?? this.elevation,
7782
shape: shape ?? this.shape,
7883
alignment: alignment ?? this.alignment,
84+
iconColor: iconColor ?? this.iconColor,
7985
titleTextStyle: titleTextStyle ?? this.titleTextStyle,
8086
contentTextStyle: contentTextStyle ?? this.contentTextStyle,
8187
actionsPadding: actionsPadding ?? this.actionsPadding,
@@ -99,6 +105,7 @@ class DialogTheme with Diagnosticable {
99105
elevation: lerpDouble(a?.elevation, b?.elevation, t),
100106
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
101107
alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t),
108+
iconColor: Color.lerp(a?.iconColor, b?.iconColor, t),
102109
titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t),
103110
contentTextStyle: TextStyle.lerp(a?.contentTextStyle, b?.contentTextStyle, t),
104111
actionsPadding: EdgeInsetsGeometry.lerp(a?.actionsPadding, b?.actionsPadding, t),
@@ -121,6 +128,7 @@ class DialogTheme with Diagnosticable {
121128
&& other.elevation == elevation
122129
&& other.shape == shape
123130
&& other.alignment == alignment
131+
&& other.iconColor == iconColor
124132
&& other.titleTextStyle == titleTextStyle
125133
&& other.contentTextStyle == contentTextStyle
126134
&& other.actionsPadding == actionsPadding;
@@ -133,6 +141,7 @@ class DialogTheme with Diagnosticable {
133141
properties.add(DoubleProperty('elevation', elevation));
134142
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
135143
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
144+
properties.add(ColorProperty('iconColor', iconColor));
136145
properties.add(DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null));
137146
properties.add(DiagnosticsProperty<TextStyle>('contentTextStyle', contentTextStyle, defaultValue: null));
138147
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('actionsPadding', actionsPadding, defaultValue: null));

0 commit comments

Comments
 (0)