Skip to content

Commit

Permalink
Added keyboardType & textInputAction props to SearchBar, SearchAnchor…
Browse files Browse the repository at this point in the history
… & SearchAnchor.bar (#138553)

Added `keyboardType` & `textInputAction` props to `SearchBar`, `SearchAnchor` & `SearchAnchor.bar`

Fixes #138483
  • Loading branch information
piedcipher authored Nov 28, 2023
1 parent 89edac4 commit 18b239f
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 2 deletions.
46 changes: 44 additions & 2 deletions packages/flutter/lib/src/material/search_anchor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<SearchAnchor> createState() => _SearchAnchorState();
}
Expand Down Expand Up @@ -396,6 +408,8 @@ class _SearchAnchorState extends State<SearchAnchor> {
suggestionsBuilder: widget.suggestionsBuilder,
textCapitalization: widget.textCapitalization,
capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
textInputAction: widget.textInputAction,
keyboardType: widget.keyboardType,
));
}

Expand Down Expand Up @@ -469,6 +483,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
required this.searchController,
required this.suggestionsBuilder,
required this.capturedThemes,
this.textInputAction,
this.keyboardType,
});

final ValueChanged<String>? viewOnChanged;
Expand All @@ -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;
Expand Down Expand Up @@ -637,6 +655,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
searchController: searchController,
suggestionsBuilder: suggestionsBuilder,
textCapitalization: textCapitalization,
textInputAction: textInputAction,
keyboardType: keyboardType,
),
),
);
Expand Down Expand Up @@ -673,6 +693,8 @@ class _ViewContent extends StatefulWidget {
required this.viewRect,
required this.searchController,
required this.suggestionsBuilder,
this.textInputAction,
this.keyboardType,
});

final ValueChanged<String>? viewOnChanged;
Expand All @@ -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();
Expand Down Expand Up @@ -876,6 +900,8 @@ class _ViewContentState extends State<_ViewContent> {
},
onSubmitted: widget.viewOnSubmitted,
textCapitalization: widget.textCapitalization,
textInputAction: widget.textInputAction,
keyboardType: widget.keyboardType,
),
),
),
Expand Down Expand Up @@ -939,7 +965,9 @@ class _SearchAnchorWithSearchBar extends SearchAnchor {
super.textCapitalization,
ValueChanged<String>? onChanged,
ValueChanged<String>? onSubmitted,
required super.suggestionsBuilder
required super.suggestionsBuilder,
super.textInputAction,
super.keyboardType,
}) : super(
viewHintText: viewHintText ?? barHintText,
headerTextStyle: viewHeaderTextStyle,
Expand Down Expand Up @@ -970,6 +998,8 @@ class _SearchAnchorWithSearchBar extends SearchAnchor {
leading: barLeading ?? const Icon(Icons.search),
trailing: barTrailing,
textCapitalization: textCapitalization,
textInputAction: textInputAction,
keyboardType: keyboardType,
);
}
);
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<SearchBar> createState() => _SearchBarState();
}
Expand Down Expand Up @@ -1349,6 +1389,8 @@ class _SearchBarState extends State<SearchBar> {
isDense: true,
)),
textCapitalization: effectiveTextCapitalization,
textInputAction: widget.textInputAction,
keyboardType: widget.keyboardType,
),
),
),
Expand Down
2 changes: 2 additions & 0 deletions packages/flutter/lib/src/material/text_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
184 changes: 184 additions & 0 deletions packages/flutter/test/material/search_anchor_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Widget>[];
},
),
),
),
);
}
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 <Widget>[];
},
),
),
),
);
}
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<TextField>(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 <Widget>[];
},
),
),
),
);
}
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 <Widget>[];
},
),
),
),
);
}
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<TextField>(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<void> checkSearchBarDefaults(WidgetTester tester, ColorScheme colorScheme, Material material) async {
Expand Down

0 comments on commit 18b239f

Please sign in to comment.