@@ -257,16 +257,19 @@ class AlertDialog extends StatelessWidget {
257
257
///
258
258
/// Typically used in conjunction with [showDialog] .
259
259
///
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.
263
263
const AlertDialog ({
264
264
super .key,
265
+ this .icon,
266
+ this .iconPadding,
267
+ this .iconColor,
265
268
this .title,
266
269
this .titlePadding,
267
270
this .titleTextStyle,
268
271
this .content,
269
- this .contentPadding = const EdgeInsets . fromLTRB ( 24.0 , 20.0 , 24.0 , 24.0 ) ,
272
+ this .contentPadding,
270
273
this .contentTextStyle,
271
274
this .actions,
272
275
this .actionsPadding,
@@ -283,11 +286,35 @@ class AlertDialog extends StatelessWidget {
283
286
this .shape,
284
287
this .alignment,
285
288
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;
288
315
289
316
/// 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] .
291
318
///
292
319
/// Typically a [Text] widget.
293
320
final Widget ? title;
@@ -321,11 +348,17 @@ class AlertDialog extends StatelessWidget {
321
348
322
349
/// Padding around the content.
323
350
///
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;
329
362
330
363
/// Style for the text in the [content] of this [AlertDialog] .
331
364
///
@@ -508,21 +541,55 @@ class AlertDialog extends StatelessWidget {
508
541
final double paddingScaleFactor = _paddingScaleFactor (MediaQuery .of (context).textScaleFactor);
509
542
final TextDirection ? textDirection = Directionality .maybeOf (context);
510
543
544
+ Widget ? iconWidget;
511
545
Widget ? titleWidget;
512
546
Widget ? contentWidget;
513
547
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
+
514
575
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
+ );
516
582
final EdgeInsets effectiveTitlePadding = titlePadding? .resolve (textDirection) ?? defaultTitlePadding;
517
583
titleWidget = Padding (
518
584
padding: EdgeInsets .only (
519
585
left: effectiveTitlePadding.left * paddingScaleFactor,
520
586
right: effectiveTitlePadding.right * paddingScaleFactor,
521
- top: effectiveTitlePadding.top * paddingScaleFactor,
587
+ top: icon == null ? effectiveTitlePadding.top * paddingScaleFactor : effectiveTitlePadding.top ,
522
588
bottom: effectiveTitlePadding.bottom,
523
589
),
524
590
child: DefaultTextStyle (
525
591
style: titleTextStyle ?? dialogTheme.titleTextStyle ?? defaults.titleTextStyle! ,
592
+ textAlign: icon == null ? TextAlign .start : TextAlign .center,
526
593
child: Semantics (
527
594
// For iOS platform, the focus always lands on the title.
528
595
// Set nameRoute to false to avoid title being announce twice.
@@ -535,12 +602,20 @@ class AlertDialog extends StatelessWidget {
535
602
}
536
603
537
604
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;
539
612
contentWidget = Padding (
540
613
padding: EdgeInsets .only (
541
614
left: effectiveContentPadding.left * paddingScaleFactor,
542
615
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,
544
619
bottom: effectiveContentPadding.bottom,
545
620
),
546
621
child: DefaultTextStyle (
@@ -580,6 +655,7 @@ class AlertDialog extends StatelessWidget {
580
655
mainAxisSize: MainAxisSize .min,
581
656
crossAxisAlignment: CrossAxisAlignment .stretch,
582
657
children: < Widget > [
658
+ if (icon != null ) iconWidget! ,
583
659
if (title != null ) titleWidget! ,
584
660
if (content != null ) contentWidget! ,
585
661
],
@@ -591,6 +667,7 @@ class AlertDialog extends StatelessWidget {
591
667
];
592
668
} else {
593
669
columnChildren = < Widget > [
670
+ if (icon != null ) iconWidget! ,
594
671
if (title != null ) titleWidget! ,
595
672
if (content != null ) Flexible (child: contentWidget! ),
596
673
if (actions != null ) actionsWidget! ,
@@ -1187,6 +1264,7 @@ double _paddingScaleFactor(double textScaleFactor) {
1187
1264
class _DefaultsM2 extends DialogTheme {
1188
1265
_DefaultsM2 (this .context)
1189
1266
: _textTheme = Theme .of (context).textTheme,
1267
+ _iconTheme = Theme .of (context).iconTheme,
1190
1268
super (
1191
1269
alignment: Alignment .center,
1192
1270
elevation: 24.0 ,
@@ -1195,6 +1273,10 @@ class _DefaultsM2 extends DialogTheme {
1195
1273
1196
1274
final BuildContext context;
1197
1275
final TextTheme _textTheme;
1276
+ final IconThemeData _iconTheme;
1277
+
1278
+ @override
1279
+ Color ? get iconColor => _iconTheme.color;
1198
1280
1199
1281
@override
1200
1282
Color ? get backgroundColor => Theme .of (context).dialogBackgroundColor;
@@ -1228,6 +1310,9 @@ class _TokenDefaultsM3 extends DialogTheme {
1228
1310
late final ColorScheme _colors = Theme .of (context).colorScheme;
1229
1311
late final TextTheme _textTheme = Theme .of (context).textTheme;
1230
1312
1313
+ @override
1314
+ Color ? get iconColor => _colors.secondary;
1315
+
1231
1316
// TODO(darrenaustin): overlay should be handled by Material widget: https://github.com/flutter/flutter/issues/9160
1232
1317
@override
1233
1318
Color ? get backgroundColor => ElevationOverlay .colorWithOverlay (_colors.surface, _colors.primary, 6.0 );
0 commit comments