From 4c6929d4c92d8b289e7dd85b2bd3f83d49ed6fe2 Mon Sep 17 00:00:00 2001 From: Qun Cheng <36861262+QuncCccccc@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:23:20 -0700 Subject: [PATCH] Dismiss the docked search view when the window size is changed (#125071) --- .../lib/src/material/search_anchor.dart | 58 +++++--------- .../test/material/search_anchor_test.dart | 78 +++++++++++++++++++ 2 files changed, 98 insertions(+), 38 deletions(-) diff --git a/packages/flutter/lib/src/material/search_anchor.dart b/packages/flutter/lib/src/material/search_anchor.dart index 70ab74c72b..7c90db1dd0 100644 --- a/packages/flutter/lib/src/material/search_anchor.dart +++ b/packages/flutter/lib/src/material/search_anchor.dart @@ -67,6 +67,11 @@ typedef ViewBuilder = Widget Function(Iterable suggestions); /// If [builder] returns an Icon, or any un-tappable widgets, we don't have /// to explicitly call [SearchController.openView]. /// +/// The search view route will be popped if the window size is changed and the +/// search view route is not in full-screen mode. However, if the search view route +/// is in full-screen mode, changing the window size, such as rotating a mobile +/// device from portrait mode to landscape mode, will not close the search view. +/// /// {@tool dartpad} /// This example shows how to use an IconButton to open a search view in a [SearchAnchor]. /// It also shows how to use [SearchController] to open or close the search view route. @@ -286,6 +291,7 @@ class SearchAnchor extends StatefulWidget { } class _SearchAnchorState extends State { + Size? _screenSize; bool _anchorIsVisible = true; final GlobalKey _anchorKey = GlobalKey(); bool get _viewIsOpen => !_anchorIsVisible; @@ -301,6 +307,18 @@ class _SearchAnchorState extends State { _searchController._attach(this); } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final Size updatedScreenSize = MediaQuery.of(context).size; + if (_screenSize != null && _screenSize != updatedScreenSize) { + if (_searchController.isOpen && !getShowFullScreenView()) { + _closeView(null); + } + } + _screenSize = updatedScreenSize; + } + @override void dispose() { super.dispose(); @@ -672,45 +690,9 @@ class _ViewContentState extends State<_ViewContent> { result = widget.suggestionsBuilder(context, _controller); final Size updatedScreenSize = MediaQuery.of(context).size; - if (_screenSize != updatedScreenSize) { + if (_screenSize != updatedScreenSize && widget.showFullScreenView) { _screenSize = updatedScreenSize; - setState(() { - final Rect anchorRect = widget.getRect() ?? _viewRect; - final BoxConstraints constraints = widget.viewConstraints ?? widget.viewTheme.constraints ?? widget.viewDefaults.constraints!; - final double viewWidth = clampDouble(anchorRect.width, constraints.minWidth, constraints.maxWidth); - final double viewHeight = clampDouble(_screenSize!.height * 2 / 3, constraints.minHeight, constraints.maxHeight); - final Size updatedViewSize = Size(viewWidth, viewHeight); - - switch (Directionality.of(context)) { - case TextDirection.ltr: - final double viewLeftToScreenRight = _screenSize!.width - anchorRect.left; - final double viewTopToScreenBottom = _screenSize!.height - anchorRect.top; - - // Make sure the search view doesn't go off the screen when the screen - // size is changed. If the search view doesn't fit, move the top-left - // corner of the view to fit the window. If the window is smaller than - // the view, then we resize the view to fit the window. - Offset topLeft = anchorRect.topLeft; - if (viewLeftToScreenRight < viewWidth) { - topLeft = Offset(_screenSize!.width - math.min(viewWidth, _screenSize!.width), anchorRect.top); - } - if (viewTopToScreenBottom < viewHeight) { - topLeft = Offset(topLeft.dx, _screenSize!.height - math.min(viewHeight, _screenSize!.height)); - } - _viewRect = topLeft & updatedViewSize; - return; - case TextDirection.rtl: - final double viewTopToScreenBottom = _screenSize!.height - anchorRect.top; - Offset topLeft = Offset( - math.max(anchorRect.right - viewWidth, 0.0), - anchorRect.top, - ); - if (viewTopToScreenBottom < viewHeight) { - topLeft = Offset(topLeft.dx, _screenSize!.height - math.min(viewHeight, _screenSize!.height)); - } - _viewRect = topLeft & updatedViewSize; - } - }); + _viewRect = Offset.zero & _screenSize!; } } diff --git a/packages/flutter/test/material/search_anchor_test.dart b/packages/flutter/test/material/search_anchor_test.dart index a59d674042..e05b9aaaa0 100644 --- a/packages/flutter/test/material/search_anchor_test.dart +++ b/packages/flutter/test/material/search_anchor_test.dart @@ -1560,6 +1560,84 @@ void main() { final Rect searchViewRectRTL = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first); expect(searchViewRectRTL, equals(const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0))); }); + + testWidgets('Docked search view route is popped if the window size changes', (WidgetTester tester) async { + addTearDown(tester.view.reset); + tester.view.physicalSize = const Size(500.0, 600.0); + tester.view.devicePixelRatio = 1.0; + + await tester.pumpWidget(MaterialApp( + home: Material( + child: SearchAnchor( + isFullScreen: false, + builder: (BuildContext context, SearchController controller) { + return Align( + alignment: Alignment.bottomRight, + child: IconButton( + icon: const Icon(Icons.search), + onPressed: () { + controller.openView(); + }, + ), + ); + }, + suggestionsBuilder: (BuildContext context, SearchController controller) { + return []; + }, + ), + ), + )); + + // Open the search view + await tester.tap(find.byIcon(Icons.search)); + await tester.pumpAndSettle(); + expect(find.byIcon(Icons.arrow_back), findsOneWidget); + + // Change window size + tester.view.physicalSize = const Size(250.0, 200.0); + tester.view.devicePixelRatio = 1.0; + await tester.pumpAndSettle(); + expect(find.byIcon(Icons.arrow_back), findsNothing); + }); + + testWidgets('Full-screen search view route should stay if the window size changes', (WidgetTester tester) async { + addTearDown(tester.view.reset); + tester.view.physicalSize = const Size(500.0, 600.0); + tester.view.devicePixelRatio = 1.0; + + await tester.pumpWidget(MaterialApp( + home: Material( + child: SearchAnchor( + isFullScreen: true, + builder: (BuildContext context, SearchController controller) { + return Align( + alignment: Alignment.bottomRight, + child: IconButton( + icon: const Icon(Icons.search), + onPressed: () { + controller.openView(); + }, + ), + ); + }, + suggestionsBuilder: (BuildContext context, SearchController controller) { + return []; + }, + ), + ), + )); + + // Open a full-screen search view + await tester.tap(find.byIcon(Icons.search)); + await tester.pumpAndSettle(); + expect(find.byIcon(Icons.arrow_back), findsOneWidget); + + // Change window size + tester.view.physicalSize = const Size(250.0, 200.0); + tester.view.devicePixelRatio = 1.0; + await tester.pumpAndSettle(); + expect(find.byIcon(Icons.arrow_back), findsOneWidget); + }); } TextStyle? _iconStyle(WidgetTester tester, IconData icon) {