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.
This commit is contained in:
parent
7e2f1f5bf6
commit
eebb1d6ddf
@ -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>> {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 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> {
|
|||||||
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> {
|
|||||||
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> {
|
|||||||
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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user