Skip to content

Commit 534adfb

Browse files
authored
Normalize ThemeData.cardTheme (#153254)
Following flutter/flutter#151914, this PR is to normalize `ThemeData.cardTheme`; change the `CardTheme cardTheme` property to `CardThemeData cardTheme` in `ThemeData`. In `ThemeData()` and `ThemeData.copyWith()`, the `cardTheme` parameter type is changed to `Object?` to accept both `CardTheme` and `CardThemeData` so that we won't cause immediate breaking change and make sure rolling is smooth. Once all component themes are normalized, these `Object?` types should be changed to `xxxThemeData`. There's no way to create a dart fix because we can't add a "@deprecated" label for `CardTheme` because `CardTheme` is a new InheritedWidget subclass now. Addresses the "theme normalization" sub project within flutter/flutter#91772
1 parent 85abc1a commit 534adfb

File tree

7 files changed

+154
-22
lines changed

7 files changed

+154
-22
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ class Card extends StatelessWidget {
215215

216216
@override
217217
Widget build(BuildContext context) {
218-
final CardTheme cardTheme = CardTheme.of(context);
218+
final CardThemeData cardTheme = CardTheme.of(context);
219219
final CardThemeData defaults;
220220
if (Theme.of(context).useMaterial3) {
221221
defaults = switch (_variant) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,9 @@ class CardTheme extends InheritedWidget with Diagnosticable {
156156
}
157157

158158
/// The [ThemeData.cardTheme] property of the ambient [Theme].
159-
static CardTheme of(BuildContext context) {
159+
static CardThemeData of(BuildContext context) {
160160
final CardTheme? cardTheme = context.dependOnInheritedWidgetOfExactType<CardTheme>();
161-
return cardTheme ?? Theme.of(context).cardTheme;
161+
return cardTheme?.data ?? Theme.of(context).cardTheme;
162162
}
163163

164164
@override

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,8 @@ class ThemeData with Diagnosticable {
315315
BottomNavigationBarThemeData? bottomNavigationBarTheme,
316316
BottomSheetThemeData? bottomSheetTheme,
317317
ButtonThemeData? buttonTheme,
318-
CardTheme? cardTheme,
318+
// TODO(QuncCccccc): Change the parameter type to CardThemeData
319+
Object? cardTheme,
319320
CheckboxThemeData? checkboxTheme,
320321
ChipThemeData? chipTheme,
321322
DataTableThemeData? dataTableTheme,
@@ -493,7 +494,15 @@ class ThemeData with Diagnosticable {
493494
bottomAppBarTheme ??= const BottomAppBarTheme();
494495
bottomNavigationBarTheme ??= const BottomNavigationBarThemeData();
495496
bottomSheetTheme ??= const BottomSheetThemeData();
496-
cardTheme ??= const CardTheme();
497+
// TODO(QuncCccccc): Clean it up once the type of `cardTheme` is changed to `CardThemeData`
498+
if (cardTheme != null) {
499+
if (cardTheme is CardTheme) {
500+
cardTheme = cardTheme.data;
501+
} else if (cardTheme is! CardThemeData) {
502+
throw ArgumentError('cardTheme must be either a CardThemeData or a CardTheme');
503+
}
504+
}
505+
cardTheme ??= const CardThemeData();
497506
checkboxTheme ??= const CheckboxThemeData();
498507
chipTheme ??= const ChipThemeData();
499508
dataTableTheme ??= const DataTableThemeData();
@@ -594,7 +603,7 @@ class ThemeData with Diagnosticable {
594603
bottomNavigationBarTheme: bottomNavigationBarTheme,
595604
bottomSheetTheme: bottomSheetTheme,
596605
buttonTheme: buttonTheme,
597-
cardTheme: cardTheme,
606+
cardTheme: cardTheme as CardThemeData,
598607
checkboxTheme: checkboxTheme,
599608
chipTheme: chipTheme,
600609
dataTableTheme: dataTableTheme,
@@ -1266,7 +1275,7 @@ class ThemeData with Diagnosticable {
12661275
/// The colors and styles used to render [Card].
12671276
///
12681277
/// This is the value returned from [CardTheme.of].
1269-
final CardTheme cardTheme;
1278+
final CardThemeData cardTheme;
12701279

12711280
/// A theme for customizing the appearance and layout of [Checkbox] widgets.
12721281
final CheckboxThemeData checkboxTheme;
@@ -1466,7 +1475,7 @@ class ThemeData with Diagnosticable {
14661475
BottomNavigationBarThemeData? bottomNavigationBarTheme,
14671476
BottomSheetThemeData? bottomSheetTheme,
14681477
ButtonThemeData? buttonTheme,
1469-
CardTheme? cardTheme,
1478+
Object? cardTheme,
14701479
CheckboxThemeData? checkboxTheme,
14711480
ChipThemeData? chipTheme,
14721481
DataTableThemeData? dataTableTheme,
@@ -1521,6 +1530,15 @@ class ThemeData with Diagnosticable {
15211530
}) {
15221531
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
15231532

1533+
// TODO(QuncCccccc): Clean it up once the type of `cardTheme` is changed to `CardThemeData`
1534+
if (cardTheme != null) {
1535+
if (cardTheme is CardTheme) {
1536+
cardTheme = cardTheme.data;
1537+
} else if (cardTheme is! CardThemeData) {
1538+
throw ArgumentError('cardTheme must be either a CardThemeData or a CardTheme');
1539+
}
1540+
}
1541+
15241542
// TODO(QuncCccccc): Clean this up once the type of `dialogTheme` is changed to `DialogThemeData`
15251543
if (dialogTheme != null) {
15261544
if (dialogTheme is DialogTheme) {
@@ -1585,7 +1603,7 @@ class ThemeData with Diagnosticable {
15851603
bottomNavigationBarTheme: bottomNavigationBarTheme ?? this.bottomNavigationBarTheme,
15861604
bottomSheetTheme: bottomSheetTheme ?? this.bottomSheetTheme,
15871605
buttonTheme: buttonTheme ?? this.buttonTheme,
1588-
cardTheme: cardTheme ?? this.cardTheme,
1606+
cardTheme: cardTheme as CardThemeData? ?? this.cardTheme,
15891607
checkboxTheme: checkboxTheme ?? this.checkboxTheme,
15901608
chipTheme: chipTheme ?? this.chipTheme,
15911609
dataTableTheme: dataTableTheme ?? this.dataTableTheme,
@@ -1778,7 +1796,7 @@ class ThemeData with Diagnosticable {
17781796
bottomNavigationBarTheme: BottomNavigationBarThemeData.lerp(a.bottomNavigationBarTheme, b.bottomNavigationBarTheme, t),
17791797
bottomSheetTheme: BottomSheetThemeData.lerp(a.bottomSheetTheme, b.bottomSheetTheme, t)!,
17801798
buttonTheme: t < 0.5 ? a.buttonTheme : b.buttonTheme,
1781-
cardTheme: CardTheme.lerp(a.cardTheme, b.cardTheme, t),
1799+
cardTheme: CardThemeData.lerp(a.cardTheme, b.cardTheme, t),
17821800
checkboxTheme: CheckboxThemeData.lerp(a.checkboxTheme, b.checkboxTheme, t),
17831801
chipTheme: ChipThemeData.lerp(a.chipTheme, b.chipTheme, t)!,
17841802
dataTableTheme: DataTableThemeData.lerp(a.dataTableTheme, b.dataTableTheme, t),
@@ -2076,7 +2094,7 @@ class ThemeData with Diagnosticable {
20762094
properties.add(DiagnosticsProperty<BottomNavigationBarThemeData>('bottomNavigationBarTheme', bottomNavigationBarTheme, defaultValue: defaultData.bottomNavigationBarTheme, level: DiagnosticLevel.debug));
20772095
properties.add(DiagnosticsProperty<BottomSheetThemeData>('bottomSheetTheme', bottomSheetTheme, defaultValue: defaultData.bottomSheetTheme, level: DiagnosticLevel.debug));
20782096
properties.add(DiagnosticsProperty<ButtonThemeData>('buttonTheme', buttonTheme, level: DiagnosticLevel.debug));
2079-
properties.add(DiagnosticsProperty<CardTheme>('cardTheme', cardTheme, level: DiagnosticLevel.debug));
2097+
properties.add(DiagnosticsProperty<CardThemeData>('cardTheme', cardTheme, level: DiagnosticLevel.debug));
20802098
properties.add(DiagnosticsProperty<CheckboxThemeData>('checkboxTheme', checkboxTheme, defaultValue: defaultData.checkboxTheme, level: DiagnosticLevel.debug));
20812099
properties.add(DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme, level: DiagnosticLevel.debug));
20822100
properties.add(DiagnosticsProperty<DataTableThemeData>('dataTableTheme', dataTableTheme, defaultValue: defaultData.dataTableTheme, level: DiagnosticLevel.debug));

packages/flutter/test/material/card_theme_test.dart

Lines changed: 120 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ void main() {
106106
});
107107

108108
testWidgets('Card uses values from CardTheme', (WidgetTester tester) async {
109-
final CardTheme cardTheme = _cardTheme();
109+
final CardThemeData cardTheme = _cardTheme();
110110

111111
await tester.pumpWidget(MaterialApp(
112112
theme: ThemeData(cardTheme: cardTheme),
@@ -163,7 +163,7 @@ void main() {
163163
});
164164

165165
testWidgets('CardTheme properties take priority over ThemeData properties', (WidgetTester tester) async {
166-
final CardTheme cardTheme = _cardTheme();
166+
final CardThemeData cardTheme = _cardTheme();
167167
final ThemeData themeData = _themeData().copyWith(cardTheme: cardTheme);
168168

169169
await tester.pumpWidget(MaterialApp(
@@ -192,7 +192,7 @@ void main() {
192192
});
193193

194194
testWidgets('Material3 - CardTheme customizes shape', (WidgetTester tester) async {
195-
const CardTheme cardTheme = CardTheme(
195+
const CardThemeData cardTheme = CardThemeData(
196196
color: Colors.white,
197197
shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(7))),
198198
elevation: 1.0,
@@ -220,6 +220,120 @@ void main() {
220220
);
221221
});
222222

223+
testWidgets('Card properties are taken over the theme values', (WidgetTester tester) async {
224+
const Clip themeClipBehavior = Clip.antiAlias;
225+
const Color themeColor = Colors.red;
226+
const Color themeShadowColor = Colors.orange;
227+
const double themeElevation = 10.0;
228+
const EdgeInsets themeMargin = EdgeInsets.all(12.0);
229+
const ShapeBorder themeShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0)));
230+
231+
const Clip clipBehavior = Clip.hardEdge;
232+
const Color color = Colors.yellow;
233+
const Color shadowColor = Colors.green;
234+
const double elevation = 20.0;
235+
const EdgeInsets margin = EdgeInsets.all(18.0);
236+
const ShapeBorder shape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(25.0)));
237+
238+
final ThemeData themeData = ThemeData(
239+
cardTheme: const CardThemeData(
240+
clipBehavior: themeClipBehavior,
241+
color: themeColor,
242+
shadowColor: themeShadowColor,
243+
elevation: themeElevation,
244+
margin: themeMargin,
245+
shape: themeShape,
246+
),
247+
);
248+
249+
await tester.pumpWidget(MaterialApp(
250+
theme: themeData,
251+
home: const Scaffold(
252+
body: Card(
253+
clipBehavior: clipBehavior,
254+
color: color,
255+
shadowColor: shadowColor,
256+
elevation: elevation,
257+
margin: margin,
258+
shape: shape,
259+
child: SizedBox(
260+
width: 200,
261+
height: 200,
262+
),
263+
),
264+
),
265+
)
266+
);
267+
268+
final Padding cardMargin = _getCardPadding(tester);
269+
final Material material = _getCardMaterial(tester);
270+
271+
expect(material.clipBehavior, clipBehavior);
272+
expect(material.color, color);
273+
expect(material.shadowColor, shadowColor);
274+
expect(material.elevation, elevation);
275+
expect(material.shape, shape);
276+
expect(cardMargin.padding, margin);
277+
});
278+
279+
testWidgets('Local CardTheme can override global CardTheme', (WidgetTester tester) async {
280+
const Clip globalClipBehavior = Clip.antiAlias;
281+
const Color globalColor = Colors.red;
282+
const Color globalShadowColor = Colors.orange;
283+
const double globalElevation = 10.0;
284+
const EdgeInsets globalMargin = EdgeInsets.all(12.0);
285+
const ShapeBorder globalShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0)));
286+
287+
const Clip localClipBehavior = Clip.hardEdge;
288+
const Color localColor = Colors.yellow;
289+
const Color localShadowColor = Colors.green;
290+
const double localElevation = 20.0;
291+
const EdgeInsets localMargin = EdgeInsets.all(18.0);
292+
const ShapeBorder localShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(25.0)));
293+
294+
final ThemeData themeData = ThemeData(
295+
cardTheme: const CardThemeData(
296+
clipBehavior: globalClipBehavior,
297+
color: globalColor,
298+
shadowColor: globalShadowColor,
299+
elevation: globalElevation,
300+
margin: globalMargin,
301+
shape: globalShape,
302+
),
303+
);
304+
await tester.pumpWidget(MaterialApp(
305+
theme: themeData,
306+
home: const Scaffold(
307+
body: CardTheme(
308+
data: CardThemeData(
309+
clipBehavior: localClipBehavior,
310+
color: localColor,
311+
shadowColor: localShadowColor,
312+
elevation: localElevation,
313+
margin: localMargin,
314+
shape: localShape,
315+
),
316+
child: Card(
317+
child: SizedBox(
318+
width: 200,
319+
height: 200,
320+
),
321+
),
322+
),
323+
),
324+
));
325+
326+
final Padding cardMargin = _getCardPadding(tester);
327+
final Material material = _getCardMaterial(tester);
328+
329+
expect(material.clipBehavior, localClipBehavior);
330+
expect(material.color, localColor);
331+
expect(material.shadowColor, localShadowColor);
332+
expect(material.elevation, localElevation);
333+
expect(material.shape, localShape);
334+
expect(cardMargin.padding, localMargin);
335+
});
336+
223337
group('Material 2', () {
224338
// These tests are only relevant for Material 2. Once Material 2
225339
// support is deprecated and the APIs are removed, these tests
@@ -262,7 +376,7 @@ void main() {
262376
});
263377

264378
testWidgets('Material2 - CardTheme customizes shape', (WidgetTester tester) async {
265-
const CardTheme cardTheme = CardTheme(
379+
const CardThemeData cardTheme = CardThemeData(
266380
color: Colors.white,
267381
shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(7))),
268382
elevation: 1.0,
@@ -292,8 +406,8 @@ void main() {
292406
});
293407
}
294408

295-
CardTheme _cardTheme() {
296-
return const CardTheme(
409+
CardThemeData _cardTheme() {
410+
return const CardThemeData(
297411
clipBehavior: Clip.antiAlias,
298412
color: Colors.green,
299413
shadowColor: Colors.red,

packages/flutter/test/material/search_anchor_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2707,7 +2707,7 @@ void main() {
27072707
backgroundColor: const Color(0xffffff00)
27082708
),
27092709
),
2710-
cardTheme: const CardTheme(color: Color(0xff00ffff)),
2710+
cardTheme: const CardThemeData(color: Color(0xff00ffff)),
27112711
);
27122712
Widget buildSearchAnchor() {
27132713
return MaterialApp(

packages/flutter/test/material/theme_data_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,7 @@ void main() {
888888
bottomSheetTheme: const BottomSheetThemeData(backgroundColor: Colors.black),
889889
buttonBarTheme: const ButtonBarThemeData(alignment: MainAxisAlignment.start),
890890
buttonTheme: const ButtonThemeData(colorScheme: ColorScheme.dark()),
891-
cardTheme: const CardTheme(color: Colors.black),
891+
cardTheme: const CardThemeData(color: Colors.black),
892892
checkboxTheme: const CheckboxThemeData(),
893893
chipTheme: chipTheme,
894894
dataTableTheme: const DataTableThemeData(),
@@ -1002,7 +1002,7 @@ void main() {
10021002
bottomSheetTheme: const BottomSheetThemeData(backgroundColor: Colors.white),
10031003
buttonBarTheme: const ButtonBarThemeData(alignment: MainAxisAlignment.end),
10041004
buttonTheme: const ButtonThemeData(colorScheme: ColorScheme.light()),
1005-
cardTheme: const CardTheme(color: Colors.white),
1005+
cardTheme: const CardThemeData(color: Colors.white),
10061006
checkboxTheme: const CheckboxThemeData(),
10071007
chipTheme: otherChipTheme,
10081008
dataTableTheme: const DataTableThemeData(),

packages/flutter/test/widgets/inherited_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class ThemedCard extends SingleChildRenderObjectWidget {
6060

6161
@override
6262
RenderPhysicalShape createRenderObject(BuildContext context) {
63-
final CardThemeData cardTheme = CardTheme.of(context).data;
63+
final CardThemeData cardTheme = CardTheme.of(context);
6464

6565
return RenderPhysicalShape(
6666
clipper: ShapeBorderClipper(shape: cardTheme.shape ?? const RoundedRectangleBorder()),
@@ -73,7 +73,7 @@ class ThemedCard extends SingleChildRenderObjectWidget {
7373

7474
@override
7575
void updateRenderObject(BuildContext context, RenderPhysicalShape renderObject) {
76-
final CardThemeData cardTheme = CardTheme.of(context).data;
76+
final CardThemeData cardTheme = CardTheme.of(context);
7777

7878
renderObject
7979
..clipper = ShapeBorderClipper(shape: cardTheme.shape ?? const RoundedRectangleBorder())

0 commit comments

Comments
 (0)