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.
This commit is contained in:
parent
659ed55a83
commit
6673fe5cb1
@ -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 {
|
|||||||
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 {
|
|||||||
/// {@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> {
|
|||||||
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> {
|
|||||||
|
|
||||||
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> {
|
|||||||
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> {
|
|||||||
),
|
),
|
||||||
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> {
|
|||||||
|
|
||||||
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 {
|
|||||||
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> {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user