feat: add icon to AlertDialog (#104920)

diff --git a/dev/tools/gen_defaults/lib/dialog_template.dart b/dev/tools/gen_defaults/lib/dialog_template.dart
index edeb132..798741b 100644
--- a/dev/tools/gen_defaults/lib/dialog_template.dart
+++ b/dev/tools/gen_defaults/lib/dialog_template.dart
@@ -25,6 +25,9 @@
   late final ColorScheme _colors = Theme.of(context).colorScheme;
   late final TextTheme _textTheme = Theme.of(context).textTheme;
 
+  @override
+  Color? get iconColor => _colors.secondary;
+
   // TODO(darrenaustin): overlay should be handled by Material widget: https://github.com/flutter/flutter/issues/9160
   @override
   Color? get backgroundColor => ElevationOverlay.colorWithOverlay(${componentColor("md.comp.dialog.container")}, _colors.primary, ${elevation("md.comp.dialog.container")});
diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart
index 459234b..0f440ad 100644
--- a/packages/flutter/lib/src/material/dialog.dart
+++ b/packages/flutter/lib/src/material/dialog.dart
@@ -257,16 +257,19 @@
   ///
   /// Typically used in conjunction with [showDialog].
   ///
-  /// The [contentPadding] must not be null. The [titlePadding] defaults to
-  /// null, which implies a default that depends on the values of the other
-  /// properties. See the documentation of [titlePadding] for details.
+  /// The [titlePadding] and [contentPadding] default to null, which implies a
+  /// default that depends on the values of the other properties. See the
+  /// documentation of [titlePadding] and [contentPadding] for details.
   const AlertDialog({
     super.key,
+    this.icon,
+    this.iconPadding,
+    this.iconColor,
     this.title,
     this.titlePadding,
     this.titleTextStyle,
     this.content,
-    this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),
+    this.contentPadding,
     this.contentTextStyle,
     this.actions,
     this.actionsPadding,
@@ -283,11 +286,35 @@
     this.shape,
     this.alignment,
     this.scrollable = false,
-  }) : assert(contentPadding != null),
-       assert(clipBehavior != null);
+  }) : assert(clipBehavior != null);
+
+  /// An optional icon to display at the top of the dialog.
+  ///
+  /// Typically, an [Icon] widget. Providing an icon centers the [title]'s text.
+  final Widget? icon;
+
+  /// Color for the [Icon] in the [icon] of this [AlertDialog].
+  ///
+  /// If null, [DialogTheme.iconColor] is used. If that is null, defaults to
+  /// color scheme's [ColorScheme.secondary] if [ThemeData.useMaterial3] is
+  /// true, black otherwise.
+  final Color? iconColor;
+
+  /// Padding around the [icon].
+  ///
+  /// If there is no [icon], no padding will be provided. Otherwise, this
+  /// padding is used.
+  ///
+  /// This property defaults to providing 24 pixels on the top, left, and right
+  /// of the [icon]. If [title] is _not_ null, 16 pixels of bottom padding is
+  /// added to separate the [icon] from the [title]. If the [title] is null and
+  /// [content] is _not_ null, then no bottom padding is provided (but see
+  /// [contentPadding]). In any other case 24 pixels of bottom padding is
+  /// added.
+  final EdgeInsetsGeometry? iconPadding;
 
   /// The (optional) title of the dialog is displayed in a large font at the top
-  /// of the dialog.
+  /// of the dialog, below the (optional) [icon].
   ///
   /// Typically a [Text] widget.
   final Widget? title;
@@ -321,11 +348,17 @@
 
   /// Padding around the content.
   ///
