Update `CircleAvatar` to support Material 3 (#114812)

diff --git a/packages/flutter/lib/src/material/circle_avatar.dart b/packages/flutter/lib/src/material/circle_avatar.dart
index 61faac6..11775c4 100644
--- a/packages/flutter/lib/src/material/circle_avatar.dart
+++ b/packages/flutter/lib/src/material/circle_avatar.dart
@@ -84,7 +84,8 @@
   /// The color with which to fill the circle. Changing the background
   /// color will cause the avatar to animate to the new color.
   ///
-  /// If a [backgroundColor] is not specified, the theme's
+  /// If a [backgroundColor] is not specified and [ThemeData.useMaterial3] is true,
+  /// [ColorScheme.primaryContainer] will be used, otherwise the theme's
   /// [ThemeData.primaryColorLight] is used with dark foreground colors, and
   /// [ThemeData.primaryColorDark] with light foreground colors.
   final Color? backgroundColor;
@@ -94,7 +95,9 @@
   /// Defaults to the primary text theme color if no [backgroundColor] is
   /// specified.
   ///
-  /// Defaults to [ThemeData.primaryColorLight] for dark background colors, and
+  /// If a [foregroundColor] is not specified and [ThemeData.useMaterial3] is true,
+  /// [ColorScheme.onPrimaryContainer] will be used, otherwise the theme's
+  /// [ThemeData.primaryColorLight] for dark background colors, and
   /// [ThemeData.primaryColorDark] for light background colors.
   final Color? foregroundColor;
 
@@ -192,8 +195,14 @@
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
     final ThemeData theme = Theme.of(context);
-    TextStyle textStyle = theme.primaryTextTheme.titleMedium!.copyWith(color: foregroundColor);
-    Color? effectiveBackgroundColor = backgroundColor;
+    final Color? effectiveForegroundColor = foregroundColor
+      ?? (theme.useMaterial3 ? theme.colorScheme.onPrimaryContainer : null);
+    final TextStyle effectiveTextStyle = theme.useMaterial3
+      ? theme.textTheme.titleMedium!
+      : theme.primaryTextTheme.titleMedium!;
+    TextStyle textStyle = effectiveTextStyle.copyWith(color: effectiveForegroundColor);
+    Color? effectiveBackgroundColor = backgroundColor
+      ?? (theme.useMaterial3 ? theme.colorScheme.primaryContainer : null);
     if (effectiveBackgroundColor == null) {
       switch (ThemeData.estimateBrightnessForColor(textStyle.color!)) {
         case Brightness.dark:
@@ -203,7 +212,7 @@
           effectiveBackgroundColor = theme.primaryColorDark;
           break;
       }
-    } else if (foregroundColor == null) {
+    } else if (effectiveForegroundColor == null) {
       switch (ThemeData.estimateBrightnessForColor(backgroundColor!)) {
         case Brightness.dark:
           textStyle = textStyle.copyWith(color: theme.primaryColorLight);
diff --git a/packages/flutter/test/material/circle_avatar_test.dart b/packages/flutter/test/material/circle_avatar_test.dart
index 4a7f534..9249f43 100644
--- a/packages/flutter/test/material/circle_avatar_test.dart
+++ b/packages/flutter/test/material/circle_avatar_test.dart
@@ -144,11 +144,8 @@
     expect(paragraph.text.style!.color, equals(foregroundColor));
   });
 
-  testWidgets('CircleAvatar with light theme', (WidgetTester tester) async {
-    final ThemeData theme = ThemeData(
-      primaryColor: Colors.grey.shade100,
-      primaryColorBrightness: Brightness.light,
-    );
+  testWidgets('CircleAvatar default colors', (WidgetTester tester) async {
+    final ThemeData theme = ThemeData(useMaterial3: true);
     await tester.pumpWidget(
       wrap(
         child: Theme(
@@ -163,35 +160,10 @@
     final RenderConstrainedBox box = tester.renderObject(find.byType(CircleAvatar));
     final RenderDecoratedBox child = box.child! as RenderDecoratedBox;
     final BoxDecoration decoration = child.decoration as BoxDecoration;
-    expect(decoration.color, equals(theme.primaryColorLight));
+    expect(decoration.color, equals(theme.colorScheme.primaryContainer));
 
     final RenderParagraph paragraph = tester.renderObject(find.text('Z'));
-    expect(paragraph.text.style!.color, equals(theme.primaryTextTheme.titleLarge!.color));
-  });
-
-  testWidgets('CircleAvatar with dark theme', (WidgetTester tester) async {
-    final ThemeData theme = ThemeData(
-      primaryColor: Colors.grey.shade800,
-      primaryColorBrightness: Brightness.dark,
-    );
-    await tester.pumpWidget(
-      wrap(
-        child: Theme(
-          data: theme,
-          child: const CircleAvatar(
-            child: Text('Z'),
-          ),
-        ),
-      ),
-    );
-
-    final RenderConstrainedBox box = tester.renderObject(find.byType(CircleAvatar));
-    final RenderDecoratedBox child = box.child! as RenderDecoratedBox;
-    final BoxDecoration decoration = child.decoration as BoxDecoration;
-    expect(decoration.color, equals(theme.primaryColorDark));
-
-    final RenderParagraph paragraph = tester.renderObject(find.text('Z'));
-    expect(paragraph.text.style!.color, equals(theme.primaryTextTheme.titleLarge!.color));
+    expect(paragraph.text.style!.color, equals(theme.colorScheme.onPrimaryContainer));
   });
 
   testWidgets('CircleAvatar text does not expand with textScaleFactor', (WidgetTester tester) async {
@@ -306,6 +278,61 @@
     final RenderParagraph paragraph = tester.renderObject(find.text('Z'));
     expect(paragraph.text.style!.color, equals(ThemeData.fallback().primaryColorLight));
   });
+
+  group('Material 2', () {
+    // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
+    // is turned on by default, these tests can be removed.
+
+    testWidgets('CircleAvatar default colors with light theme', (WidgetTester tester) async {
+      final ThemeData theme = ThemeData(
+        primaryColor: Colors.grey.shade100,
+        primaryColorBrightness: Brightness.light,
+      );
+      await tester.pumpWidget(
+        wrap(
+          child: Theme(
+            data: theme,
+            child: const CircleAvatar(
+              child: Text('Z'),
+            ),
+          ),
+        ),
+      );
+
+      final RenderConstrainedBox box = tester.renderObject(find.byType(CircleAvatar));
+      final RenderDecoratedBox child = box.child! as RenderDecoratedBox;
+      final BoxDecoration decoration = child.decoration as BoxDecoration;
+      expect(decoration.color, equals(theme.primaryColorLight));
+
+      final RenderParagraph paragraph = tester.renderObject(find.text('Z'));
+      expect(paragraph.text.style!.color, equals(theme.primaryTextTheme.titleLarge!.color));
+    });
+
+    testWidgets('CircleAvatar default colors with dark theme', (WidgetTester tester) async {
+      final ThemeData theme = ThemeData(
+        primaryColor: Colors.grey.shade800,
+        primaryColorBrightness: Brightness.dark,
+      );
+      await tester.pumpWidget(
+        wrap(
+          child: Theme(
+            data: theme,
+            child: const CircleAvatar(
+              child: Text('Z'),
+            ),
+          ),
+        ),
+      );
+
+      final RenderConstrainedBox box = tester.renderObject(find.byType(CircleAvatar));
+      final RenderDecoratedBox child = box.child! as RenderDecoratedBox;
+      final BoxDecoration decoration = child.decoration as BoxDecoration;
+      expect(decoration.color, equals(theme.primaryColorDark));
+
+      final RenderParagraph paragraph = tester.renderObject(find.text('Z'));
+      expect(paragraph.text.style!.color, equals(theme.primaryTextTheme.titleLarge!.color));
+    });
+  });
 }
 
 Widget wrap({ required Widget child }) {