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:
parent
1d89ae3f65
commit
7c72a089e1
@ -2055,15 +2055,24 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
|||||||
?? MaterialStateProperty.resolveAs(defaults.iconColor!, materialState);
|
?? 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)
|
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);
|
?? 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)
|
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);
|
?? MaterialStateProperty.resolveAs(defaults.suffixIconColor!, materialState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2189,6 +2198,8 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
|||||||
final ThemeData themeData = Theme.of(context);
|
final ThemeData themeData = Theme.of(context);
|
||||||
final InputDecorationTheme defaults =
|
final InputDecorationTheme defaults =
|
||||||
Theme.of(context).useMaterial3 ? _InputDecoratorDefaultsM3(context) : _InputDecoratorDefaultsM2(context);
|
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 TextStyle labelStyle = _getInlineLabelStyle(themeData, defaults);
|
||||||
final TextBaseline textBaseline = labelStyle.textBaseline!;
|
final TextBaseline textBaseline = labelStyle.textBaseline!;
|
||||||
@ -2320,15 +2331,15 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
|||||||
),
|
),
|
||||||
child: IconTheme.merge(
|
child: IconTheme.merge(
|
||||||
data: IconThemeData(
|
data: IconThemeData(
|
||||||
color: _getPrefixIconColor(themeData, defaults),
|
color: _getPrefixIconColor(inputDecorationTheme, iconButtonTheme, defaults),
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
),
|
),
|
||||||
child: IconButtonTheme(
|
child: IconButtonTheme(
|
||||||
data: IconButtonThemeData(
|
data: IconButtonThemeData(
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
foregroundColor: _getPrefixIconColor(themeData, defaults),
|
foregroundColor: _getPrefixIconColor(inputDecorationTheme, iconButtonTheme, defaults),
|
||||||
iconSize: iconSize,
|
iconSize: iconSize,
|
||||||
),
|
).merge(iconButtonTheme.style),
|
||||||
),
|
),
|
||||||
child: Semantics(
|
child: Semantics(
|
||||||
child: decoration.prefixIcon,
|
child: decoration.prefixIcon,
|
||||||
@ -2355,15 +2366,15 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
|||||||
),
|
),
|
||||||
child: IconTheme.merge(
|
child: IconTheme.merge(
|
||||||
data: IconThemeData(
|
data: IconThemeData(
|
||||||
color: _getSuffixIconColor(themeData, defaults),
|
color: _getSuffixIconColor(inputDecorationTheme, iconButtonTheme, defaults),
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
),
|
),
|
||||||
child: IconButtonTheme(
|
child: IconButtonTheme(
|
||||||
data: IconButtonThemeData(
|
data: IconButtonThemeData(
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
foregroundColor: _getSuffixIconColor(themeData, defaults),
|
foregroundColor: _getSuffixIconColor(inputDecorationTheme, iconButtonTheme, defaults),
|
||||||
iconSize: iconSize,
|
iconSize: iconSize,
|
||||||
),
|
).merge(iconButtonTheme.style),
|
||||||
),
|
),
|
||||||
child: Semantics(
|
child: Semantics(
|
||||||
child: decoration.suffixIcon,
|
child: decoration.suffixIcon,
|
||||||
|
@ -44,6 +44,7 @@ Widget buildInputDecorator({
|
|||||||
InputDecoration decoration = const InputDecoration(),
|
InputDecoration decoration = const InputDecoration(),
|
||||||
ThemeData? theme,
|
ThemeData? theme,
|
||||||
InputDecorationTheme? inputDecorationTheme,
|
InputDecorationTheme? inputDecorationTheme,
|
||||||
|
IconButtonThemeData? iconButtonTheme,
|
||||||
TextDirection textDirection = TextDirection.ltr,
|
TextDirection textDirection = TextDirection.ltr,
|
||||||
bool expands = false,
|
bool expands = false,
|
||||||
bool isEmpty = false,
|
bool isEmpty = false,
|
||||||
@ -81,6 +82,7 @@ Widget buildInputDecorator({
|
|||||||
return Theme(
|
return Theme(
|
||||||
data: (theme ?? Theme.of(context)).copyWith(
|
data: (theme ?? Theme.of(context)).copyWith(
|
||||||
inputDecorationTheme: inputDecorationTheme,
|
inputDecorationTheme: inputDecorationTheme,
|
||||||
|
iconButtonTheme: iconButtonTheme,
|
||||||
visualDensity: visualDensity,
|
visualDensity: visualDensity,
|
||||||
),
|
),
|
||||||
child: Align(
|
child: Align(
|
||||||
@ -4965,6 +4967,148 @@ void main() {
|
|||||||
expect(merged.constraints, overrideTheme.constraints);
|
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', () {
|
group('Material2', () {
|
||||||
// These tests are only relevant for Material 2. Once Material 2
|
// These tests are only relevant for Material 2. Once Material 2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user