Unverified Commit 6673fe5c authored by Qun Cheng's avatar Qun Cheng Committed by GitHub

Add `onSubmitted` and `onChanged` for `SearchAnchor` and `SearchAnchor.bar` (#136840)

Fixes #130687 and #132915

This PR is to add two properties: `viewOnChanged` and `viewOnSubmitted` to `SearchAnchor` and `SearchAnchor.bar` so we can control the search bar on the search view.
parent 659ed55a
......@@ -127,6 +127,8 @@ class SearchAnchor extends StatefulWidget {
this.dividerColor,
this.viewConstraints,
this.textCapitalization,
this.viewOnChanged,
this.viewOnSubmitted,
required this.builder,
required this.suggestionsBuilder,
});
......@@ -147,6 +149,8 @@ class SearchAnchor extends StatefulWidget {
Iterable<Widget>? barTrailing,
String? barHintText,
GestureTapCallback? onTap,
ValueChanged<String>? onSubmitted,
ValueChanged<String>? onChanged,
MaterialStateProperty<double?>? barElevation,
MaterialStateProperty<Color?>? barBackgroundColor,
MaterialStateProperty<Color?>? barOverlayColor,
......@@ -289,6 +293,24 @@ class SearchAnchor extends StatefulWidget {
/// {@macro flutter.widgets.editableText.textCapitalization}
final TextCapitalization? textCapitalization;
/// Called each time the user modifies the search view's text field.
///
/// See also:
///
/// * [viewOnSubmitted], which is called when the user indicates that they
/// are done editing the search view's text field.
final ValueChanged<String>? viewOnChanged;
/// Called when the user indicates that they are done editing the text in the
/// text field of a search view. Typically this is called when the user presses
/// the enter key.
///
/// See also:
///
/// * [viewOnChanged], which is called when the user modifies the text field
/// of the search view.
final ValueChanged<String>? viewOnSubmitted;
/// Called to create a widget which can open a search view route when it is tapped.
///
/// The widget returned by this builder is faded out when it is tapped.
......@@ -351,6 +373,8 @@ class _SearchAnchorState extends State<SearchAnchor> {
void _openView() {
final NavigatorState navigator = Navigator.of(context);
navigator.push(_SearchViewRoute(
viewOnChanged: widget.viewOnChanged,
viewOnSubmitted: widget.viewOnSubmitted,
viewLeading: widget.viewLeading,
viewTrailing: widget.viewTrailing,
viewHintText: widget.viewHintText,
......@@ -422,6 +446,8 @@ class _SearchAnchorState extends State<SearchAnchor> {
class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
_SearchViewRoute({
this.viewOnChanged,
this.viewOnSubmitted,
this.toggleVisibility,
this.textDirection,
this.viewBuilder,
......@@ -445,6 +471,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
required this.capturedThemes,
});
final ValueChanged<String>? viewOnChanged;
final ValueChanged<String>? viewOnSubmitted;
final ValueGetter<bool>? toggleVisibility;
final TextDirection? textDirection;
final ViewBuilder? viewBuilder;
......@@ -587,6 +615,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
),
child: capturedThemes.wrap(
_ViewContent(
viewOnChanged: viewOnChanged,
viewOnSubmitted: viewOnSubmitted,
viewLeading: viewLeading,
viewTrailing: viewTrailing,
viewHintText: viewHintText,
......@@ -621,6 +651,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
class _ViewContent extends StatefulWidget {
const _ViewContent({
this.viewOnChanged,
this.viewOnSubmitted,
this.viewBuilder,
this.viewLeading,
this.viewTrailing,
......@@ -643,6 +675,8 @@ class _ViewContent extends StatefulWidget {
required this.suggestionsBuilder,
});
final ValueChanged<String>? viewOnChanged;
final ValueChanged<String>? viewOnSubmitted;
final ViewBuilder? viewBuilder;
final Widget? viewLeading;
final Iterable<Widget>? viewTrailing;
......@@ -842,9 +876,11 @@ class _ViewContentState extends State<_ViewContent> {
textStyle: MaterialStatePropertyAll<TextStyle?>(effectiveTextStyle),
hintStyle: MaterialStatePropertyAll<TextStyle?>(effectiveHintStyle),
controller: _controller,
onChanged: (_) {
onChanged: (String value) {
widget.viewOnChanged?.call(value);
updateSuggestions();
},
onSubmitted: widget.viewOnSubmitted,
textCapitalization: widget.textCapitalization,
),
),
......@@ -907,11 +943,15 @@ class _SearchAnchorWithSearchBar extends SearchAnchor {
super.isFullScreen,
super.searchController,
super.textCapitalization,
ValueChanged<String>? onChanged,
ValueChanged<String>? onSubmitted,
required super.suggestionsBuilder
}) : super(
viewHintText: viewHintText ?? barHintText,
headerTextStyle: viewHeaderTextStyle,
headerHintStyle: viewHeaderHintStyle,
viewOnSubmitted: onSubmitted,
viewOnChanged: onChanged,
builder: (BuildContext context, SearchController controller) {
return SearchBar(
constraints: constraints,
......@@ -920,9 +960,10 @@ class _SearchAnchorWithSearchBar extends SearchAnchor {
controller.openView();
onTap?.call();
},
onChanged: (_) {
onChanged: (String value) {
controller.openView();
},
onSubmitted: onSubmitted,
hintText: barHintText,
hintStyle: barHintStyle,
textStyle: barTextStyle,
......
......@@ -839,6 +839,65 @@ void main() {
expect(textField.textCapitalization, TextCapitalization.none);
});
testWidgetsWithLeakTracking('SearchAnchor respects viewOnChanged and viewOnSubmitted properties', (WidgetTester tester) async {
final SearchController controller = SearchController();
addTearDown(controller.dispose);
int onChangedCalled = 0;
int onSubmittedCalled = 0;
await tester.pumpWidget(MaterialApp(
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: Material(
child: SearchAnchor(
searchController: controller,
viewOnChanged: (String value) {
setState(() {
onChangedCalled = onChangedCalled + 1;
});
},
viewOnSubmitted: (String value) {
setState(() {
onSubmittedCalled = onSubmittedCalled + 1;
});
controller.closeView(value);
},
builder: (BuildContext context, SearchController controller) {
return SearchBar(
onTap: () {
if (!controller.isOpen) {
controller.openView();
}
},
);
},
suggestionsBuilder: (BuildContext context, SearchController controller) {
return <Widget>[];
},
),
),
);
}
),
));
await tester.tap(find.byType(SearchBar)); // Open search view.
await tester.pumpAndSettle();
expect(controller.isOpen, true);
final Finder barOnView = find.descendant(
of: findViewContent(),
matching: find.byType(TextField)
);
await tester.enterText(barOnView, 'a');
expect(onChangedCalled, 1);
await tester.enterText(barOnView, 'abc');
expect(onChangedCalled, 2);
await tester.testTextInput.receiveAction(TextInputAction.done);
expect(onSubmittedCalled, 1);
expect(controller.isOpen, false);
});
testWidgetsWithLeakTracking('SearchAnchor.bar respects textCapitalization property', (WidgetTester tester) async {
Widget buildSearchAnchor(TextCapitalization textCapitalization) {
return MaterialApp(
......@@ -868,6 +927,59 @@ void main() {
expect(textField.textCapitalization, TextCapitalization.characters);
});
testWidgetsWithLeakTracking('SearchAnchor.bar respects onChanged and onSubmitted properties', (WidgetTester tester) async {
final SearchController controller = SearchController();
addTearDown(controller.dispose);
int onChangedCalled = 0;
int onSubmittedCalled = 0;
await tester.pumpWidget(MaterialApp(
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: Material(
child: SearchAnchor.bar(
searchController: controller,
onSubmitted: (String value) {
setState(() {
onSubmittedCalled = onSubmittedCalled + 1;
});
controller.closeView(value);
},
onChanged: (String value) {
setState(() {
onChangedCalled = onChangedCalled + 1;
});
},
suggestionsBuilder: (BuildContext context, SearchController controller) {
return <Widget>[];
},
),
),
);
}
),
));
await tester.tap(find.byType(SearchBar)); // Open search view.
await tester.pumpAndSettle();
expect(controller.isOpen, true);
final Finder barOnView = find.descendant(
of: findViewContent(),
matching: find.byType(TextField)
);
await tester.enterText(barOnView, 'a');
expect(onChangedCalled, 1);
await tester.enterText(barOnView, 'abc');
expect(onChangedCalled, 2);
await tester.testTextInput.receiveAction(TextInputAction.done);
expect(onSubmittedCalled, 1);
expect(controller.isOpen, false);
await tester.testTextInput.receiveAction(TextInputAction.done);
expect(onSubmittedCalled, 2);
});
testWidgetsWithLeakTracking('hintStyle can override textStyle for hintText', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment