From 18b239fbde3add31618daca8db457c43353dbd62 Mon Sep 17 00:00:00 2001 From: Tirth Date: Wed, 29 Nov 2023 00:08:55 +0530 Subject: [PATCH] Added keyboardType & textInputAction props to SearchBar, SearchAnchor & SearchAnchor.bar (#138553) Added `keyboardType` & `textInputAction` props to `SearchBar`, `SearchAnchor` & `SearchAnchor.bar` Fixes #138483 --- .../lib/src/material/search_anchor.dart | 46 ++++- .../flutter/lib/src/material/text_field.dart | 2 + .../test/material/search_anchor_test.dart | 184 ++++++++++++++++++ 3 files changed, 230 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/material/search_anchor.dart b/packages/flutter/lib/src/material/search_anchor.dart index 0fd34c6e7d..212774273c 100644 --- a/packages/flutter/lib/src/material/search_anchor.dart +++ b/packages/flutter/lib/src/material/search_anchor.dart @@ -131,6 +131,8 @@ class SearchAnchor extends StatefulWidget { this.viewOnSubmitted, required this.builder, required this.suggestionsBuilder, + this.textInputAction, + this.keyboardType, }); /// Create a [SearchAnchor] that has a [SearchBar] which opens a search view. @@ -174,7 +176,9 @@ class SearchAnchor extends StatefulWidget { bool? isFullScreen, SearchController searchController, TextCapitalization textCapitalization, - required SuggestionsBuilder suggestionsBuilder + required SuggestionsBuilder suggestionsBuilder, + TextInputAction? textInputAction, + TextInputType? keyboardType, }) = _SearchAnchorWithSearchBar; /// Whether the search view grows to fill the entire screen when the @@ -323,6 +327,14 @@ class SearchAnchor extends StatefulWidget { /// To get a different layout, use [viewBuilder] to override. final SuggestionsBuilder suggestionsBuilder; + /// {@macro flutter.widgets.TextField.textInputAction} + final TextInputAction? textInputAction; + + /// The type of action button to use for the keyboard. + /// + /// Defaults to the default value specified in [TextField]. + final TextInputType? keyboardType; + @override State createState() => _SearchAnchorState(); } @@ -396,6 +408,8 @@ class _SearchAnchorState extends State { suggestionsBuilder: widget.suggestionsBuilder, textCapitalization: widget.textCapitalization, capturedThemes: InheritedTheme.capture(from: context, to: navigator.context), + textInputAction: widget.textInputAction, + keyboardType: widget.keyboardType, )); } @@ -469,6 +483,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { required this.searchController, required this.suggestionsBuilder, required this.capturedThemes, + this.textInputAction, + this.keyboardType, }); final ValueChanged? viewOnChanged; @@ -494,6 +510,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { final SearchController searchController; final SuggestionsBuilder suggestionsBuilder; final CapturedThemes capturedThemes; + final TextInputAction? textInputAction; + final TextInputType? keyboardType; @override Color? get barrierColor => Colors.transparent; @@ -637,6 +655,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { searchController: searchController, suggestionsBuilder: suggestionsBuilder, textCapitalization: textCapitalization, + textInputAction: textInputAction, + keyboardType: keyboardType, ), ), ); @@ -673,6 +693,8 @@ class _ViewContent extends StatefulWidget { required this.viewRect, required this.searchController, required this.suggestionsBuilder, + this.textInputAction, + this.keyboardType, }); final ValueChanged? viewOnChanged; @@ -697,6 +719,8 @@ class _ViewContent extends StatefulWidget { final Rect viewRect; final SearchController searchController; final SuggestionsBuilder suggestionsBuilder; + final TextInputAction? textInputAction; + final TextInputType? keyboardType; @override State<_ViewContent> createState() => _ViewContentState(); @@ -876,6 +900,8 @@ class _ViewContentState extends State<_ViewContent> { }, onSubmitted: widget.viewOnSubmitted, textCapitalization: widget.textCapitalization, + textInputAction: widget.textInputAction, + keyboardType: widget.keyboardType, ), ), ), @@ -939,7 +965,9 @@ class _SearchAnchorWithSearchBar extends SearchAnchor { super.textCapitalization, ValueChanged? onChanged, ValueChanged? onSubmitted, - required super.suggestionsBuilder + required super.suggestionsBuilder, + super.textInputAction, + super.keyboardType, }) : super( viewHintText: viewHintText ?? barHintText, headerTextStyle: viewHeaderTextStyle, @@ -970,6 +998,8 @@ class _SearchAnchorWithSearchBar extends SearchAnchor { leading: barLeading ?? const Icon(Icons.search), trailing: barTrailing, textCapitalization: textCapitalization, + textInputAction: textInputAction, + keyboardType: keyboardType, ); } ); @@ -1086,6 +1116,8 @@ class SearchBar extends StatefulWidget { this.hintStyle, this.textCapitalization, this.autoFocus = false, + this.textInputAction, + this.keyboardType, }); /// Controls the text being edited in the search bar's text field. @@ -1210,6 +1242,14 @@ class SearchBar extends StatefulWidget { /// {@macro flutter.widgets.editableText.autofocus} final bool autoFocus; + /// {@macro flutter.widgets.TextField.textInputAction} + final TextInputAction? textInputAction; + + /// The type of action button to use for the keyboard. + /// + /// Defaults to the default value specified in [TextField]. + final TextInputType? keyboardType; + @override State createState() => _SearchBarState(); } @@ -1349,6 +1389,8 @@ class _SearchBarState extends State { isDense: true, )), textCapitalization: effectiveTextCapitalization, + textInputAction: widget.textInputAction, + keyboardType: widget.keyboardType, ), ), ), diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index f508d70abb..f024372824 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -419,10 +419,12 @@ class TextField extends StatefulWidget { /// {@macro flutter.widgets.editableText.keyboardType} final TextInputType keyboardType; + /// {@template flutter.widgets.TextField.textInputAction} /// The type of action button to use for the keyboard. /// /// Defaults to [TextInputAction.newline] if [keyboardType] is /// [TextInputType.multiline] and [TextInputAction.done] otherwise. + /// {@endtemplate} final TextInputAction? textInputAction; /// {@macro flutter.widgets.editableText.textCapitalization} diff --git a/packages/flutter/test/material/search_anchor_test.dart b/packages/flutter/test/material/search_anchor_test.dart index b4c5100f40..cc9c55f458 100644 --- a/packages/flutter/test/material/search_anchor_test.dart +++ b/packages/flutter/test/material/search_anchor_test.dart @@ -2762,6 +2762,190 @@ void main() { ).first); expect(suggestionMaterial.color, localTheme.cardTheme.color); }); + + testWidgetsWithLeakTracking('SearchBar respects keyboardType property', (WidgetTester tester) async { + Widget buildSearchBar(TextInputType keyboardType) { + return MaterialApp( + home: Center( + child: Material( + child: SearchBar( + keyboardType: keyboardType, + ), + ), + ), + ); + } + await tester.pumpWidget(buildSearchBar(TextInputType.number)); + await tester.pump(); + TextField textField = tester.widget(find.byType(TextField)); + expect(textField.keyboardType, TextInputType.number); + + await tester.pumpWidget(buildSearchBar(TextInputType.phone)); + await tester.pump(); + textField = tester.widget(find.byType(TextField)); + expect(textField.keyboardType, TextInputType.phone); + }); + + testWidgetsWithLeakTracking('SearchAnchor respects keyboardType property', (WidgetTester tester) async { + Widget buildSearchAnchor(TextInputType keyboardType) { + return MaterialApp( + home: Center( + child: Material( + child: SearchAnchor( + keyboardType: keyboardType, + builder: (BuildContext context, SearchController controller) { + return IconButton( + icon: const Icon(Icons.ac_unit), + onPressed: () { + controller.openView(); + }, + ); + }, + suggestionsBuilder: (BuildContext context, SearchController controller) { + return []; + }, + ), + ), + ), + ); + } + await tester.pumpWidget(buildSearchAnchor(TextInputType.number)); + await tester.pump(); + await tester.tap(find.widgetWithIcon(IconButton, Icons.ac_unit)); + await tester.pumpAndSettle(); + TextField textField = tester.widget(find.byType(TextField)); + expect(textField.keyboardType, TextInputType.number); + await tester.tap(find.widgetWithIcon(IconButton, Icons.arrow_back)); + await tester.pump(); + + await tester.pumpWidget(buildSearchAnchor(TextInputType.phone)); + await tester.pump(); + await tester.tap(find.widgetWithIcon(IconButton, Icons.ac_unit)); + await tester.pumpAndSettle(); + textField = tester.widget(find.byType(TextField)); + expect(textField.keyboardType, TextInputType.phone); + }); + + testWidgetsWithLeakTracking('SearchAnchor.bar respects keyboardType property', (WidgetTester tester) async { + Widget buildSearchAnchor(TextInputType keyboardType) { + return MaterialApp( + home: Center( + child: Material( + child: SearchAnchor.bar( + keyboardType: keyboardType, + suggestionsBuilder: (BuildContext context, SearchController controller) { + return []; + }, + ), + ), + ), + ); + } + await tester.pumpWidget(buildSearchAnchor(TextInputType.number)); + await tester.pump(); + await tester.tap(find.byType(SearchBar)); // Open search view. + await tester.pumpAndSettle(); + final Finder textFieldFinder = find.descendant(of: findViewContent(), matching: find.byType(TextField)); + final TextField textFieldInView = tester.widget(textFieldFinder); + expect(textFieldInView.keyboardType, TextInputType.number); + // Close search view. + await tester.tap(find.widgetWithIcon(IconButton, Icons.arrow_back)); + await tester.pumpAndSettle(); + final TextField textField = tester.widget(find.byType(TextField)); + expect(textField.keyboardType, TextInputType.number); + }); + + testWidgetsWithLeakTracking('SearchBar respects textInputAction property', (WidgetTester tester) async { + Widget buildSearchBar(TextInputAction textInputAction) { + return MaterialApp( + home: Center( + child: Material( + child: SearchBar( + textInputAction: textInputAction, + ), + ), + ), + ); + } + await tester.pumpWidget(buildSearchBar(TextInputAction.previous)); + await tester.pump(); + TextField textField = tester.widget(find.byType(TextField)); + expect(textField.textInputAction, TextInputAction.previous); + + await tester.pumpWidget(buildSearchBar(TextInputAction.send)); + await tester.pump(); + textField = tester.widget(find.byType(TextField)); + expect(textField.textInputAction, TextInputAction.send); + }); + + testWidgetsWithLeakTracking('SearchAnchor respects textInputAction property', (WidgetTester tester) async { + Widget buildSearchAnchor(TextInputAction textInputAction) { + return MaterialApp( + home: Center( + child: Material( + child: SearchAnchor( + textInputAction: textInputAction, + builder: (BuildContext context, SearchController controller) { + return IconButton( + icon: const Icon(Icons.ac_unit), + onPressed: () { + controller.openView(); + }, + ); + }, + suggestionsBuilder: (BuildContext context, SearchController controller) { + return []; + }, + ), + ), + ), + ); + } + await tester.pumpWidget(buildSearchAnchor(TextInputAction.previous)); + await tester.pump(); + await tester.tap(find.widgetWithIcon(IconButton, Icons.ac_unit)); + await tester.pumpAndSettle(); + TextField textField = tester.widget(find.byType(TextField)); + expect(textField.textInputAction, TextInputAction.previous); + await tester.tap(find.widgetWithIcon(IconButton, Icons.arrow_back)); + await tester.pump(); + + await tester.pumpWidget(buildSearchAnchor(TextInputAction.send)); + await tester.pump(); + await tester.tap(find.widgetWithIcon(IconButton, Icons.ac_unit)); + await tester.pumpAndSettle(); + textField = tester.widget(find.byType(TextField)); + expect(textField.textInputAction, TextInputAction.send); + }); + + testWidgetsWithLeakTracking('SearchAnchor.bar respects textInputAction property', (WidgetTester tester) async { + Widget buildSearchAnchor(TextInputAction textInputAction) { + return MaterialApp( + home: Center( + child: Material( + child: SearchAnchor.bar( + textInputAction: textInputAction, + suggestionsBuilder: (BuildContext context, SearchController controller) { + return []; + }, + ), + ), + ), + ); + } + await tester.pumpWidget(buildSearchAnchor(TextInputAction.previous)); + await tester.pump(); + await tester.tap(find.byType(SearchBar)); // Open search view. + await tester.pumpAndSettle(); + final Finder textFieldFinder = find.descendant(of: findViewContent(), matching: find.byType(TextField)); + final TextField textFieldInView = tester.widget(textFieldFinder); + expect(textFieldInView.textInputAction, TextInputAction.previous); + // Close search view. + await tester.tap(find.widgetWithIcon(IconButton, Icons.arrow_back)); + await tester.pumpAndSettle(); + final TextField textField = tester.widget(find.byType(TextField)); + expect(textField.textInputAction, TextInputAction.previous); + }); } Future checkSearchBarDefaults(WidgetTester tester, ColorScheme colorScheme, Material material) async {