Fix search view position (#124169)
This commit is contained in:
@@ -148,6 +148,7 @@ class SearchAnchor extends StatefulWidget {
|
||||
TextStyle? viewHeaderHintStyle,
|
||||
Color? dividerColor,
|
||||
BoxConstraints? constraints,
|
||||
BoxConstraints? viewConstraints,
|
||||
bool? isFullScreen,
|
||||
SearchController searchController,
|
||||
required SuggestionsBuilder suggestionsBuilder
|
||||
@@ -254,6 +255,11 @@ class SearchAnchor extends StatefulWidget {
|
||||
|
||||
/// Optional size constraints for the search view.
|
||||
///
|
||||
/// By default, the search view has the same width as the anchor and is 2/3
|
||||
/// the height of the screen. If the width and height of the view are within
|
||||
/// the [viewConstraints], the view will show its default size. Otherwise,
|
||||
/// the size of the view will be constrained by this property.
|
||||
///
|
||||
/// If null, the value of [SearchViewThemeData.constraints] will be used. If
|
||||
/// this is also null, then the constraints defaults to:
|
||||
/// ```dart
|
||||
@@ -477,24 +483,43 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
|
||||
final Size screenSize = MediaQuery.of(context).size;
|
||||
final Rect anchorRect = getRect() ?? Rect.zero;
|
||||
|
||||
// Check if the search view goes off the screen.
|
||||
final BoxConstraints effectiveConstraints = viewConstraints ?? viewTheme.constraints ?? viewDefaults.constraints!;
|
||||
final double verticalDistanceToEdge = screenSize.height - anchorRect.top;
|
||||
final double endHeight = math.max(effectiveConstraints.minHeight, math.min(screenSize.height * 2 / 3, verticalDistanceToEdge));
|
||||
_rectTween.begin = anchorRect;
|
||||
|
||||
final double viewWidth = clampDouble(anchorRect.width, effectiveConstraints.minWidth, effectiveConstraints.maxWidth);
|
||||
final double viewHeight = clampDouble(screenSize.height * 2 / 3, effectiveConstraints.minHeight, effectiveConstraints.maxHeight);
|
||||
|
||||
switch (textDirection ?? TextDirection.ltr) {
|
||||
case TextDirection.ltr:
|
||||
final double viewEdgeToScreenEdge = screenSize.width - anchorRect.left;
|
||||
final double endWidth = math.max(effectiveConstraints.minWidth, math.min(anchorRect.width, viewEdgeToScreenEdge));
|
||||
final Size endSize = Size(endWidth, endHeight);
|
||||
_rectTween.end = showFullScreenView ? Offset.zero & screenSize : (anchorRect.topLeft & endSize);
|
||||
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. 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), topLeft.dy);
|
||||
}
|
||||
if (viewTopToScreenBottom < viewHeight) {
|
||||
topLeft = Offset(topLeft.dx, screenSize.height - math.min(viewHeight, screenSize.height));
|
||||
}
|
||||
final Size endSize = Size(viewWidth, viewHeight);
|
||||
_rectTween.end = showFullScreenView ? Offset.zero & screenSize : (topLeft & endSize);
|
||||
return;
|
||||
case TextDirection.rtl:
|
||||
final double viewEdgeToScreenEdge = anchorRect.right;
|
||||
final double endWidth = math.max(effectiveConstraints.minWidth, math.min(anchorRect.width, viewEdgeToScreenEdge));
|
||||
final Offset topLeft = Offset(math.max(anchorRect.right - endWidth, 0.0), anchorRect.top);
|
||||
final Size endSize = Size(endWidth, endHeight);
|
||||
final double viewRightToScreenLeft = anchorRect.right;
|
||||
final double viewTopToScreenBottom = screenSize.height - anchorRect.top;
|
||||
|
||||
// Make sure the search view doesn't go off the screen.
|
||||
Offset topLeft = Offset(math.max(anchorRect.right - viewWidth, 0.0), anchorRect.top);
|
||||
if (viewRightToScreenLeft < viewWidth) {
|
||||
topLeft = Offset(0.0, topLeft.dy);
|
||||
}
|
||||
if (viewTopToScreenBottom < viewHeight) {
|
||||
topLeft = Offset(topLeft.dx, screenSize.height - math.min(viewHeight, screenSize.height));
|
||||
}
|
||||
final Size endSize = Size(viewWidth, viewHeight);
|
||||
_rectTween.end = showFullScreenView ? Offset.zero & screenSize : (topLeft & endSize);
|
||||
}
|
||||
}
|
||||
@@ -626,7 +651,6 @@ class _ViewContentState extends State<_ViewContent> {
|
||||
super.initState();
|
||||
_viewRect = widget.viewRect;
|
||||
_controller = widget.searchController;
|
||||
result = widget.suggestionsBuilder(context, _controller);
|
||||
if (!_focusNode.hasFocus) {
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
@@ -645,20 +669,45 @@ class _ViewContentState extends State<_ViewContent> {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
result = widget.suggestionsBuilder(context, _controller);
|
||||
final Size updatedScreenSize = MediaQuery.of(context).size;
|
||||
|
||||
if (_screenSize != updatedScreenSize) {
|
||||
_screenSize = updatedScreenSize;
|
||||
setState(() {
|
||||
final Rect anchorRect = widget.getRect() ?? _viewRect;
|
||||
final BoxConstraints constraints = widget.viewConstraints ?? widget.viewTheme.constraints ?? widget.viewDefaults.constraints!;
|
||||
final Size updatedViewSize = Size(math.max(constraints.minWidth, anchorRect.width), _viewRect.height);
|
||||
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 Offset updatedPosition = anchorRect.topLeft;
|
||||
_viewRect = updatedPosition & updatedViewSize;
|
||||
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 Offset topLeft = Offset(math.max(anchorRect.right - updatedViewSize.width, 0.0), anchorRect.top);
|
||||
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;
|
||||
}
|
||||
});
|
||||
@@ -834,6 +883,7 @@ class _SearchAnchorWithSearchBar extends SearchAnchor {
|
||||
TextStyle? viewHeaderHintStyle,
|
||||
super.dividerColor,
|
||||
BoxConstraints? constraints,
|
||||
super.viewConstraints,
|
||||
super.isFullScreen,
|
||||
super.searchController,
|
||||
required super.suggestionsBuilder
|
||||
|
||||
@@ -1423,6 +1423,143 @@ void main() {
|
||||
controller.openView();
|
||||
expect(controller.isOpen, true);
|
||||
});
|
||||
|
||||
testWidgets('Search view does not go off the screen - LTR', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Material(
|
||||
child: Align(
|
||||
// Put the search anchor on the bottom-right corner of the screen to test
|
||||
// if the search view goes off the window.
|
||||
alignment: Alignment.bottomRight,
|
||||
child: SearchAnchor(
|
||||
isFullScreen: false,
|
||||
builder: (BuildContext context, SearchController controller) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
onPressed: () {
|
||||
controller.openView();
|
||||
},
|
||||
);
|
||||
},
|
||||
suggestionsBuilder: (BuildContext context, SearchController controller) {
|
||||
return <Widget>[];
|
||||
},
|
||||
),
|
||||
),
|
||||
),),
|
||||
);
|
||||
|
||||
final Finder findIconButton = find.widgetWithIcon(IconButton, Icons.search);
|
||||
final Rect iconButton = tester.getRect(findIconButton);
|
||||
// Icon button has a size of (48.0, 48.0) and the screen size is (800.0, 600.0).
|
||||
expect(iconButton, equals(const Rect.fromLTRB(752.0, 552.0, 800.0, 600.0)));
|
||||
|
||||
await tester.tap(find.byIcon(Icons.search));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Rect searchViewRect = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
|
||||
expect(searchViewRect, equals(const Rect.fromLTRB(440.0, 200.0, 800.0, 600.0)));
|
||||
});
|
||||
|
||||
testWidgets('Search view does not go off the screen - RTL', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: Material(
|
||||
child: Align(
|
||||
// Put the search anchor on the bottom-left corner of the screen to test
|
||||
// if the search view goes off the window when the text direction is right-to-left.
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: SearchAnchor(
|
||||
isFullScreen: false,
|
||||
builder: (BuildContext context, SearchController controller) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
onPressed: () {
|
||||
controller.openView();
|
||||
},
|
||||
);
|
||||
},
|
||||
suggestionsBuilder: (BuildContext context, SearchController controller) {
|
||||
return <Widget>[];
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),),
|
||||
);
|
||||
|
||||
final Finder findIconButton = find.widgetWithIcon(IconButton, Icons.search);
|
||||
final Rect iconButton = tester.getRect(findIconButton);
|
||||
expect(iconButton, equals(const Rect.fromLTRB(0.0, 552.0, 48.0, 600.0)));
|
||||
|
||||
await tester.tap(find.byIcon(Icons.search));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Rect searchViewRect = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
|
||||
expect(searchViewRect, equals(const Rect.fromLTRB(0.0, 200.0, 360.0, 600.0)));
|
||||
});
|
||||
|
||||
testWidgets('Search view becomes smaller if the window size is smaller than the view size', (WidgetTester tester) async {
|
||||
addTearDown(tester.view.reset);
|
||||
tester.view.physicalSize = const Size(200.0, 200.0);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
|
||||
Widget buildSearchAnchor({TextDirection textDirection = TextDirection.ltr}) {
|
||||
return MaterialApp(
|
||||
home: Directionality(
|
||||
textDirection: textDirection,
|
||||
child: 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 <Widget>[];
|
||||
},
|
||||
),
|
||||
),
|
||||
),);
|
||||
}
|
||||
|
||||
// Test LTR text direction.
|
||||
await tester.pumpWidget(buildSearchAnchor());
|
||||
|
||||
final Finder findIconButton = find.widgetWithIcon(IconButton, Icons.search);
|
||||
final Rect iconButton = tester.getRect(findIconButton);
|
||||
// The icon button size is (48.0, 48.0), and the screen size is (200.0, 200.0)
|
||||
expect(iconButton, equals(const Rect.fromLTRB(152.0, 152.0, 200.0, 200.0)));
|
||||
|
||||
await tester.tap(find.byIcon(Icons.search));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Rect searchViewRect = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
|
||||
expect(searchViewRect, equals(const Rect.fromLTRB(0.0, 0.0, 200.0, 200.0)));
|
||||
|
||||
// Test RTL text direction.
|
||||
await tester.pumpWidget(Container());
|
||||
await tester.pumpWidget(buildSearchAnchor(textDirection: TextDirection.rtl));
|
||||
|
||||
final Finder findIconButtonRTL = find.widgetWithIcon(IconButton, Icons.search);
|
||||
final Rect iconButtonRTL = tester.getRect(findIconButtonRTL);
|
||||
// The icon button size is (48.0, 48.0), and the screen size is (200.0, 200.0)
|
||||
expect(iconButtonRTL, equals(const Rect.fromLTRB(152.0, 152.0, 200.0, 200.0)));
|
||||
|
||||
await tester.tap(find.byIcon(Icons.search));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
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)));
|
||||
});
|
||||
}
|
||||
|
||||
TextStyle? _iconStyle(WidgetTester tester, IconData icon) {
|
||||
|
||||
Reference in New Issue
Block a user