Fix InputDecorator suffix and prefix IconButtons ignore IconButtonTheme (#145473)

fixes [DropdownMenu TrailingIcon can't be styled through providing an IconButtonTheme](https://github.com/flutter/flutter/issues/145081)

### 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,
      home: Scaffold(
        body: Center(
          child: IconButtonTheme(
            data: IconButtonThemeData(
              style: IconButton.styleFrom(
                foregroundColor: const Color(0xffff0000),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(10.0),
                ),
              ),
            ),
            child: SizedBox(
              width: 300,
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Text('IconButton'),
                  IconButton(onPressed: () {}, icon: const Icon(Icons.search)),
                  const Text('TextField'),
                  TextField(
                    decoration: InputDecoration(
                      prefixIcon: IconButton(
                        onPressed: () {},
                        icon: const Icon(Icons.search),
                      ),
                      suffixIcon: IconButton(
                        onPressed: () {},
                        icon: const Icon(Icons.search),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}
```

</details>

| Before | After |
| --------------- | --------------- |
| <img src="https://github.com/flutter/flutter/assets/48603081/69b5966b-c95d-4934-b867-3262d1377f70"  /> | <img src="https://github.com/flutter/flutter/assets/48603081/0064db2b-0379-4424-a5bf-39bdc5441fe8" /> |
This commit is contained in:
Taha Tesser 2024-04-04 22:20:30 +03:00 committed by GitHub
parent 1d89ae3f65
commit 7c72a089e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 169 additions and 14 deletions

View File

@ -2055,15 +2055,24 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
?? MaterialStateProperty.resolveAs(defaults.iconColor!, materialState);
}
Color _getPrefixIconColor(ThemeData themeData, InputDecorationTheme defaults) {
Color _getPrefixIconColor(
InputDecorationTheme inputDecorationTheme,
IconButtonThemeData iconButtonTheme,
InputDecorationTheme defaults) {
return MaterialStateProperty.resolveAs(decoration.prefixIconColor, materialState)
?? MaterialStateProperty.resolveAs(themeData.inputDecorationTheme.prefixIconColor, materialState)
?? MaterialStateProperty.resolveAs(inputDecorationTheme.prefixIconColor, materialState)
?? iconButtonTheme.style?.foregroundColor?.resolve(materialState)
?? MaterialStateProperty.resolveAs(defaults.prefixIconColor!, materialState);
}
Color _getSuffixIconColor(ThemeData themeData, InputDecorationTheme defaults) {
Color _getSuffixIconColor(
InputDecorationTheme inputDecorationTheme,
IconButtonThemeData iconButtonTheme,
InputDecorationTheme defaults,
) {
return MaterialStateProperty.resolveAs(decoration.suffixIconColor, materialState)
?? MaterialStateProperty.resolveAs(themeData.inputDecorationTheme.suffixIconColor, materialState)
?? MaterialStateProperty.resolveAs(inputDecorationTheme.suffixIconColor, materialState)
?? iconButtonTheme.style?.foregroundColor?.resolve(materialState)
?? MaterialStateProperty.resolveAs(defaults.suffixIconColor!, materialState);
}
@ -2189,6 +2198,8 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
final ThemeData themeData = Theme.of(context);
final InputDecorationTheme defaults =
Theme.of(context).useMaterial3 ? _InputDecoratorDefaultsM3(context) : _InputDecoratorDefaultsM2(context);
final InputDecorationTheme inputDecorationTheme = themeData.inputDecorationTheme;
final IconButtonThemeData iconButtonTheme = IconButtonTheme.of(context);
final TextStyle labelStyle = _getInlineLabelStyle(themeData, defaults);
final TextBaseline textBaseline = labelStyle.textBaseline!;
@ -2320,15 +2331,15 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
),
child: IconTheme.merge(
data: IconThemeData(
color: _getPrefixIconColor(themeData, defaults),
color: _getPrefixIconColor(inputDecorationTheme, iconButtonTheme, defaults),
size: iconSize,
),
child: IconButtonTheme(
data: IconButtonThemeData(
style: IconButton.styleFrom(
foregroundColor: _getPrefixIconColor(themeData, defaults),
foregroundColor: _getPrefixIconColor(inputDecorationTheme, iconButtonTheme, defaults),
iconSize: iconSize,
),
).merge(iconButtonTheme.style),
),
child: Semantics(
child: decoration.prefixIcon,
@ -2355,15 +2366,15 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
),
child: IconTheme.merge(
data: IconThemeData(
color: _getSuffixIconColor(themeData, defaults),
color: _getSuffixIconColor(inputDecorationTheme, iconButtonTheme, defaults),
size: iconSize,
),
child: IconButtonTheme(
data: IconButtonThemeData(
style: IconButton.styleFrom(
foregroundColor: _getSuffixIconColor(themeData, defaults),
foregroundColor: _getSuffixIconColor(inputDecorationTheme, iconButtonTheme, defaults),
iconSize: iconSize,
),
).merge(iconButtonTheme.style),
),
child: Semantics(
child: decoration.suffixIcon,

View File

@ -44,6 +44,7 @@ Widget buildInputDecorator({
InputDecoration decoration = const InputDecoration(),
ThemeData? theme,
InputDecorationTheme? inputDecorationTheme,
IconButtonThemeData? iconButtonTheme,
TextDirection textDirection = TextDirection.ltr,
bool expands = false,
bool isEmpty = false,
@ -81,6 +82,7 @@ Widget buildInputDecorator({
return Theme(
data: (theme ?? Theme.of(context)).copyWith(
inputDecorationTheme: inputDecorationTheme,
iconButtonTheme: iconButtonTheme,
visualDensity: visualDensity,
),
child: Align(
@ -4965,6 +4967,148 @@ void main() {
expect(merged.constraints, overrideTheme.constraints);
});
testWidgets('Prefix and suffix IconButtons inherit IconButtonTheme', (WidgetTester tester) async {
const IconData prefixIcon = Icons.person;
const IconData suffixIcon = Icons.search;
const Color backgroundColor = Color(0xffff0000);
const Color foregroundColor = Color(0xff00ff00);
final OutlinedBorder shape =RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
);
final ButtonStyle iconButtonStyle = IconButton.styleFrom(
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
shape: shape,
);
await tester.pumpWidget(
IconButtonTheme(
data: IconButtonThemeData(style: iconButtonStyle),
child: buildInputDecorator(
decoration: InputDecoration(
prefixIcon: IconButton(
onPressed: () {},
icon: const Icon(prefixIcon),
),
suffixIcon: IconButton(
onPressed: () {},
icon: const Icon(suffixIcon),
),
),
),
),
);
final Finder prefixIconMaterial = find.descendant(
of: find.widgetWithIcon(IconButton, prefixIcon),
matching: find.byType(Material),
);
Material material = tester.widget<Material>(prefixIconMaterial);
expect(material.color, backgroundColor);
expect(material.shape, iconButtonStyle.shape?.resolve(<WidgetState>{}));
final Finder suffixIconMaterial = find.descendant(
of: find.widgetWithIcon(IconButton, suffixIcon),
matching: find.byType(Material),
);
material = tester.widget<Material>(suffixIconMaterial);
expect(material.color, backgroundColor);
expect(material.shape, shape);
expect(getIconStyle(tester, prefixIcon)?.color, foregroundColor);
expect(getIconStyle(tester, suffixIcon)?.color, foregroundColor);
});
testWidgets('Prefix IconButton color respects IconButtonTheme foreground color states', (WidgetTester tester) async {
const IconData prefixIcon = Icons.person;
const Color iconErrorColor = Color(0xffff0000);
const Color iconColor = Color(0xff00ff00);
final ButtonStyle iconButtonStyle = ButtonStyle(
foregroundColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
if (states.contains(MaterialState.error)) {
return iconErrorColor;
}
return iconColor;
}),
);
// Test the prefix IconButton color when there is an error text.
await tester.pumpWidget(
buildInputDecorator(
iconButtonTheme: IconButtonThemeData(style: iconButtonStyle),
decoration: InputDecoration(
errorText: 'error',
prefixIcon: IconButton(
onPressed: () {},
icon: const Icon(prefixIcon),
),
),
),
);
expect(getIconStyle(tester, prefixIcon)?.color, iconErrorColor);
// Test the prefix IconButton color when there is no error text.
await tester.pumpWidget(
buildInputDecorator(
iconButtonTheme: IconButtonThemeData(style: iconButtonStyle),
decoration: InputDecoration(
prefixIcon: IconButton(
onPressed: () {},
icon: const Icon(prefixIcon),
),
),
),
);
await tester.pump();
expect(getIconStyle(tester, prefixIcon)?.color, iconColor);
});
testWidgets('Suffix IconButton color respects IconButtonTheme foreground color states', (WidgetTester tester) async {
const IconData suffixIcon = Icons.search;
const Color iconErrorColor = Color(0xffff0000);
const Color iconColor = Color(0xff00ff00);
final ButtonStyle iconButtonStyle = ButtonStyle(
foregroundColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
if (states.contains(MaterialState.error)) {
return iconErrorColor;
}
return iconColor;
}),
);
// Test the prefix IconButton color when there is an error text.
await tester.pumpWidget(
buildInputDecorator(
iconButtonTheme: IconButtonThemeData(style: iconButtonStyle),
decoration: InputDecoration(
errorText: 'error',
suffixIcon: IconButton(
onPressed: () {},
icon: const Icon(suffixIcon),
),
),
),
);
expect(getIconStyle(tester, suffixIcon)?.color, iconErrorColor);
// Test the prefix IconButton color when there is no error text.
await tester.pumpWidget(
buildInputDecorator(
iconButtonTheme: IconButtonThemeData(style: iconButtonStyle),
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: () {},
icon: const Icon(suffixIcon),
),
),
),
);
await tester.pump();
expect(getIconStyle(tester, suffixIcon)?.color, iconColor);
});
group('Material2', () {
// These tests are only relevant for Material 2. Once Material 2