diff --git a/packages/flutter/lib/src/material/search.dart b/packages/flutter/lib/src/material/search.dart index 7f6e26235e..f8104d8a8f 100644 --- a/packages/flutter/lib/src/material/search.dart +++ b/packages/flutter/lib/src/material/search.dart @@ -92,9 +92,10 @@ Future showSearch({ /// ## Handling emojis and other complex characters /// {@macro flutter.widgets.editableText.complexCharacters} abstract class SearchDelegate { - - /// Constructor to be called by subclasses which may specify [searchFieldLabel], [keyboardType] and/or - /// [textInputAction]. + /// Constructor to be called by subclasses which may specify + /// [searchFieldLabel], either [searchFieldStyle] or [searchFieldDecorationTheme], + /// [keyboardType] and/or [textInputAction]. Only one of [searchFieldLabel] + /// and [searchFieldDecorationTheme] may be non-null. /// /// {@tool snippet} /// ```dart @@ -124,9 +125,10 @@ abstract class SearchDelegate { SearchDelegate({ this.searchFieldLabel, this.searchFieldStyle, + this.searchFieldDecorationTheme, this.keyboardType, this.textInputAction = TextInputAction.search, - }); + }) : assert(searchFieldStyle == null || searchFieldDecorationTheme == null); /// Suggestions shown in the body of the search page while the user types a /// query into the search field. @@ -205,6 +207,11 @@ abstract class SearchDelegate { primaryIconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey), primaryColorBrightness: Brightness.light, primaryTextTheme: theme.textTheme, + inputDecorationTheme: searchFieldDecorationTheme ?? + InputDecorationTheme( + hintStyle: searchFieldStyle ?? theme.inputDecorationTheme.hintStyle, + border: InputBorder.none, + ), ); } @@ -276,8 +283,18 @@ abstract class SearchDelegate { /// /// If this value is set to null, the value of the ambient [Theme]'s /// [InputDecorationTheme.hintStyle] will be used instead. + /// + /// If this value is not null, [searchFieldDecorationTheme] + /// will be ignored so this can be used. final TextStyle? searchFieldStyle; + /// The [InputDecorationTheme] for the search field, use + /// this if you just want to customize the [TextField] + /// + /// If this value is not null, [searchFieldStyle] + /// will be ignored so this can be used. + final InputDecorationTheme? searchFieldDecorationTheme; + /// The type of action button to use for the keyboard. /// /// Defaults to the default value specified in [TextField]. @@ -482,8 +499,6 @@ class _SearchPageState extends State<_SearchPage> { final ThemeData theme = widget.delegate.appBarTheme(context); final String searchFieldLabel = widget.delegate.searchFieldLabel ?? MaterialLocalizations.of(context).searchFieldLabel; - final TextStyle? searchFieldStyle = widget.delegate.searchFieldStyle - ?? theme.inputDecorationTheme.hintStyle; Widget? body; switch(widget.delegate._currentBody) { case _SearchBody.suggestions: @@ -501,7 +516,8 @@ class _SearchPageState extends State<_SearchPage> { case null: break; } - final String routeName; + + late final String routeName; switch (theme.platform) { case TargetPlatform.iOS: case TargetPlatform.macOS: @@ -519,33 +535,28 @@ class _SearchPageState extends State<_SearchPage> { scopesRoute: true, namesRoute: true, label: routeName, - child: Scaffold( - appBar: AppBar( - backgroundColor: theme.primaryColor, - iconTheme: theme.primaryIconTheme, - textTheme: theme.primaryTextTheme, - brightness: theme.primaryColorBrightness, - leading: widget.delegate.buildLeading(context), - title: TextField( - controller: widget.delegate._queryTextController, - focusNode: focusNode, - style: theme.textTheme.headline6, - textInputAction: widget.delegate.textInputAction, - keyboardType: widget.delegate.keyboardType, - onSubmitted: (String _) { - widget.delegate.showResults(context); - }, - decoration: InputDecoration( - border: InputBorder.none, - hintText: searchFieldLabel, - hintStyle: searchFieldStyle, + child: Theme( + data: theme, + child: Scaffold( + appBar: AppBar( + leading: widget.delegate.buildLeading(context), + title: TextField( + controller: widget.delegate._queryTextController, + focusNode: focusNode, + style: theme.textTheme.title, + textInputAction: widget.delegate.textInputAction, + keyboardType: widget.delegate.keyboardType, + onSubmitted: (String _) { + widget.delegate.showResults(context); + }, + decoration: InputDecoration(hintText: searchFieldLabel), ), + actions: widget.delegate.buildActions(context), + ), + body: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: body, ), - actions: widget.delegate.buildActions(context), - ), - body: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: body, ), ), ); diff --git a/packages/flutter/test/material/search_test.dart b/packages/flutter/test/material/search_test.dart index eff15bafd1..e849e3fbc5 100644 --- a/packages/flutter/test/material/search_test.dart +++ b/packages/flutter/test/material/search_test.dart @@ -122,7 +122,8 @@ void main() { }); testWidgets('Hint text color overridden', (WidgetTester tester) async { - final _TestSearchDelegate delegate = _TestSearchDelegate(); + const String searchHintText = 'Enter search terms'; + final _TestSearchDelegate delegate = _TestSearchDelegate(searchHint: searchHintText); await tester.pumpWidget(TestHomePage( delegate: delegate, @@ -130,9 +131,8 @@ void main() { await tester.tap(find.byTooltip('Search')); await tester.pumpAndSettle(); - final TextField textField = tester.widget(find.byType(TextField)); - final Color hintColor = textField.decoration!.hintStyle!.color!; - expect(hintColor, delegate.hintTextColor); + final Text hintText = tester.widget(find.text(searchHintText)); + expect(hintText.style!.color, _TestSearchDelegate.hintTextColor); }); testWidgets('Requests suggestions', (WidgetTester tester) async { @@ -540,20 +540,18 @@ void main() { }); testWidgets('Custom searchFieldStyle value', (WidgetTester tester) async { - const TextStyle searchStyle = TextStyle(color: Colors.red, fontSize: 3); + const String searchHintText = 'Enter search terms'; + const TextStyle searchFieldStyle = TextStyle(color: Colors.red, fontSize: 3); - final _TestSearchDelegate delegate = _TestSearchDelegate(searchFieldStyle: searchStyle); + final _TestSearchDelegate delegate = _TestSearchDelegate(searchHint: searchHintText, searchFieldStyle: searchFieldStyle); - await tester.pumpWidget( - TestHomePage( - delegate: delegate, - )); + await tester.pumpWidget(TestHomePage(delegate: delegate)); await tester.tap(find.byTooltip('Search')); await tester.pumpAndSettle(); - final TextField textField = tester.widget(find.byType(TextField)); - final TextStyle hintStyle = textField.decoration!.hintStyle!; - expect(hintStyle, delegate.searchFieldStyle); + final Text hintText = tester.widget(find.text(searchHintText)); + expect(hintText.style?.color, delegate.searchFieldStyle?.color); + expect(hintText.style?.fontSize, delegate.searchFieldStyle?.fontSize); }); testWidgets('keyboard show search button by default', (WidgetTester tester) async { @@ -692,6 +690,23 @@ void main() { }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); }); + testWidgets('Custom searchFieldDecorationTheme value', + (WidgetTester tester) async { + const InputDecorationTheme searchFieldDecorationTheme = InputDecorationTheme( + hintStyle: TextStyle(color: _TestSearchDelegate.hintTextColor), + ); + final _TestSearchDelegate delegate = _TestSearchDelegate( + searchFieldDecorationTheme: searchFieldDecorationTheme, + ); + + await tester.pumpWidget(TestHomePage(delegate: delegate)); + await tester.tap(find.byTooltip('Search')); + await tester.pumpAndSettle(); + + final ThemeData? textFieldTheme = Theme.of(tester.element(find.byType(TextField))); + expect(textFieldTheme?.inputDecorationTheme, searchFieldDecorationTheme); + }); + // Regression test for: https://github.com/flutter/flutter/issues/66781 testWidgets('text in search bar contrasts background (light mode)', (WidgetTester tester) async { final ThemeData themeData = ThemeData.light(); @@ -709,8 +724,11 @@ void main() { await tester.tap(find.byTooltip('Search')); await tester.pumpAndSettle(); - final AppBar appBar = tester.widget(find.byType(AppBar)); - expect(appBar.backgroundColor, Colors.white); + final Material appBarBackground = tester.widget(find.descendant( + of: find.byType(AppBar), + matching: find.byType(Material), + )); + expect(appBarBackground.color, Colors.white); final TextField textField = tester.widget(find.byType(TextField)); expect(textField.style!.color, themeData.textTheme.bodyText1!.color); @@ -734,8 +752,11 @@ void main() { await tester.tap(find.byTooltip('Search')); await tester.pumpAndSettle(); - final AppBar appBar = tester.widget(find.byType(AppBar)); - expect(appBar.backgroundColor, themeData.primaryColor); + final Material appBarBackground = tester.widget(find.descendant( + of: find.byType(AppBar), + matching: find.byType(Material), + )); + expect(appBarBackground.color, themeData.primaryColor); final TextField textField = tester.widget(find.byType(TextField)); expect(textField.style!.color, themeData.textTheme.bodyText1!.color); @@ -803,16 +824,22 @@ class _TestSearchDelegate extends SearchDelegate { this.result = 'Result', this.actions = const [], this.defaultAppBarTheme = false, + InputDecorationTheme? searchFieldDecorationTheme, TextStyle? searchFieldStyle, String? searchHint, TextInputAction textInputAction = TextInputAction.search, - }) : super(searchFieldLabel: searchHint, textInputAction: textInputAction, searchFieldStyle: searchFieldStyle); + }) : super( + searchFieldLabel: searchHint, + textInputAction: textInputAction, + searchFieldStyle: searchFieldStyle, + searchFieldDecorationTheme: searchFieldDecorationTheme, + ); final bool defaultAppBarTheme; final String suggestions; final String result; final List actions; - final Color hintTextColor = Colors.green; + static const Color hintTextColor = Colors.green; @override ThemeData appBarTheme(BuildContext context) { @@ -821,7 +848,13 @@ class _TestSearchDelegate extends SearchDelegate { } final ThemeData theme = Theme.of(context)!; return theme.copyWith( - inputDecorationTheme: InputDecorationTheme(hintStyle: TextStyle(color: hintTextColor)), + inputDecorationTheme: searchFieldDecorationTheme ?? + InputDecorationTheme( + hintStyle: searchFieldStyle ?? + const TextStyle( + color: hintTextColor, + ), + ), ); }