From 7c72a089e19dbc7868c85575c7b8edcf2afedc8a Mon Sep 17 00:00:00 2001 From: Taha Tesser Date: Thu, 4 Apr 2024 22:20:30 +0300 Subject: [PATCH] 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
expand to view the code sample ```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), ), ), ), ], ), ), ), ), ), ); } } ```
| Before | After | | --------------- | --------------- | | | | --- .../lib/src/material/input_decorator.dart | 39 +++-- .../test/material/input_decorator_test.dart | 144 ++++++++++++++++++ 2 files changed, 169 insertions(+), 14 deletions(-) diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 208066a0822..c5638e6be88 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -2055,15 +2055,24 @@ class _InputDecoratorState extends State 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 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 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), - iconSize: iconSize, - ), + style: IconButton.styleFrom( + foregroundColor: _getPrefixIconColor(inputDecorationTheme, iconButtonTheme, defaults), + iconSize: iconSize, + ).merge(iconButtonTheme.style), ), child: Semantics( child: decoration.prefixIcon, @@ -2355,15 +2366,15 @@ class _InputDecoratorState extends State 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), - iconSize: iconSize, - ), + style: IconButton.styleFrom( + foregroundColor: _getSuffixIconColor(inputDecorationTheme, iconButtonTheme, defaults), + iconSize: iconSize, + ).merge(iconButtonTheme.style), ), child: Semantics( child: decoration.suffixIcon, diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index 80966c3dd3a..e2a6c7fd70b 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -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(prefixIconMaterial); + expect(material.color, backgroundColor); + expect(material.shape, iconButtonStyle.shape?.resolve({})); + final Finder suffixIconMaterial = find.descendant( + of: find.widgetWithIcon(IconButton, suffixIcon), + matching: find.byType(Material), + ); + material = tester.widget(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((Set 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((Set 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