diff --git a/packages/flutter/lib/src/material/search.dart b/packages/flutter/lib/src/material/search.dart index dd3dbdb288b..6c0602267f2 100644 --- a/packages/flutter/lib/src/material/search.dart +++ b/packages/flutter/lib/src/material/search.dart @@ -344,6 +344,15 @@ abstract class SearchDelegate { ..pop(result); } + /// Closes the search page and returns to the underlying route whitout result. + void _pop(BuildContext context) { + _currentBody = null; + _focusNode?.unfocus(); + Navigator.of(context) + ..popUntil((Route route) => route == _route) + ..pop(); + } + /// The hint text that is shown in the search field when it is empty. /// /// If this value is set to null, the value of @@ -506,7 +515,16 @@ class _SearchPage extends StatefulWidget { class _SearchPageState extends State<_SearchPage> { // This node is owned, but not hosted by, the search page. Hosting is done by // the text field. - FocusNode focusNode = FocusNode(); + late final FocusNode focusNode = FocusNode( + onKeyEvent: (FocusNode node, KeyEvent event) { + // When the user presses the escape key, close the search page. + if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.escape) { + widget.delegate._pop(context); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + ); @override void initState() { diff --git a/packages/flutter/test/material/search_test.dart b/packages/flutter/test/material/search_test.dart index 8ee6da85bcf..f2cf9d8ddb5 100644 --- a/packages/flutter/test/material/search_test.dart +++ b/packages/flutter/test/material/search_test.dart @@ -137,6 +137,47 @@ void main() { expect(find.text('Suggestions'), findsOneWidget); }); + testWidgets('Can close search with escape button and return null', (WidgetTester tester) async { + final _TestSearchDelegate delegate = _TestSearchDelegate(); + addTearDown(() => delegate.dispose()); + final List selectedResults = []; + + await tester.pumpWidget(TestHomePage(delegate: delegate, results: selectedResults)); + + // We are on the homepage. + expect(find.text('HomeBody'), findsOneWidget); + expect(find.text('HomeTitle'), findsOneWidget); + expect(find.text('Suggestions'), findsNothing); + + // Open search. + await tester.tap(find.byTooltip('Search')); + await tester.pumpAndSettle(); + + expect(find.text('HomeBody'), findsNothing); + expect(find.text('HomeTitle'), findsNothing); + expect(find.text('Suggestions'), findsOneWidget); + expect(find.text('Bottom'), findsOneWidget); + + // Simulate escape button. + await simulateKeyDownEvent(LogicalKeyboardKey.escape, platform: 'windows'); + await tester.pumpAndSettle(); + + expect(selectedResults, [null]); + + // We are on the homepage again. + expect(find.text('HomeBody'), findsOneWidget); + expect(find.text('HomeTitle'), findsOneWidget); + expect(find.text('Suggestions'), findsNothing); + + // Open search again. + await tester.tap(find.byTooltip('Search')); + await tester.pumpAndSettle(); + + expect(find.text('HomeBody'), findsNothing); + expect(find.text('HomeTitle'), findsNothing); + expect(find.text('Suggestions'), findsOneWidget); + }); + testWidgets('Hint text color overridden', (WidgetTester tester) async { const String searchHintText = 'Enter search terms'; final _TestSearchDelegate delegate = _TestSearchDelegate(searchHint: searchHintText);