-  /// If there is no content, no padding will be provided. Otherwise, padding of
-  /// 20 pixels is provided above the content to separate the content from the
-  /// title, and padding of 24 pixels is provided on the left, right, and bottom
-  /// to separate the content from the other edges of the dialog.
-  final EdgeInsetsGeometry contentPadding;
+  /// If there is no [content], no padding will be provided. Otherwise, this
+  /// padding is used.
+  ///
+  /// This property defaults to providing a padding of 20 pixels above the
+  /// [content] to separate the [content] from the [title], and 24 pixels on the
+  /// left, right, and bottom to separate the [content] from the other edges of
+  /// the dialog.
+  ///
+  /// If [ThemeData.useMaterial3] is true, the top padding separating the
+  /// content from the title defaults to 16 pixels instead of 20 pixels.
+  final EdgeInsetsGeometry? contentPadding;
 
   /// Style for the text in the [content] of this [AlertDialog].
   ///
@@ -508,21 +541,55 @@
     final double paddingScaleFactor = _paddingScaleFactor(MediaQuery.of(context).textScaleFactor);
     final TextDirection? textDirection = Directionality.maybeOf(context);
 
+    Widget? iconWidget;
     Widget? titleWidget;
     Widget? contentWidget;
     Widget? actionsWidget;
