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 { ...@@ -127,6 +127,8 @@ class SearchAnchor extends StatefulWidget {
this.dividerColor, this.dividerColor,
this.viewConstraints, this.viewConstraints,
this.textCapitalization, this.textCapitalization,
this.viewOnChanged,
this.viewOnSubmitted,
required this.builder, required this.builder,
required this.suggestionsBuilder, required this.suggestionsBuilder,
}); });
...@@ -147,6 +149,8 @@ class SearchAnchor extends StatefulWidget { ...@@ -147,6 +149,8 @@ class SearchAnchor extends StatefulWidget {
Iterable<Widget>? barTrailing, Iterable<Widget>? barTrailing,
String? barHintText, String? barHintText,
GestureTapCallback? onTap, GestureTapCallback? onTap,
ValueChanged<String>? onSubmitted,
ValueChanged<String>? onChanged,
MaterialStateProperty<double?>? barElevation, MaterialStateProperty<double?>? barElevation,
MaterialStateProperty<Color?>? barBackgroundColor, MaterialStateProperty<Color?>? barBackgroundColor,
MaterialStateProperty<Color?>? barOverlayColor, MaterialStateProperty<Color?>? barOverlayColor,
...@@ -289,6 +293,24 @@ class SearchAnchor extends StatefulWidget { ...@@ -289,6 +293,24 @@ class SearchAnchor extends StatefulWidget {
/// {@macro flutter.widgets.editableText.textCapitalization} /// {@macro flutter.widgets.editableText.textCapitalization}
final TextCapitalization? 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. /// 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. /// The widget returned by this builder is faded out when it is tapped.
...@@ -351,6 +373,8 @@ class _SearchAnchorState extends State<SearchAnchor> { ...@@ -351,6 +373,8 @@ class _SearchAnchorState extends State<SearchAnchor> {
void _openView() { void _openView() {
final NavigatorState navigator = Navigator.of(context); final NavigatorState navigator = Navigator.of(context);
navigator.push(_SearchViewRoute( navigator.push(_SearchViewRoute(
viewOnChanged: widget.viewOnChanged,
viewOnSubmitted: widget.viewOnSubmitted,
viewLeading: widget.viewLeading, viewLeading: widget.viewLeading,
viewTrailing: widget.viewTrailing, viewTrailing: widget.viewTrailing,
viewHintText: widget.viewHintText, viewHintText: widget.viewHintText,
...@@ -422,6 +446,8 @@ class _SearchAnchorState extends State<SearchAnchor> { ...@@ -422,6 +446,8 @@ class _SearchAnchorState extends State<SearchAnchor> {
class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
_SearchViewRoute({ _SearchViewRoute({
this.viewOnChanged,
this.viewOnSubmitted,
this.toggleVisibility, this.toggleVisibility,
this.textDirection, this.textDirection,
this.viewBuilder, this.viewBuilder,
...@@ -445,6 +471,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { ...@@ -445,6 +471,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
required this.capturedThemes, required this.capturedThemes,
}); });
final ValueChanged<String>? viewOnChanged;
final ValueChanged<String>? viewOnSubmitted;
final ValueGetter<bool>? toggleVisibility; final ValueGetter<bool>? toggleVisibility;
final TextDirection? textDirection; final TextDirection? textDirection;
final ViewBuilder? viewBuilder; final ViewBuilder? viewBuilder;
...@@ -587,6 +615,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { ...@@ -587,6 +615,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
), ),
child: capturedThemes.wrap( child: capturedThemes.wrap(
_ViewContent( _ViewContent(
viewOnChanged: viewOnChanged,
viewOnSubmitted: viewOnSubmitted,
viewLeading: viewLeading, viewLeading: viewLeading,
viewTrailing: viewTrailing, viewTrailing: viewTrailing,
viewHintText: viewHintText, viewHintText: viewHintText,
...@@ -621,6 +651,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { ...@@ -621,6 +651,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
class _ViewContent extends StatefulWidget { class _ViewContent extends StatefulWidget {
const _ViewContent({ const _ViewContent({
this.viewOnChanged,
this.viewOnSubmitted,
this.viewBuilder, this.viewBuilder,
this.viewLeading, this.viewLeading,
this.viewTrailing, this.viewTrailing,
...@@ -643,6 +675,8 @@ class _ViewContent extends StatefulWidget { ...@@ -643,6 +675,8 @@ class _ViewContent extends StatefulWidget {
required this.suggestionsBuilder, required this.suggestionsBuilder,
}); });
final ValueChanged<String>? viewOnChanged;
final ValueChanged<String>? viewOnSubmitted;
final ViewBuilder? viewBuilder; final ViewBuilder? viewBuilder;
final Widget? viewLeading; final Widget? viewLeading;
final Iterable<Widget>? viewTrailing; final Iterable<Widget>? viewTrailing;
...@@ -842,9 +876,11 @@ class _ViewContentState extends State<_ViewContent> { ...@@ -842,9 +876,11 @@ class _ViewContentState extends State<_ViewContent> {
textStyle: MaterialStatePropertyAll<TextStyle?>(effectiveTextStyle), textStyle: MaterialStatePropertyAll<TextStyle?>(effectiveTextStyle),
hintStyle: MaterialStatePropertyAll<TextStyle?>(effectiveHintStyle), hintStyle: MaterialStatePropertyAll<TextStyle?>(effectiveHintStyle),
controller: _controller, controller: _controller,
onChanged: (_) { onChanged: (String value) {
widget.viewOnChanged?.call(value);
updateSuggestions(); updateSuggestions();
}, },
onSubmitted: widget.viewOnSubmitted,
textCapitalization: widget.textCapitalization, textCapitalization: widget.textCapitalization,
), ),
), ),
...@@ -907,11 +943,15 @@ class _SearchAnchorWithSearchBar extends SearchAnchor { ...@@ -907,11 +943,15 @@ class _SearchAnchorWithSearchBar extends SearchAnchor {
super.isFullScreen, super.isFullScreen,
super.searchController, super.searchController,
super.textCapitalization, super.textCapitalization,
ValueChanged<String>? onChanged,
ValueChanged<String>? onSubmitted,
required super.suggestionsBuilder required super.suggestionsBuilder
}) : super( }) : super(
viewHintText: viewHintText ?? barHintText, viewHintText: viewHintText ?? barHintText,
headerTextStyle: viewHeaderTextStyle, headerTextStyle: viewHeaderTextStyle,
headerHintStyle: viewHeaderHintStyle, headerHintStyle: viewHeaderHintStyle,
viewOnSubmitted: onSubmitted,
viewOnChanged: onChanged,
builder: (BuildContext context, SearchController controller) { builder: (BuildContext context, SearchController controller) {
return SearchBar( return SearchBar(
constraints: constraints, constraints: constraints,
...@@ -920,9 +960,10 @@ class _SearchAnchorWithSearchBar extends SearchAnchor { ...@@ -920,9 +960,10 @@ class _SearchAnchorWithSearchBar extends SearchAnchor {
controller.openView(); controller.openView();
onTap?.call(); onTap?.call();
}, },
onChanged: (_) { onChanged: (String value) {
controller.openView(); controller.openView();
}, },
onSubmitted: onSubmitted,
hintText: barHintText, hintText: barHintText,
hintStyle: barHintStyle, hintStyle: barHintStyle,
textStyle: barTextStyle, textStyle: barTextStyle,
......
...@@ -839,6 +839,65 @@ void main() { ...@@ -839,6 +839,65 @@ void main() {
expect(textField.textCapitalization, TextCapitalization.none); 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 { testWidgetsWithLeakTracking('SearchAnchor.bar respects textCapitalization property', (WidgetTester tester) async {
Widget buildSearchAnchor(TextCapitalization textCapitalization) { Widget buildSearchAnchor(TextCapitalization textCapitalization) {
return MaterialApp( return MaterialApp(
...@@ -868,6 +927,59 @@ void main() { ...@@ -868,6 +927,59 @@ void main() {
expect(textField.textCapitalization, TextCapitalization.characters); 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 { testWidgetsWithLeakTracking('hintStyle can override textStyle for hintText', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( 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