Skip to content

Commit 4c6929d

Browse files
authored
Dismiss the docked search view when the window size is changed (#125071)
1 parent 4473899 commit 4c6929d

File tree

2 files changed

+98
-38
lines changed

2 files changed

+98
-38
lines changed

packages/flutter/lib/src/material/search_anchor.dart

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ typedef ViewBuilder = Widget Function(Iterable<Widget> suggestions);
6767
/// If [builder] returns an Icon, or any un-tappable widgets, we don't have
6868
/// to explicitly call [SearchController.openView].
6969
///
70+
/// The search view route will be popped if the window size is changed and the
71+
/// search view route is not in full-screen mode. However, if the search view route
72+
/// is in full-screen mode, changing the window size, such as rotating a mobile
73+
/// device from portrait mode to landscape mode, will not close the search view.
74+
///
7075
/// {@tool dartpad}
7176
/// This example shows how to use an IconButton to open a search view in a [SearchAnchor].
7277
/// It also shows how to use [SearchController] to open or close the search view route.
@@ -286,6 +291,7 @@ class SearchAnchor extends StatefulWidget {
286291
}
287292

288293
class _SearchAnchorState extends State<SearchAnchor> {
294+
Size? _screenSize;
289295
bool _anchorIsVisible = true;
290296
final GlobalKey _anchorKey = GlobalKey();
291297
bool get _viewIsOpen => !_anchorIsVisible;
@@ -301,6 +307,18 @@ class _SearchAnchorState extends State<SearchAnchor> {
301307
_searchController._attach(this);
302308
}
303309

310+
@override
311+
void didChangeDependencies() {
312+
super.didChangeDependencies();
313+
final Size updatedScreenSize = MediaQuery.of(context).size;
314+
if (_screenSize != null && _screenSize != updatedScreenSize) {
315+
if (_searchController.isOpen && !getShowFullScreenView()) {
316+
_closeView(null);
317+
}
318+
}
319+
_screenSize = updatedScreenSize;
320+
}
321+
304322
@override
305323
void dispose() {
306324
super.dispose();
@@ -672,45 +690,9 @@ class _ViewContentState extends State<_ViewContent> {
672690
result = widget.suggestionsBuilder(context, _controller);
673691
final Size updatedScreenSize = MediaQuery.of(context).size;
674692

675-
if (_screenSize != updatedScreenSize) {
693+
if (_screenSize != updatedScreenSize && widget.showFullScreenView) {
676694
_screenSize = updatedScreenSize;
677-
setState(() {
678-
final Rect anchorRect = widget.getRect() ?? _viewRect;
679-
final BoxConstraints constraints = widget.viewConstraints ?? widget.viewTheme.constraints ?? widget.viewDefaults.constraints!;
680-
final double viewWidth = clampDouble(anchorRect.width, constraints.minWidth, constraints.maxWidth);
681-
final double viewHeight = clampDouble(_screenSize!.height * 2 / 3, constraints.minHeight, constraints.maxHeight);
682-
final Size updatedViewSize = Size(viewWidth, viewHeight);
683-
684-
switch (Directionality.of(context)) {
685-
case TextDirection.ltr:
686-
final double viewLeftToScreenRight = _screenSize!.width - anchorRect.left;
687-
final double viewTopToScreenBottom = _screenSize!.height - anchorRect.top;
688-
689-
// Make sure the search view doesn't go off the screen when the screen
690-
// size is changed. If the search view doesn't fit, move the top-left
691-
// corner of the view to fit the window. If the window is smaller than
692-
// the view, then we resize the view to fit the window.
693-
Offset topLeft = anchorRect.topLeft;
694-
if (viewLeftToScreenRight < viewWidth) {
695-
topLeft = Offset(_screenSize!.width - math.min(viewWidth, _screenSize!.width), anchorRect.top);
696-
}
697-
if (viewTopToScreenBottom < viewHeight) {
698-
topLeft = Offset(topLeft.dx, _screenSize!.height - math.min(viewHeight, _screenSize!.height));
699-
}
700-
_viewRect = topLeft & updatedViewSize;
701-
return;
702-
case TextDirection.rtl:
703-
final double viewTopToScreenBottom = _screenSize!.height - anchorRect.top;
704-
Offset topLeft = Offset(
705-
math.max(anchorRect.right - viewWidth, 0.0),
706-
anchorRect.top,
707-
);
708-
if (viewTopToScreenBottom < viewHeight) {
709-
topLeft = Offset(topLeft.dx, _screenSize!.height - math.min(viewHeight, _screenSize!.height));
710-
}
711-
_viewRect = topLeft & updatedViewSize;
712-
}
713-
});
695+
_viewRect = Offset.zero & _screenSize!;
714696
}
715697
}
716698

packages/flutter/test/material/search_anchor_test.dart

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,84 @@ void main() {
15601560
final Rect searchViewRectRTL = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
15611561
expect(searchViewRectRTL, equals(const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0)));
15621562
});
1563+
1564+
testWidgets('Docked search view route is popped if the window size changes', (WidgetTester tester) async {
1565+
addTearDown(tester.view.reset);
1566+
tester.view.physicalSize = const Size(500.0, 600.0);
1567+
tester.view.devicePixelRatio = 1.0;
1568+
1569+
await tester.pumpWidget(MaterialApp(
1570+
home: Material(
1571+
child: SearchAnchor(
1572+
isFullScreen: false,
1573+
builder: (BuildContext context, SearchController controller) {
1574+
return Align(
1575+
alignment: Alignment.bottomRight,
1576+
child: IconButton(
1577+
icon: const Icon(Icons.search),
1578+
onPressed: () {
1579+
controller.openView();
1580+
},
1581+
),
1582+
);
1583+
},
1584+
suggestionsBuilder: (BuildContext context, SearchController controller) {
1585+
return <Widget>[];
1586+
},
1587+
),
1588+
),
1589+
));
1590+
1591+
// Open the search view
1592+
await tester.tap(find.byIcon(Icons.search));
1593+
await tester.pumpAndSettle();
1594+
expect(find.byIcon(Icons.arrow_back), findsOneWidget);
1595+
1596+
// Change window size
1597+
tester.view.physicalSize = const Size(250.0, 200.0);
1598+
tester.view.devicePixelRatio = 1.0;
1599+
await tester.pumpAndSettle();
1600+
expect(find.byIcon(Icons.arrow_back), findsNothing);
1601+
});
1602+
1603+
testWidgets('Full-screen search view route should stay if the window size changes', (WidgetTester tester) async {
1604+
addTearDown(tester.view.reset);
1605+
tester.view.physicalSize = const Size(500.0, 600.0);
1606+
tester.view.devicePixelRatio = 1.0;
1607+
1608+
await tester.pumpWidget(MaterialApp(
1609+
home: Material(
1610+
child: SearchAnchor(
1611+
isFullScreen: true,
1612+
builder: (BuildContext context, SearchController controller) {
1613+
return Align(
1614+
alignment: Alignment.bottomRight,
1615+
child: IconButton(
1616+
icon: const Icon(Icons.search),
1617+
onPressed: () {
1618+
controller.openView();
1619+
},
1620+
),
1621+
);
1622+
},
1623+
suggestionsBuilder: (BuildContext context, SearchController controller) {
1624+
return <Widget>[];
1625+
},
1626+
),
1627+
),
1628+
));
1629+
1630+
// Open a full-screen search view
1631+
await tester.tap(find.byIcon(Icons.search));
1632+
await tester.pumpAndSettle();
1633+
expect(find.byIcon(Icons.arrow_back), findsOneWidget);
1634+
1635+
// Change window size
1636+
tester.view.physicalSize = const Size(250.0, 200.0);
1637+
tester.view.devicePixelRatio = 1.0;
1638+
await tester.pumpAndSettle();
1639+
expect(find.byIcon(Icons.arrow_back), findsOneWidget);
1640+
});
15631641
}
15641642

15651643
TextStyle? _iconStyle(WidgetTester tester, IconData icon) {

0 commit comments

Comments
 (0)