diff --git a/packages/flutter/lib/src/material/card.dart b/packages/flutter/lib/src/material/card.dart index 176a87c6f2..54410bd29f 100644 --- a/packages/flutter/lib/src/material/card.dart +++ b/packages/flutter/lib/src/material/card.dart @@ -215,7 +215,7 @@ class Card extends StatelessWidget { @override Widget build(BuildContext context) { - final CardTheme cardTheme = CardTheme.of(context); + final CardThemeData cardTheme = CardTheme.of(context); final CardThemeData defaults; if (Theme.of(context).useMaterial3) { defaults = switch (_variant) { diff --git a/packages/flutter/lib/src/material/card_theme.dart b/packages/flutter/lib/src/material/card_theme.dart index 197289c415..5636fdf43f 100644 --- a/packages/flutter/lib/src/material/card_theme.dart +++ b/packages/flutter/lib/src/material/card_theme.dart @@ -156,9 +156,9 @@ class CardTheme extends InheritedWidget with Diagnosticable { } /// The [ThemeData.cardTheme] property of the ambient [Theme]. - static CardTheme of(BuildContext context) { + static CardThemeData of(BuildContext context) { final CardTheme? cardTheme = context.dependOnInheritedWidgetOfExactType(); - return cardTheme ?? Theme.of(context).cardTheme; + return cardTheme?.data ?? Theme.of(context).cardTheme; } @override diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index 26e391ecbb..8c11fe5b43 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -315,7 +315,8 @@ class ThemeData with Diagnosticable { BottomNavigationBarThemeData? bottomNavigationBarTheme, BottomSheetThemeData? bottomSheetTheme, ButtonThemeData? buttonTheme, - CardTheme? cardTheme, + // TODO(QuncCccccc): Change the parameter type to CardThemeData + Object? cardTheme, CheckboxThemeData? checkboxTheme, ChipThemeData? chipTheme, DataTableThemeData? dataTableTheme, @@ -493,7 +494,15 @@ class ThemeData with Diagnosticable { bottomAppBarTheme ??= const BottomAppBarTheme(); bottomNavigationBarTheme ??= const BottomNavigationBarThemeData(); bottomSheetTheme ??= const BottomSheetThemeData(); - cardTheme ??= const CardTheme(); + // TODO(QuncCccccc): Clean it up once the type of `cardTheme` is changed to `CardThemeData` + if (cardTheme != null) { + if (cardTheme is CardTheme) { + cardTheme = cardTheme.data; + } else if (cardTheme is! CardThemeData) { + throw ArgumentError('cardTheme must be either a CardThemeData or a CardTheme'); + } + } + cardTheme ??= const CardThemeData(); checkboxTheme ??= const CheckboxThemeData(); chipTheme ??= const ChipThemeData(); dataTableTheme ??= const DataTableThemeData(); @@ -594,7 +603,7 @@ class ThemeData with Diagnosticable { bottomNavigationBarTheme: bottomNavigationBarTheme, bottomSheetTheme: bottomSheetTheme, buttonTheme: buttonTheme, - cardTheme: cardTheme, + cardTheme: cardTheme as CardThemeData, checkboxTheme: checkboxTheme, chipTheme: chipTheme, dataTableTheme: dataTableTheme, @@ -1266,7 +1275,7 @@ class ThemeData with Diagnosticable { /// The colors and styles used to render [Card]. /// /// This is the value returned from [CardTheme.of]. - final CardTheme cardTheme; + final CardThemeData cardTheme; /// A theme for customizing the appearance and layout of [Checkbox] widgets. final CheckboxThemeData checkboxTheme; @@ -1466,7 +1475,7 @@ class ThemeData with Diagnosticable { BottomNavigationBarThemeData? bottomNavigationBarTheme, BottomSheetThemeData? bottomSheetTheme, ButtonThemeData? buttonTheme, - CardTheme? cardTheme, + Object? cardTheme, CheckboxThemeData? checkboxTheme, ChipThemeData? chipTheme, DataTableThemeData? dataTableTheme, @@ -1521,6 +1530,15 @@ class ThemeData with Diagnosticable { }) { cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault(); + // TODO(QuncCccccc): Clean it up once the type of `cardTheme` is changed to `CardThemeData` + if (cardTheme != null) { + if (cardTheme is CardTheme) { + cardTheme = cardTheme.data; + } else if (cardTheme is! CardThemeData) { + throw ArgumentError('cardTheme must be either a CardThemeData or a CardTheme'); + } + } + // TODO(QuncCccccc): Clean this up once the type of `dialogTheme` is changed to `DialogThemeData` if (dialogTheme != null) { if (dialogTheme is DialogTheme) { @@ -1585,7 +1603,7 @@ class ThemeData with Diagnosticable { bottomNavigationBarTheme: bottomNavigationBarTheme ?? this.bottomNavigationBarTheme, bottomSheetTheme: bottomSheetTheme ?? this.bottomSheetTheme, buttonTheme: buttonTheme ?? this.buttonTheme, - cardTheme: cardTheme ?? this.cardTheme, + cardTheme: cardTheme as CardThemeData? ?? this.cardTheme, checkboxTheme: checkboxTheme ?? this.checkboxTheme, chipTheme: chipTheme ?? this.chipTheme, dataTableTheme: dataTableTheme ?? this.dataTableTheme, @@ -1778,7 +1796,7 @@ class ThemeData with Diagnosticable { bottomNavigationBarTheme: BottomNavigationBarThemeData.lerp(a.bottomNavigationBarTheme, b.bottomNavigationBarTheme, t), bottomSheetTheme: BottomSheetThemeData.lerp(a.bottomSheetTheme, b.bottomSheetTheme, t)!, buttonTheme: t < 0.5 ? a.buttonTheme : b.buttonTheme, - cardTheme: CardTheme.lerp(a.cardTheme, b.cardTheme, t), + cardTheme: CardThemeData.lerp(a.cardTheme, b.cardTheme, t), checkboxTheme: CheckboxThemeData.lerp(a.checkboxTheme, b.checkboxTheme, t), chipTheme: ChipThemeData.lerp(a.chipTheme, b.chipTheme, t)!, dataTableTheme: DataTableThemeData.lerp(a.dataTableTheme, b.dataTableTheme, t), @@ -2076,7 +2094,7 @@ class ThemeData with Diagnosticable { properties.add(DiagnosticsProperty('bottomNavigationBarTheme', bottomNavigationBarTheme, defaultValue: defaultData.bottomNavigationBarTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty('bottomSheetTheme', bottomSheetTheme, defaultValue: defaultData.bottomSheetTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty('buttonTheme', buttonTheme, level: DiagnosticLevel.debug)); - properties.add(DiagnosticsProperty('cardTheme', cardTheme, level: DiagnosticLevel.debug)); + properties.add(DiagnosticsProperty('cardTheme', cardTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty('checkboxTheme', checkboxTheme, defaultValue: defaultData.checkboxTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty('chipTheme', chipTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty('dataTableTheme', dataTableTheme, defaultValue: defaultData.dataTableTheme, level: DiagnosticLevel.debug)); diff --git a/packages/flutter/test/material/card_theme_test.dart b/packages/flutter/test/material/card_theme_test.dart index df6a5ae157..b9a734bb88 100644 --- a/packages/flutter/test/material/card_theme_test.dart +++ b/packages/flutter/test/material/card_theme_test.dart @@ -106,7 +106,7 @@ void main() { }); testWidgets('Card uses values from CardTheme', (WidgetTester tester) async { - final CardTheme cardTheme = _cardTheme(); + final CardThemeData cardTheme = _cardTheme(); await tester.pumpWidget(MaterialApp( theme: ThemeData(cardTheme: cardTheme), @@ -163,7 +163,7 @@ void main() { }); testWidgets('CardTheme properties take priority over ThemeData properties', (WidgetTester tester) async { - final CardTheme cardTheme = _cardTheme(); + final CardThemeData cardTheme = _cardTheme(); final ThemeData themeData = _themeData().copyWith(cardTheme: cardTheme); await tester.pumpWidget(MaterialApp( @@ -192,7 +192,7 @@ void main() { }); testWidgets('Material3 - CardTheme customizes shape', (WidgetTester tester) async { - const CardTheme cardTheme = CardTheme( + const CardThemeData cardTheme = CardThemeData( color: Colors.white, shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(7))), elevation: 1.0, @@ -220,6 +220,120 @@ void main() { ); }); + testWidgets('Card properties are taken over the theme values', (WidgetTester tester) async { + const Clip themeClipBehavior = Clip.antiAlias; + const Color themeColor = Colors.red; + const Color themeShadowColor = Colors.orange; + const double themeElevation = 10.0; + const EdgeInsets themeMargin = EdgeInsets.all(12.0); + const ShapeBorder themeShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0))); + + const Clip clipBehavior = Clip.hardEdge; + const Color color = Colors.yellow; + const Color shadowColor = Colors.green; + const double elevation = 20.0; + const EdgeInsets margin = EdgeInsets.all(18.0); + const ShapeBorder shape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(25.0))); + + final ThemeData themeData = ThemeData( + cardTheme: const CardThemeData( + clipBehavior: themeClipBehavior, + color: themeColor, + shadowColor: themeShadowColor, + elevation: themeElevation, + margin: themeMargin, + shape: themeShape, + ), + ); + + await tester.pumpWidget(MaterialApp( + theme: themeData, + home: const Scaffold( + body: Card( + clipBehavior: clipBehavior, + color: color, + shadowColor: shadowColor, + elevation: elevation, + margin: margin, + shape: shape, + child: SizedBox( + width: 200, + height: 200, + ), + ), + ), + ) + ); + + final Padding cardMargin = _getCardPadding(tester); + final Material material = _getCardMaterial(tester); + + expect(material.clipBehavior, clipBehavior); + expect(material.color, color); + expect(material.shadowColor, shadowColor); + expect(material.elevation, elevation); + expect(material.shape, shape); + expect(cardMargin.padding, margin); + }); + + testWidgets('Local CardTheme can override global CardTheme', (WidgetTester tester) async { + const Clip globalClipBehavior = Clip.antiAlias; + const Color globalColor = Colors.red; + const Color globalShadowColor = Colors.orange; + const double globalElevation = 10.0; + const EdgeInsets globalMargin = EdgeInsets.all(12.0); + const ShapeBorder globalShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0))); + + const Clip localClipBehavior = Clip.hardEdge; + const Color localColor = Colors.yellow; + const Color localShadowColor = Colors.green; + const double localElevation = 20.0; + const EdgeInsets localMargin = EdgeInsets.all(18.0); + const ShapeBorder localShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(25.0))); + + final ThemeData themeData = ThemeData( + cardTheme: const CardThemeData( + clipBehavior: globalClipBehavior, + color: globalColor, + shadowColor: globalShadowColor, + elevation: globalElevation, + margin: globalMargin, + shape: globalShape, + ), + ); + await tester.pumpWidget(MaterialApp( + theme: themeData, + home: const Scaffold( + body: CardTheme( + data: CardThemeData( + clipBehavior: localClipBehavior, + color: localColor, + shadowColor: localShadowColor, + elevation: localElevation, + margin: localMargin, + shape: localShape, + ), + child: Card( + child: SizedBox( + width: 200, + height: 200, + ), + ), + ), + ), + )); + + final Padding cardMargin = _getCardPadding(tester); + final Material material = _getCardMaterial(tester); + + expect(material.clipBehavior, localClipBehavior); + expect(material.color, localColor); + expect(material.shadowColor, localShadowColor); + expect(material.elevation, localElevation); + expect(material.shape, localShape); + expect(cardMargin.padding, localMargin); + }); + group('Material 2', () { // These tests are only relevant for Material 2. Once Material 2 // support is deprecated and the APIs are removed, these tests @@ -262,7 +376,7 @@ void main() { }); testWidgets('Material2 - CardTheme customizes shape', (WidgetTester tester) async { - const CardTheme cardTheme = CardTheme( + const CardThemeData cardTheme = CardThemeData( color: Colors.white, shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(7))), elevation: 1.0, @@ -292,8 +406,8 @@ void main() { }); } -CardTheme _cardTheme() { - return const CardTheme( +CardThemeData _cardTheme() { + return const CardThemeData( clipBehavior: Clip.antiAlias, color: Colors.green, shadowColor: Colors.red, diff --git a/packages/flutter/test/material/search_anchor_test.dart b/packages/flutter/test/material/search_anchor_test.dart index 5893ef4da1..7f1ef6fbc2 100644 --- a/packages/flutter/test/material/search_anchor_test.dart +++ b/packages/flutter/test/material/search_anchor_test.dart @@ -2707,7 +2707,7 @@ void main() { backgroundColor: const Color(0xffffff00) ), ), - cardTheme: const CardTheme(color: Color(0xff00ffff)), + cardTheme: const CardThemeData(color: Color(0xff00ffff)), ); Widget buildSearchAnchor() { return MaterialApp( diff --git a/packages/flutter/test/material/theme_data_test.dart b/packages/flutter/test/material/theme_data_test.dart index 5f14ae5b46..44c824cd1b 100644 --- a/packages/flutter/test/material/theme_data_test.dart +++ b/packages/flutter/test/material/theme_data_test.dart @@ -888,7 +888,7 @@ void main() { bottomSheetTheme: const BottomSheetThemeData(backgroundColor: Colors.black), buttonBarTheme: const ButtonBarThemeData(alignment: MainAxisAlignment.start), buttonTheme: const ButtonThemeData(colorScheme: ColorScheme.dark()), - cardTheme: const CardTheme(color: Colors.black), + cardTheme: const CardThemeData(color: Colors.black), checkboxTheme: const CheckboxThemeData(), chipTheme: chipTheme, dataTableTheme: const DataTableThemeData(), @@ -1002,7 +1002,7 @@ void main() { bottomSheetTheme: const BottomSheetThemeData(backgroundColor: Colors.white), buttonBarTheme: const ButtonBarThemeData(alignment: MainAxisAlignment.end), buttonTheme: const ButtonThemeData(colorScheme: ColorScheme.light()), - cardTheme: const CardTheme(color: Colors.white), + cardTheme: const CardThemeData(color: Colors.white), checkboxTheme: const CheckboxThemeData(), chipTheme: otherChipTheme, dataTableTheme: const DataTableThemeData(), diff --git a/packages/flutter/test/widgets/inherited_test.dart b/packages/flutter/test/widgets/inherited_test.dart index d793e85010..f6187a7c9e 100644 --- a/packages/flutter/test/widgets/inherited_test.dart +++ b/packages/flutter/test/widgets/inherited_test.dart @@ -60,7 +60,7 @@ class ThemedCard extends SingleChildRenderObjectWidget { @override RenderPhysicalShape createRenderObject(BuildContext context) { - final CardThemeData cardTheme = CardTheme.of(context).data; + final CardThemeData cardTheme = CardTheme.of(context); return RenderPhysicalShape( clipper: ShapeBorderClipper(shape: cardTheme.shape ?? const RoundedRectangleBorder()), @@ -73,7 +73,7 @@ class ThemedCard extends SingleChildRenderObjectWidget { @override void updateRenderObject(BuildContext context, RenderPhysicalShape renderObject) { - final CardThemeData cardTheme = CardTheme.of(context).data; + final CardThemeData cardTheme = CardTheme.of(context); renderObject ..clipper = ShapeBorderClipper(shape: cardTheme.shape ?? const RoundedRectangleBorder())