Unverified Commit eebb1d6d authored by Henrique Nascimento's avatar Henrique Nascimento Committed by GitHub

Add option for flexible space on material SearchDelegate (#128132)

This pull request introduces the `buildFlexibleSpace` method to the `SearchDelegate` class in the material library. 
It allows users to add a flexible space widget to the `AppBar` in a `_SearchPage`, providing more customization options.

This PR does not fix any specific issue as there are no open issues related to this feature.
parent 7e2f1f5b
...@@ -211,6 +211,15 @@ abstract class SearchDelegate<T> { ...@@ -211,6 +211,15 @@ abstract class SearchDelegate<T> {
/// ///
PreferredSizeWidget? buildBottom(BuildContext context) => null; PreferredSizeWidget? buildBottom(BuildContext context) => null;
/// Widget to display a flexible space in the [AppBar].
///
/// Returns null by default, i.e. a flexible space widget is not included.
///
/// See also:
///
/// * [AppBar.flexibleSpace], the intended use for the return value of this method.
Widget? buildFlexibleSpace(BuildContext context) => null;
/// The theme used to configure the search page. /// The theme used to configure the search page.
/// ///
/// The returned [ThemeData] will be used to wrap the entire search page, /// The returned [ThemeData] will be used to wrap the entire search page,
...@@ -581,11 +590,10 @@ class _SearchPageState<T> extends State<_SearchPage<T>> { ...@@ -581,11 +590,10 @@ class _SearchPageState<T> extends State<_SearchPage<T>> {
style: widget.delegate.searchFieldStyle ?? theme.textTheme.titleLarge, style: widget.delegate.searchFieldStyle ?? theme.textTheme.titleLarge,
textInputAction: widget.delegate.textInputAction, textInputAction: widget.delegate.textInputAction,
keyboardType: widget.delegate.keyboardType, keyboardType: widget.delegate.keyboardType,
onSubmitted: (String _) { onSubmitted: (String _) => widget.delegate.showResults(context),
widget.delegate.showResults(context);
},
decoration: InputDecoration(hintText: searchFieldLabel), decoration: InputDecoration(hintText: searchFieldLabel),
), ),
flexibleSpace: widget.delegate.buildFlexibleSpace(context),
actions: widget.delegate.buildActions(context), actions: widget.delegate.buildActions(context),
bottom: widget.delegate.buildBottom(context), bottom: widget.delegate.buildBottom(context),
), ),
......
...@@ -589,6 +589,163 @@ void main() { ...@@ -589,6 +589,163 @@ void main() {
expect(tester.testTextInput.setClientArgs!['inputAction'], TextInputAction.done.toString()); expect(tester.testTextInput.setClientArgs!['inputAction'], TextInputAction.done.toString());
}); });
testWidgets('Custom flexibleSpace value', (WidgetTester tester) async {
const Widget flexibleSpace = Text('custom flexibleSpace');
final _TestSearchDelegate delegate = _TestSearchDelegate(flexibleSpace: flexibleSpace);
await tester.pumpWidget(TestHomePage(delegate: delegate));
await tester.tap(find.byTooltip('Search'));
await tester.pumpAndSettle();
expect(find.byWidget(flexibleSpace), findsOneWidget);
});
group('contributes semantics with custom flexibleSpace', () {
const Widget flexibleSpace = Text('FlexibleSpace');
TestSemantics buildExpected({ required String routeName }) {
return TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
id: 1,
textDirection: TextDirection.ltr,
children: <TestSemantics>[
TestSemantics(
id: 2,
children: <TestSemantics>[
TestSemantics(
id: 3,
flags: <SemanticsFlag>[
SemanticsFlag.scopesRoute,
SemanticsFlag.namesRoute,
],
label: routeName,
textDirection: TextDirection.ltr,
children: <TestSemantics>[
TestSemantics(
id: 4,
children: <TestSemantics>[
TestSemantics(
id: 6,
children: <TestSemantics>[
TestSemantics(
id: 8,
flags: <SemanticsFlag>[
SemanticsFlag.hasEnabledState,
SemanticsFlag.isButton,
SemanticsFlag.isEnabled,
SemanticsFlag.isFocusable,
],
actions: <SemanticsAction>[SemanticsAction.tap],
tooltip: 'Back',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 9,
flags: <SemanticsFlag>[
SemanticsFlag.isTextField,
SemanticsFlag.isFocused,
SemanticsFlag.isHeader,
if (debugDefaultTargetPlatformOverride != TargetPlatform.iOS &&
debugDefaultTargetPlatformOverride != TargetPlatform.macOS) SemanticsFlag.namesRoute,
],
actions: <SemanticsAction>[
if (debugDefaultTargetPlatformOverride == TargetPlatform.macOS ||
debugDefaultTargetPlatformOverride == TargetPlatform.windows)
SemanticsAction.didGainAccessibilityFocus,
SemanticsAction.tap,
SemanticsAction.setSelection,
SemanticsAction.setText,
SemanticsAction.paste,
],
label: 'Search',
textDirection: TextDirection.ltr,
textSelection: const TextSelection(baseOffset: 0, extentOffset: 0),
),
TestSemantics(
id: 10,
label: 'Bottom',
textDirection: TextDirection.ltr,
),
],
),
TestSemantics(
id: 7,
children: <TestSemantics>[
TestSemantics(
id: 11,
label: 'FlexibleSpace',
textDirection: TextDirection.ltr,
),
],
),
],
),
TestSemantics(
id: 5,
flags: <SemanticsFlag>[
SemanticsFlag.hasEnabledState,
SemanticsFlag.isButton,
SemanticsFlag.isEnabled,
SemanticsFlag.isFocusable,
],
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'Suggestions',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
);
}
testWidgets('includes routeName on Android', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final _TestSearchDelegate delegate = _TestSearchDelegate(flexibleSpace: flexibleSpace);
await tester.pumpWidget(TestHomePage(
delegate: delegate,
));
await tester.tap(find.byTooltip('Search'));
await tester.pumpAndSettle();
expect(semantics, hasSemantics(
buildExpected(routeName: 'Search'),
ignoreId: true,
ignoreRect: true,
ignoreTransform: true,
));
semantics.dispose();
});
testWidgets('does not include routeName', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final _TestSearchDelegate delegate = _TestSearchDelegate(flexibleSpace: flexibleSpace);
await tester.pumpWidget(TestHomePage(
delegate: delegate,
));
await tester.tap(find.byTooltip('Search'));
await tester.pumpAndSettle();
expect(semantics, hasSemantics(
buildExpected(routeName: ''),
ignoreId: true,
ignoreRect: true,
ignoreTransform: true,
));
semantics.dispose();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
});
group('contributes semantics', () { group('contributes semantics', () {
TestSemantics buildExpected({ required String routeName }) { TestSemantics buildExpected({ required String routeName }) {
return TestSemantics.root( return TestSemantics.root(
...@@ -749,10 +906,10 @@ void main() { ...@@ -749,10 +906,10 @@ void main() {
await tester.tap(find.byTooltip('Search')); await tester.tap(find.byTooltip('Search'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
final Material appBarBackground = tester.widget<Material>(find.descendant( final Material appBarBackground = tester.widgetList<Material>(find.descendant(
of: find.byType(AppBar), of: find.byType(AppBar),
matching: find.byType(Material), matching: find.byType(Material),
)); )).first;
expect(appBarBackground.color, Colors.white); expect(appBarBackground.color, Colors.white);
final TextField textField = tester.widget<TextField>(find.byType(TextField)); final TextField textField = tester.widget<TextField>(find.byType(TextField));
...@@ -777,10 +934,10 @@ void main() { ...@@ -777,10 +934,10 @@ void main() {
await tester.tap(find.byTooltip('Search')); await tester.tap(find.byTooltip('Search'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
final Material appBarBackground = tester.widget<Material>(find.descendant( final Material appBarBackground = tester.widgetList<Material>(find.descendant(
of: find.byType(AppBar), of: find.byType(AppBar),
matching: find.byType(Material), matching: find.byType(Material),
)); )).first;
expect(appBarBackground.color, themeData.primaryColor); expect(appBarBackground.color, themeData.primaryColor);
final TextField textField = tester.widget<TextField>(find.byType(TextField)); final TextField textField = tester.widget<TextField>(find.byType(TextField));
...@@ -789,9 +946,9 @@ void main() { ...@@ -789,9 +946,9 @@ void main() {
}); });
// Regression test for: https://github.com/flutter/flutter/issues/78144 // Regression test for: https://github.com/flutter/flutter/issues/78144
testWidgets('`Leading` and `Actions` nullable test', (WidgetTester tester) async { testWidgets('`Leading`, `Actions` and `FlexibleSpace` nullable test', (WidgetTester tester) async {
// The search delegate page is displayed with no issues // The search delegate page is displayed with no issues
// even with a null return values for [buildLeading] and [buildActions]. // even with a null return values for [buildLeading], [buildActions] and [flexibleSpace].
final _TestEmptySearchDelegate delegate = _TestEmptySearchDelegate(); final _TestEmptySearchDelegate delegate = _TestEmptySearchDelegate();
final List<String> selectedResults = <String>[]; final List<String> selectedResults = <String>[];
...@@ -980,6 +1137,7 @@ class _TestSearchDelegate extends SearchDelegate<String> { ...@@ -980,6 +1137,7 @@ class _TestSearchDelegate extends SearchDelegate<String> {
this.suggestions = 'Suggestions', this.suggestions = 'Suggestions',
this.result = 'Result', this.result = 'Result',
this.actions = const <Widget>[], this.actions = const <Widget>[],
this.flexibleSpace ,
this.defaultAppBarTheme = false, this.defaultAppBarTheme = false,
super.searchFieldDecorationTheme, super.searchFieldDecorationTheme,
super.searchFieldStyle, super.searchFieldStyle,
...@@ -993,6 +1151,7 @@ class _TestSearchDelegate extends SearchDelegate<String> { ...@@ -993,6 +1151,7 @@ class _TestSearchDelegate extends SearchDelegate<String> {
final String suggestions; final String suggestions;
final String result; final String result;
final List<Widget> actions; final List<Widget> actions;
final Widget? flexibleSpace;
static const Color hintTextColor = Colors.green; static const Color hintTextColor = Colors.green;
@override @override
...@@ -1048,6 +1207,11 @@ class _TestSearchDelegate extends SearchDelegate<String> { ...@@ -1048,6 +1207,11 @@ class _TestSearchDelegate extends SearchDelegate<String> {
return actions; return actions;
} }
@override
Widget? buildFlexibleSpace(BuildContext context) {
return flexibleSpace;
}
@override @override
PreferredSizeWidget buildBottom(BuildContext context) { PreferredSizeWidget buildBottom(BuildContext context) {
return const PreferredSize( return const PreferredSize(
......
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