Unverified Commit f65dd3ba authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Fix chip widgets don't the apply provided `iconTheme` (#135751)

fixes [`Chip.iconTheme` does not apply the icon theme](https://github.com/flutter/flutter/issues/111828)

### Description
- Fix chip widgets that don't utilize the provided `iconTheme`.
- Prevent `iconTheme` with just color from overriding the default icon size.
- Add some missing M3 tests for the chip and chip theme properties.

### Code sample

<details>
<summary>expand to view the code sample</summary> 

```dart
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(useMaterial3: true),
      home: const Example(),
    );
  }
}

class Example extends StatefulWidget {
  const Example({super.key});

  @override
  State<Example> createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  final bool _isEnable = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: <Widget>[
            RawChip(
              iconTheme: const IconThemeData(color: Colors.amber),
              avatar: const Icon(Icons.favorite_rounded),
              label: const Text('RawChip'),
              onPressed: () {},
              isEnabled: _isEnable,
            ),
            const Chip(
              iconTheme: IconThemeData(color: Colors.amber),
              avatar: Icon(Icons.favorite_rounded),
              label: Text('Chip'),
              // onDeleted: () {},
            ),
            FilterChip(
              iconTheme: const IconThemeData(color: Colors.amber),
              avatar: const Icon(Icons.favorite_rounded),
              label: const Text('FilterChip'),
              selected: false,
              onSelected: _isEnable ? (bool value) {} : null,
            ),
            InputChip(
              iconTheme: const IconThemeData(color: Colors.amber),
              avatar: const Icon(Icons.favorite_rounded),
              label: const Text('InputChip'),
              isEnabled: _isEnable,
              onPressed: () {},
            ),
            ActionChip(
              iconTheme: const IconThemeData(color: Colors.amber),
              avatar: const Icon(Icons.favorite_rounded),
              label: const Text('ActionChip'),
              onPressed: _isEnable ? () {} : null,
            ),
            ChoiceChip(
              iconTheme: const IconThemeData(color: Colors.amber),
              avatar: const Icon(Icons.favorite_rounded),
              label: const Text('ChoiceChip'),
              selected: false,
              onSelected: _isEnable ? (bool value) {} : null,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        child: const Icon(Icons.add),
      ),
    );
  }
}

```

</details>

### Before
![Screenshot 2023-09-29 at 16 59 39](https://github.com/flutter/flutter/assets/48603081/4bc32032-cff3-4237-812f-86f17ed95337)

### After

![Screenshot 2023-09-29 at 16 55 24](https://github.com/flutter/flutter/assets/48603081/05a1fc52-fb31-4790-a840-18f2e9718241)
parent fad8bda6
......@@ -225,6 +225,7 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
elevation: elevation,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
iconTheme: iconTheme,
);
}
}
......
......@@ -659,6 +659,7 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri
elevation: elevation,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
iconTheme: iconTheme,
);
}
}
......@@ -1219,7 +1220,10 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
final Color? resolvedLabelColor = MaterialStateProperty.resolveAs<Color?>(effectiveLabelStyle.color, materialStates);
final TextStyle resolvedLabelStyle = effectiveLabelStyle.copyWith(color: resolvedLabelColor);
final Widget? avatar = iconTheme != null && hasAvatar
? IconTheme(data: iconTheme, child: widget.avatar!)
? IconTheme.merge(
data: theme.useMaterial3 ? chipDefaults.iconTheme!.merge(iconTheme) : iconTheme,
child: widget.avatar!,
)
: widget.avatar;
Widget result = Material(
......
......@@ -235,6 +235,7 @@ class FilterChip extends StatelessWidget
showCheckmark: showCheckmark,
checkmarkColor: checkmarkColor,
avatarBorder: avatarBorder,
iconTheme: iconTheme,
);
}
}
......
......@@ -236,6 +236,7 @@ class InputChip extends StatelessWidget
checkmarkColor: checkmarkColor,
isEnabled: isEnabled && (onSelected != null || onDeleted != null || onPressed != null),
avatarBorder: avatarBorder,
iconTheme: iconTheme,
);
}
}
......
......@@ -44,6 +44,16 @@ Material getMaterial(WidgetTester tester) {
);
}
IconThemeData getIconData(WidgetTester tester) {
final IconTheme iconTheme = tester.firstWidget(
find.descendant(
of: find.byType(RawChip),
matching: find.byType(IconTheme),
),
);
return iconTheme.data;
}
DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) {
return tester.widget(
find.ancestor(
......@@ -349,4 +359,29 @@ void main() {
await tester.pumpWidget(wrapForChip(child: ActionChip(label: label, clipBehavior: Clip.antiAlias, onPressed: () { })));
checkChipMaterialClipBehavior(tester, Clip.antiAlias);
});
testWidgetsWithLeakTracking('ActionChip uses provided iconTheme', (WidgetTester tester) async {
Widget buildChip({ IconThemeData? iconTheme }) {
return MaterialApp(
home: Material(
child: ActionChip(
iconTheme: iconTheme,
avatar: const Icon(Icons.add),
onPressed: () { },
label: const Text('action chip'),
),
),
);
}
// Test default icon theme.
await tester.pumpWidget(buildChip());
expect(getIconData(tester).color, ThemeData().colorScheme.primary);
// Test provided icon theme.
await tester.pumpWidget(buildChip(iconTheme: const IconThemeData(color: Color(0xff00ff00))));
expect(getIconData(tester).color, const Color(0xff00ff00));
});
}
......@@ -3570,6 +3570,40 @@ void main() {
);
});
testWidgetsWithLeakTracking('Material3 - Chip.iconTheme respects default iconTheme.size', (WidgetTester tester) async {
Widget buildChip({ IconThemeData? iconTheme }) {
return MaterialApp(
theme: ThemeData(useMaterial3: true),
home: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: RawChip(
iconTheme: iconTheme,
avatar: const Icon(Icons.add),
label: const SizedBox(width: 100, height: 100),
onSelected: (bool newValue) { },
),
),
),
),
);
}
await tester.pumpWidget(buildChip(iconTheme: const IconThemeData(color: Color(0xff332211))));
// Icon should have the default chip iconSize.
expect(getIconData(tester).size, 18.0);
expect(getIconData(tester).color, const Color(0xff332211));
// Icon should have the provided iconSize.
await tester.pumpWidget(buildChip(iconTheme: const IconThemeData(color: Color(0xff112233), size: 23.0)));
await tester.pumpAndSettle();
expect(getIconData(tester).size, 23.0);
expect(getIconData(tester).color, const Color(0xff112233));
});
group('Material 2', () {
// These tests are only relevant for Material 2. Once Material 2
// support is deprecated and the APIs are removed, these tests
......
......@@ -24,6 +24,16 @@ Material getMaterial(WidgetTester tester) {
);
}
IconThemeData getIconData(WidgetTester tester) {
final IconTheme iconTheme = tester.firstWidget(
find.descendant(
of: find.byType(RawChip),
matching: find.byType(IconTheme),
),
);
return iconTheme.data;
}
DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) {
return tester.widget(
find.ancestor(
......@@ -575,6 +585,32 @@ void main() {
expect(rawChip.checkmarkColor, checkmarkColor);
});
testWidgetsWithLeakTracking('ChoiceChip uses provided iconTheme', (WidgetTester tester) async {
Widget buildChip({ IconThemeData? iconTheme }) {
return MaterialApp(
home: Material(
child: ChoiceChip(
iconTheme: iconTheme,
avatar: const Icon(Icons.add),
label: const Text('Test'),
selected: false,
onSelected: (bool _) {},
),
),
);
}
// Test default icon theme.
await tester.pumpWidget(buildChip());
expect(getIconData(tester).color, ThemeData().iconTheme.color);
// Test provided icon theme.
await tester.pumpWidget(buildChip(iconTheme: const IconThemeData(color: Color(0xff00ff00))));
expect(getIconData(tester).color, const Color(0xff00ff00));
});
group('Material 2', () {
// These tests are only relevant for Material 2. Once Material 2
// support is deprecated and the APIs are removed, these tests
......
......@@ -104,6 +104,16 @@ Material getMaterial(WidgetTester tester) {
);
}
IconThemeData getIconData(WidgetTester tester) {
final IconTheme iconTheme = tester.firstWidget(
find.descendant(
of: find.byType(RawChip),
matching: find.byType(IconTheme),
),
);
return iconTheme.data;
}
DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) {
return tester.widget(
find.ancestor(
......@@ -687,4 +697,29 @@ void main() {
await tester.pumpAndSettle();
expect(tester.getSize(find.byType(FilterChip)).width, expectedWidth);
});
testWidgetsWithLeakTracking('FilterChip uses provided iconTheme', (WidgetTester tester) async {
Widget buildChip({ IconThemeData? iconTheme }) {
return MaterialApp(
home: Material(
child: FilterChip(
iconTheme: iconTheme,
avatar: const Icon(Icons.add),
label: const Text('FilterChip'),
onSelected: (bool _) {},
),
),
);
}
// Test default icon theme.
await tester.pumpWidget(buildChip());
expect(getIconData(tester).color, ThemeData().iconTheme.color);
// Test provided icon theme.
await tester.pumpWidget(buildChip(iconTheme: const IconThemeData(color: Color(0xff00ff00))));
expect(getIconData(tester).color, const Color(0xff00ff00));
});
}
......@@ -90,6 +90,16 @@ RenderBox getMaterialBox(WidgetTester tester) {
);
}
IconThemeData getIconData(WidgetTester tester) {
final IconTheme iconTheme = tester.firstWidget(
find.descendant(
of: find.byType(RawChip),
matching: find.byType(IconTheme),
),
);
return iconTheme.data;
}
void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) {
final Iterable<Material> materials = tester.widgetList<Material>(find.byType(Material));
// There should be two Material widgets, first Material is from the "_wrapForChip" and
......@@ -377,4 +387,28 @@ void main() {
final RenderBox materialBox = getMaterialBox(tester);
expect(materialBox, paints..path(color: material3ChipDefaults.disabledColor));
});
testWidgetsWithLeakTracking('InputChip uses provided iconTheme', (WidgetTester tester) async {
Widget buildChip({ IconThemeData? iconTheme }) {
return MaterialApp(
home: Material(
child: InputChip(
iconTheme: iconTheme,
avatar: const Icon(Icons.add),
label: const Text('Test'),
),
),
);
}
// Test default icon theme.
await tester.pumpWidget(buildChip());
expect(getIconData(tester).color, ThemeData().iconTheme.color);
// Test provided icon theme.
await tester.pumpWidget(buildChip(iconTheme: const IconThemeData(color: Color(0xff00ff00))));
expect(getIconData(tester).color, const Color(0xff00ff00));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment