From 293c2c2f105573fbbaf0bfaab4c53580a43c0820 Mon Sep 17 00:00:00 2001 From: Arthur Monteiro Alves Melo Date: Fri, 25 Apr 2025 15:03:12 -0300 Subject: [PATCH] Close the search by pressing escape (#167648) Close [showSearch()](https://api.flutter.dev/flutter/material/showSearch.html) by pressing escape button. feat #148146 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Tong Mu --- packages/flutter/lib/src/material/search.dart | 20 ++++++++- .../flutter/test/material/search_test.dart | 41 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) 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);