+
+    if (icon != null) {
+      final bool belowIsTitle = title != null;
+      final bool belowIsContent = !belowIsTitle && content != null;
+      final EdgeInsets defaultIconPadding = EdgeInsets.only(
+        left: 24.0,
+        top: 24.0,
+        right: 24.0,
+        bottom: belowIsTitle ? 16.0 : belowIsContent ? 0.0 : 24.0,
+      );
+      final EdgeInsets effectiveIconPadding = iconPadding?.resolve(textDirection) ?? defaultIconPadding;
+      iconWidget = Padding(
+        padding: EdgeInsets.only(
+          left: effectiveIconPadding.left * paddingScaleFactor,
+          right: effectiveIconPadding.right * paddingScaleFactor,
+          top: effectiveIconPadding.top * paddingScaleFactor,
+          bottom: effectiveIconPadding.bottom,
+        ),
+        child: IconTheme(
+          data: IconThemeData(
+            color: iconColor ?? dialogTheme.iconColor ?? defaults.iconColor,
+          ),
+          child: icon!,
+        ),
+      );
+    }
+
     if (title != null) {
-      final EdgeInsets defaultTitlePadding = EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0);
+      final EdgeInsets defaultTitlePadding = EdgeInsets.only(
+        left: 24.0,
+        top: icon == null ? 24.0 : 0.0,
+        right: 24.0,
+        bottom: content == null ? 20.0 : 0.0,
+      );
       final EdgeInsets effectiveTitlePadding = titlePadding?.resolve(textDirection) ?? defaultTitlePadding;
       titleWidget = Padding(
         padding: EdgeInsets.only(
           left: effectiveTitlePadding.left * paddingScaleFactor,
           right: effectiveTitlePadding.right * paddingScaleFactor,
-          top: effectiveTitlePadding.top * paddingScaleFactor,
+          top: icon == null ? effectiveTitlePadding.top * paddingScaleFactor : effectiveTitlePadding.top,
           bottom: effectiveTitlePadding.bottom,
         ),
         child: DefaultTextStyle(
           style: titleTextStyle ?? dialogTheme.titleTextStyle ?? defaults.titleTextStyle!,
+          textAlign: icon == null ? TextAlign.start : TextAlign.center,
           child: Semantics(
             // For iOS platform, the focus always lands on the title.
             // Set nameRoute to false to avoid title being announce twice.
@@ -535,12 +602,20 @@
     }
 
     if (content != null) {
-      final EdgeInsets effectiveContentPadding = contentPadding.resolve(textDirection);
+      final EdgeInsets defaultContentPadding = EdgeInsets.only(
+        left: 24.0,
+        top: theme.useMaterial3 ? 16.0 : 20.0,
+        right: 24.0,
+        bottom: 24.0,
+      );
+      final EdgeInsets effectiveContentPadding = contentPadding?.resolve(textDirection) ?? defaultContentPadding;
       contentWidget = Padding(
         padding: EdgeInsets.only(
           left: effectiveContentPadding.left * paddingScaleFactor,
           right: effectiveContentPadding.right * paddingScaleFactor,
-          top: title == null ? effectiveContentPadding.top * paddingScaleFactor : effectiveContentPadding.top,
+          top: title == null && icon == null
+            ? effectiveContentPadding.top * paddingScaleFactor
+            : effectiveContentPadding.top,
           bottom: effectiveContentPadding.bottom,
         ),
         child: DefaultTextStyle(
@@ -580,6 +655,7 @@
                 mainAxisSize: MainAxisSize.min,
                 crossAxisAlignment: CrossAxisAlignment.stretch,
                 children: <Widget>[
+                  if (icon != null) iconWidget!,
                   if (title != null) titleWidget!,
                   if (content != null) contentWidget!,
                 ],
@@ -591,6 +667,7 @@
       ];
     } else {
       columnChildren = <Widget>[
+        if (icon != null) iconWidget!,
         if (title != null) titleWidget!,
         if (content != null) Flexible(child: contentWidget!),
         if (actions != null) actionsWidget!,
@@ -1187,6 +1264,7 @@
 class _DefaultsM2 extends DialogTheme {
   _DefaultsM2(this.context)
     : _textTheme = Theme.of(context).textTheme,
+      _iconTheme = Theme.of(context).iconTheme,
       super(
         alignment: Alignment.center,
         elevation: 24.0,
@@ -1195,6 +1273,10 @@
 
   final BuildContext context;
   final TextTheme _textTheme;
+  final IconThemeData _iconTheme;
+
+  @override
+  Color? get iconColor => _iconTheme.color;
 
   @override
   Color? get backgroundColor => Theme.of(context).dialogBackgroundColor;
@@ -1228,6 +1310,9 @@
   late final ColorScheme _colors = Theme.of(context).colorScheme;
   late final TextTheme _textTheme = Theme.of(context).textTheme;
 
+  @override
+  Color? get iconColor => _colors.secondary;
+
   // TODO(darrenaustin): overlay should be handled by Material widget: https://github.com/flutter/flutter/issues/9160
   @override
   Color? get backgroundColor => ElevationOverlay.colorWithOverlay(_colors.surface, _colors.primary, 6.0);
diff --git a/packages/flutter/lib/src/material/dialog_theme.dart b/packages/flutter/lib/src/material/dialog_theme.dart
index d18aa64..b3c7cd3 100644
--- a/packages/flutter/lib/src/material/dialog_theme.dart
+++ b/packages/flutter/lib/src/material/dialog_theme.dart
@@ -32,6 +32,7 @@
     this.elevation,
     this.shape,
     this.alignment,
+    this.iconColor,
     this.titleTextStyle,
     this.contentTextStyle,
     this.actionsPadding,
@@ -60,6 +61,9 @@
   /// Overrides the default value for [AlertDialog.actionsPadding].
   final EdgeInsetsGeometry? actionsPadding;
 
+  /// Used to configure the [IconTheme] for the [AlertDialog.icon] widget.
+  final Color? iconColor;
+
   /// Creates a copy of this object but with the given fields replaced with the
   /// new values.
   DialogTheme copyWith({
@@ -67,6 +71,7 @@
     double? elevation,
     ShapeBorder? shape,
     AlignmentGeometry? alignment,
+    Color? iconColor,
     TextStyle? titleTextStyle,
     TextStyle? contentTextStyle,
     EdgeInsetsGeometry? actionsPadding,
@@ -76,6 +81,7 @@
       elevation: elevation ?? this.elevation,
       shape: shape ?? this.shape,
       alignment: alignment ?? this.alignment,
+      iconColor: iconColor ?? this.iconColor,
       titleTextStyle: titleTextStyle ?? this.titleTextStyle,
       contentTextStyle: contentTextStyle ?? this.contentTextStyle,
       actionsPadding: actionsPadding ?? this.actionsPadding,
@@ -99,6 +105,7 @@
       elevation: lerpDouble(a?.elevation, b?.elevation, t),
       shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
       alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t),
+      iconColor: Color.lerp(a?.iconColor, b?.iconColor, t),
       titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t),
       contentTextStyle: TextStyle.lerp(a?.contentTextStyle, b?.contentTextStyle, t),
       actionsPadding: EdgeInsetsGeometry.lerp(a?.actionsPadding, b?.actionsPadding, t),
@@ -121,6 +128,7 @@
         && other.elevation == elevation
         && other.shape == shape
         && other.alignment == alignment
+        && other.iconColor == iconColor
         && other.titleTextStyle == titleTextStyle
         && other.contentTextStyle == contentTextStyle
         && other.actionsPadding == actionsPadding;
@@ -133,6 +141,7 @@
     properties.add(DoubleProperty('elevation', elevation));
     properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
     properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
+    properties.add(ColorProperty('iconColor', iconColor));
     properties.add(DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null));
     properties.add(DiagnosticsProperty<TextStyle>('contentTextStyle', contentTextStyle, defaultValue: null));
     properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('actionsPadding', actionsPadding, defaultValue: null));
diff --git a/packages/flutter/test/material/dialog_test.dart b/packages/flutter/test/material/dialog_test.dart
index fba213e..0511609 100644
--- a/packages/flutter/test/material/dialog_test.dart
+++ b/packages/flutter/test/material/dialog_test.dart
@@ -754,19 +754,21 @@
       3.0: 1.0 / 3.0,
     };
 
+    final GlobalKey iconKey = GlobalKey();
     final GlobalKey titleKey = GlobalKey();
     final GlobalKey contentKey = GlobalKey();
     final GlobalKey childrenKey = GlobalKey();
 
     final Finder dialogFinder = find.descendant(of: find.byType(Dialog), matching: find.byType(Material)).first;
+    final Finder iconFinder = find.byKey(iconKey);
     final Finder titleFinder = find.byKey(titleKey);
     final Finder contentFinder = find.byKey(contentKey);
     final Finder actionsFinder = _findButtonBar();
     final Finder childrenFinder = find.byKey(childrenKey);
 
-    Future<void> openDialog(WidgetTester tester, Widget dialog, double textScaleFactor) async {
+    Future<void> openDialog(WidgetTester tester, Widget dialog, double textScaleFactor, {bool isM3 = false}) async {
       await tester.pumpWidget(
-        _buildAppWithDialog(dialog, textScaleFactor: textScaleFactor),
+        _buildAppWithDialog(dialog, textScaleFactor: textScaleFactor, theme: ThemeData(useMaterial3: isM3)),
       );
 
       await tester.tap(find.text('X'));
@@ -853,6 +855,10 @@
       );
     }
 
+    final Widget icon = Icon(
+      Icons.ac_unit,
+      key: iconKey,
+    );
     final Widget title = Text(
       'title',
       key: titleKey,
@@ -876,7 +882,203 @@
     ];
 
     for (final double textScaleFactor in textScaleFactors) {
-      testWidgets('AlertDialog padding is correct when only title and actions are specified [textScaleFactor]=$textScaleFactor}', (WidgetTester tester) async {
+      testWidgets('AlertDialog padding is correct when only icon and actions are specified [textScaleFactor]=$textScaleFactor', (WidgetTester tester) async {
+        final AlertDialog dialog = AlertDialog(
+          icon: icon,
+          actions: actions,
+        );
+
+        await openDialog(tester, dialog, textScaleFactor);
+
+        expectTopEdgePadding(
+          tester,
+          finder: iconFinder,
+          textScaleFactor: textScaleFactor,
+          unscaledValue: 24.0,
+        );
+        expectLeftEdgePadding(
+          tester,
+          finder: iconFinder,
+          textScaleFactor: textScaleFactor,
+          unscaledValue: 24.0,
+        );
+        expectRightEdgePadding(
+          tester,
+          finder: iconFinder,
+          textScaleFactor: textScaleFactor,
+          unscaledValue: 24.0,
+        );
+        expectVerticalInnerPadding(
+          tester,
+          top: iconFinder,
+          bottom: actionsFinder,
+          value: 24.0,
+        );
+        expectLeftEdgePadding(
+          tester,
+          finder: actionsFinder,
+          textScaleFactor: textScaleFactor,
+          unscaledValue: 0.0,
+        );
+        expectRightEdgePadding(
+          tester,
+          finder: actionsFinder,
+          textScaleFactor: textScaleFactor,
+          unscaledValue: 0.0,
+        );
+        expectBottomEdgePadding(
+          tester,
+          finder: actionsFinder,
+          textScaleFactor: textScaleFactor,
+          unscaledValue: 0.0,
+        );
+      });
+
+      testWidgets('AlertDialog padding is correct when only icon, title and actions are specified [textScaleFactor]=$textScaleFactor', (WidgetTester tester) async {
+        final AlertDialog dialog = AlertDialog(
+          icon: icon,
+          title: title,
+          actions: actions,
+        );
+
+        await openDialog(tester, dialog, textScaleFactor);
+
+        expectTopEdgePadding(
+          tester,
+          finder: iconFinder,
+          textScaleFactor: textScaleFactor,
+          unscaledValue: 24.0,
+        );
+        expectLeftEdgePadding(
+          tester,
+          finder: iconFinder,
+          textScaleFactor: textScaleFactor,
+          unscaledValue: 24.0,
+        );
+        expectRightEdgePadding(
+          tester,
+          finder: iconFinder,
+          textScaleFactor: textScaleFactor,
+          unscaledValue: 24.0,
+        );
+        expectVerticalInnerPadding(
+          tester,
+          top: iconFinder,
+          bottom: titleFinder,
+          value: 16.0,
+        );
+        expectLeftEdgePadding(
+          tester,
+          finder: titleFinder,
+          textScaleFactor: textScaleFactor,
+          unscaledValue: 24.0,
+        );
+        expectRightEdgePadding(
+          tester,
+          finder: titleFinder,
+          textScaleFactor: textScaleFactor,
+          unscaledValue: 24.0,
+        );
+        expectVerticalInnerPadding(
+          tester,
+          top: titleFinder,
+          bottom: actionsFinder,
+          value: 20.0,
+        );
+        expectLeftEdgePadding(
+          tester,
+          finder: actionsFinder,
+          textScaleFactor: textScaleFactor,
+          unscaledValue: 0.0,
+        );
+        expectRightEdgePadding(
+          tester,
+          finder: actionsFinder,
+          textScaleFactor: textScaleFactor,
+          unscaledValue: 0.0,
+        );
+        expectBottomEdgePadding(
+          tester,
+          finder: actionsFinder,
+          textScaleFactor: textScaleFactor,
+          unscaledValue: 0.0,
+        );
+      });
+
+      for (final bool isM3 in <bool>[true, false]) {
+        testWidgets('AlertDialog padding is correct when only icon, content and actions are specified [textScaleFactor]=$textScaleFactor [isM3]=$isM3', (WidgetTester tester) async {
+          final AlertDialog dialog = AlertDialog(
+            icon: icon,
+            content: content,
+            actions: actions,
+          );
+
+          await openDialog(tester, dialog, textScaleFactor, isM3: isM3);
+
+          expectTopEdgePadding(
+            tester,
+            finder: iconFinder,
+            textScaleFactor: textScaleFactor,
+            unscaledValue: 24.0,
+          );
+          expectLeftEdgePadding(
+            tester,
+            finder: iconFinder,
+            textScaleFactor: textScaleFactor,
+            unscaledValue: 24.0,
+          );
+          expectRightEdgePadding(
+            tester,
+            finder: iconFinder,
+            textScaleFactor: textScaleFactor,
+            unscaledValue: 24.0,
+          );
+          expectVerticalInnerPadding(
+            tester,
+            top: iconFinder,
+            bottom: contentFinder,
+            value: isM3 ? 16.0 : 20.0,
+          );
+          expectLeftEdgePadding(
+            tester,
+            finder: contentFinder,
+            textScaleFactor: textScaleFactor,
+            unscaledValue: 24.0,
+          );
+          expectRightEdgePadding(
+            tester,
+            finder: contentFinder,
+            textScaleFactor: textScaleFactor,
+            unscaledValue: 24.0,
+          );
+          expectVerticalInnerPadding(
+            tester,
+            top: contentFinder,
+            bottom: actionsFinder,
+            value: 24.0,
+          );
+          expectLeftEdgePadding(
+            tester,
+            finder: actionsFinder,
+            textScaleFactor: textScaleFactor,
+            unscaledValue: 0.0,
+          );
+          expectRightEdgePadding(
+            tester,
+            finder: actionsFinder,
+            textScaleFactor: textScaleFactor,
+            unscaledValue: 0.0,
+          );
+          expectBottomEdgePadding(
+            tester,
+            finder: actionsFinder,
+            textScaleFactor: textScaleFactor,
+            unscaledValue: 0.0,
+          );
+        });
+      }
+
+      testWidgets('AlertDialog padding is correct when only title and actions are specified [textScaleFactor]=$textScaleFactor', (WidgetTester tester) async {
         final AlertDialog dialog = AlertDialog(
           title: title,
           actions: actions,
@@ -928,7 +1130,7 @@
         );
       });
 
-      testWidgets('AlertDialog padding is correct when only content and actions are specified [textScaleFactor]=$textScaleFactor}', (WidgetTester tester) async {
+      testWidgets('AlertDialog padding is correct when only content and actions are specified [textScaleFactor]=$textScaleFactor', (WidgetTester tester) async {
         final AlertDialog dialog = AlertDialog(
           content: content,
           actions: actions,
@@ -980,7 +1182,7 @@
         );
       });
 
-      testWidgets('AlertDialog padding is correct when title, content, and actions are specified [textScaleFactor]=$textScaleFactor}', (WidgetTester tester) async {
+      testWidgets('AlertDialog padding is correct when title, content, and actions are specified [textScaleFactor]=$textScaleFactor', (WidgetTester tester) async {
         final AlertDialog dialog = AlertDialog(
           title: title,
           content: content,
@@ -1051,7 +1253,7 @@
         );
       });
 
-      testWidgets('SimpleDialog padding is correct when only children are specified [textScaleFactor]=$textScaleFactor}', (WidgetTester tester) async {
+      testWidgets('SimpleDialog padding is correct when only children are specified [textScaleFactor]=$textScaleFactor', (WidgetTester tester) async {
         final SimpleDialog dialog = SimpleDialog(
           children: children,
         );
@@ -1084,7 +1286,7 @@
         );
       });
 
-      testWidgets('SimpleDialog padding is correct when title and children are specified [textScaleFactor]=$textScaleFactor}', (WidgetTester tester) async {
+      testWidgets('SimpleDialog padding is correct when title and children are specified [textScaleFactor]=$textScaleFactor', (WidgetTester tester) async {
         final SimpleDialog dialog = SimpleDialog(
           title: title,
           children: children,
diff --git a/packages/flutter/test/material/dialog_theme_test.dart b/packages/flutter/test/material/dialog_theme_test.dart
index 7380f20..5666af4 100644
--- a/packages/flutter/test/material/dialog_theme_test.dart
+++ b/packages/flutter/test/material/dialog_theme_test.dart
@@ -52,6 +52,7 @@
       backgroundColor: Color(0xff123456),
       elevation: 8.0,
       alignment: Alignment.bottomLeft,
+      iconColor: Color(0xff654321),
       titleTextStyle: TextStyle(color: Color(0xffffffff)),
       contentTextStyle: TextStyle(color: Color(0xff000000)),
       actionsPadding: EdgeInsets.all(8.0),
@@ -63,6 +64,7 @@
       'backgroundColor: Color(0xff123456)',
       'elevation: 8.0',
       'alignment: Alignment.bottomLeft',
+      'iconColor: Color(0xff654321)',
       'titleTextStyle: TextStyle(inherit: true, color: Color(0xffffffff))',
       'contentTextStyle: TextStyle(inherit: true, color: Color(0xff000000))',
       'actionsPadding: EdgeInsets.all(8.0)',
@@ -182,6 +184,80 @@
     );
   });
 
+  testWidgets('Custom Icon Color - Constructor Param - highest preference', (WidgetTester tester) async {
+    const Color iconColor = Colors.pink, dialogThemeColor = Colors.green, iconThemeColor = Colors.yellow;
+    final ThemeData theme = ThemeData(
+      iconTheme: const IconThemeData(color: iconThemeColor),
+      dialogTheme: const DialogTheme(iconColor: dialogThemeColor),
+    );
+    const AlertDialog dialog = AlertDialog(
+      icon: Icon(Icons.ac_unit),
+      iconColor: iconColor,
+      actions: <Widget>[ ],
+    );
+
+    await tester.pumpWidget(_appWithDialog(tester, dialog, theme: theme));
+    await tester.tap(find.text('X'));
+    await tester.pumpAndSettle();
+
+    // first is Text('X')
+    final RichText text = tester.widget(find.byType(RichText).last);
+    expect(text.text.style!.color, iconColor);
+  });
+
+  testWidgets('Custom Icon Color - Dialog Theme - preference over Theme', (WidgetTester tester) async {
+    const Color dialogThemeColor = Colors.green, iconThemeColor = Colors.yellow;
+    final ThemeData theme = ThemeData(
+      iconTheme: const IconThemeData(color: iconThemeColor),
+      dialogTheme: const DialogTheme(iconColor: dialogThemeColor),
+    );
+    const AlertDialog dialog = AlertDialog(
+      icon: Icon(Icons.ac_unit),
+      actions: <Widget>[ ],
+    );
+
+    await tester.pumpWidget(_appWithDialog(tester, dialog, theme: theme));
+    await tester.tap(find.text('X'));
+    await tester.pumpAndSettle();
+
+    // first is Text('X')
+    final RichText text = tester.widget(find.byType(RichText).last);
+    expect(text.text.style!.color, dialogThemeColor);
+  });
+
+  testWidgets('Custom Icon Color - Theme - lowest preference', (WidgetTester tester) async {
+    const Color iconThemeColor = Colors.yellow;
+    final ThemeData theme = ThemeData(iconTheme: const IconThemeData(color: iconThemeColor));
+    const AlertDialog dialog = AlertDialog(
+      icon: Icon(Icons.ac_unit),
+      actions: <Widget>[ ],
+    );
+
+    await tester.pumpWidget(_appWithDialog(tester, dialog, theme: theme));
+    await tester.tap(find.text('X'));
+    await tester.pumpAndSettle();
+
+    // first is Text('X')
+    final RichText text = tester.widget(find.byType(RichText).last);
+    expect(text.text.style!.color, iconThemeColor);
+  });
+
+  testWidgets('Custom Icon Color - Theme - lowest preference for M3', (WidgetTester tester) async {
+    final ThemeData theme = ThemeData(useMaterial3: true);
+    const AlertDialog dialog = AlertDialog(
+      icon: Icon(Icons.ac_unit),
+      actions: <Widget>[ ],
+    );
+
+    await tester.pumpWidget(_appWithDialog(tester, dialog, theme: theme));
+    await tester.tap(find.text('X'));
+    await tester.pumpAndSettle();
+
+    // first is Text('X')
+    final RichText text = tester.widget(find.byType(RichText).last);
+    expect(text.text.style!.color, ThemeData().colorScheme.secondary);
+  });
+
   testWidgets('Custom Title Text Style - Constructor Param', (WidgetTester tester) async {
     const String titleText = 'Title';
     const TextStyle titleTextStyle = TextStyle(color: Colors.pink);