diff --git a/packages/material_ui/lib/src/search_anchor.dart b/packages/material_ui/lib/src/search_anchor.dart index c08eccafa7e..c3e27270cab 100644 --- a/packages/material_ui/lib/src/search_anchor.dart +++ b/packages/material_ui/lib/src/search_anchor.dart @@ -1138,114 +1138,126 @@ class _ViewContentState extends State<_ViewContent> { child: const Divider(height: 1), ); - return Align( - alignment: Alignment.topLeft, - child: Transform.translate( - offset: _viewRect.topLeft, - child: ConstrainedBox( - constraints: BoxConstraints( - minWidth: minWidth, - maxWidth: _viewRect.width, - minHeight: minHeight, - maxHeight: _viewRect.height, - ), - child: Padding( - padding: widget.showFullScreenView - ? EdgeInsets.zero - : (effectivePadding ?? EdgeInsets.zero), - child: Material( - clipBehavior: Clip.antiAlias, - shape: effectiveShape, - color: effectiveBackgroundColor, - surfaceTintColor: effectiveSurfaceTint, - elevation: effectiveElevation, - child: OverflowBox( - alignment: Alignment.topLeft, - maxWidth: math.min(widget.viewMaxWidth, _screenSize!.width), - minWidth: 0, - fit: OverflowBoxFit.deferToChild, - child: FadeTransition( - opacity: viewIconsFadeCurve, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: EdgeInsets.only(top: widget.topPadding), - child: SafeArea( - top: false, - bottom: false, - child: SearchBar( - autoFocus: true, - constraints: - headerConstraints ?? - (widget.showFullScreenView - ? BoxConstraints( - minHeight: _SearchViewDefaultsM3.fullScreenBarHeight, - ) - : null), - padding: WidgetStatePropertyAll( - effectiveBarPadding, - ), - leading: widget.viewLeading ?? defaultLeading, - trailing: widget.viewTrailing ?? defaultTrailing, - hintText: widget.viewHintText, - backgroundColor: const MaterialStatePropertyAll( - Colors.transparent, + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Align( + alignment: Alignment.topLeft, + child: Transform.translate( + offset: _viewRect.topLeft, + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: minWidth, + maxWidth: widget.showFullScreenView + ? math.max(constraints.maxWidth, minWidth) + : _viewRect.width, + minHeight: minHeight, + maxHeight: widget.showFullScreenView + ? math.max(constraints.maxHeight, minHeight) + : _viewRect.height, + ), + child: Padding( + padding: widget.showFullScreenView + ? EdgeInsets.zero + : (effectivePadding ?? EdgeInsets.zero), + child: Material( + clipBehavior: Clip.antiAlias, + shape: effectiveShape, + color: effectiveBackgroundColor, + surfaceTintColor: effectiveSurfaceTint, + elevation: effectiveElevation, + child: OverflowBox( + alignment: Alignment.topLeft, + maxWidth: widget.showFullScreenView + ? constraints.maxWidth + : math.min(widget.viewMaxWidth, constraints.maxWidth), + minWidth: 0, + fit: OverflowBoxFit.deferToChild, + child: FadeTransition( + opacity: viewIconsFadeCurve, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: EdgeInsets.only(top: widget.topPadding), + child: SafeArea( + top: false, + bottom: false, + child: SearchBar( + autoFocus: true, + constraints: + headerConstraints ?? + (widget.showFullScreenView + ? BoxConstraints( + minHeight: _SearchViewDefaultsM3.fullScreenBarHeight, + ) + : null), + padding: WidgetStatePropertyAll( + effectiveBarPadding, + ), + leading: widget.viewLeading ?? defaultLeading, + trailing: widget.viewTrailing ?? defaultTrailing, + hintText: widget.viewHintText, + backgroundColor: const MaterialStatePropertyAll( + Colors.transparent, + ), + overlayColor: const MaterialStatePropertyAll( + Colors.transparent, + ), + elevation: const MaterialStatePropertyAll(0.0), + textStyle: MaterialStatePropertyAll(effectiveTextStyle), + hintStyle: MaterialStatePropertyAll(effectiveHintStyle), + controller: _controller, + onChanged: (String value) { + widget.viewOnChanged?.call(value); + updateSuggestions(); + }, + onSubmitted: widget.viewOnSubmitted, + textCapitalization: widget.textCapitalization, + textInputAction: widget.textInputAction, + keyboardType: widget.keyboardType, + smartDashesType: widget.smartDashesType, + smartQuotesType: widget.smartQuotesType, + ), ), - overlayColor: const MaterialStatePropertyAll(Colors.transparent), - elevation: const MaterialStatePropertyAll(0.0), - textStyle: MaterialStatePropertyAll(effectiveTextStyle), - hintStyle: MaterialStatePropertyAll(effectiveHintStyle), - controller: _controller, - onChanged: (String value) { - widget.viewOnChanged?.call(value); - updateSuggestions(); - }, - onSubmitted: widget.viewOnSubmitted, - textCapitalization: widget.textCapitalization, - textInputAction: widget.textInputAction, - keyboardType: widget.keyboardType, - smartDashesType: widget.smartDashesType, - smartQuotesType: widget.smartQuotesType, ), - ), + if (!effectiveShrinkWrap || + minHeight > 0 || + widget.showFullScreenView || + result.isNotEmpty) ...[ + FadeTransition(opacity: viewDividerFadeCurve, child: viewDivider), + Flexible( + fit: (effectiveShrinkWrap && !widget.showFullScreenView) + ? FlexFit.loose + : FlexFit.tight, + child: FadeTransition( + opacity: viewListFadeOnIntervalCurve, + child: widget.viewBuilder == null + ? MediaQuery.removePadding( + context: context, + removeTop: true, + child: ListView( + padding: EdgeInsets.only( + bottom: MediaQuery.viewInsetsOf(context).bottom, + ), + shrinkWrap: effectiveShrinkWrap, + children: result.toList(), + ), + ) + : widget.viewBuilder!(result), + ), + ), + ], + ], ), - if (!effectiveShrinkWrap || - minHeight > 0 || - widget.showFullScreenView || - result.isNotEmpty) ...[ - FadeTransition(opacity: viewDividerFadeCurve, child: viewDivider), - Flexible( - fit: (effectiveShrinkWrap && !widget.showFullScreenView) - ? FlexFit.loose - : FlexFit.tight, - child: FadeTransition( - opacity: viewListFadeOnIntervalCurve, - child: widget.viewBuilder == null - ? MediaQuery.removePadding( - context: context, - removeTop: true, - child: ListView( - padding: EdgeInsets.only( - bottom: MediaQuery.viewInsetsOf(context).bottom, - ), - shrinkWrap: effectiveShrinkWrap, - children: result.toList(), - ), - ) - : widget.viewBuilder!(result), - ), - ), - ], - ], + ), ), ), ), ), ), - ), - ), + ); + }, ); } } diff --git a/packages/material_ui/pending_changelogs/change_2026_08_13_1786649533919.yaml b/packages/material_ui/pending_changelogs/change_2026_08_13_1786649533919.yaml new file mode 100644 index 00000000000..6bc5100c70f --- /dev/null +++ b/packages/material_ui/pending_changelogs/change_2026_08_13_1786649533919.yaml @@ -0,0 +1,4 @@ +changelog: | + - SearchAnchor overlay expands to full-screen on viewport size change (e.g., on device rotation). + - Fixes https://github.com/flutter/flutter/issues/186154. +version: patch diff --git a/packages/material_ui/test/search_anchor_test.dart b/packages/material_ui/test/search_anchor_test.dart index 675a4e93950..aa6934b1893 100644 --- a/packages/material_ui/test/search_anchor_test.dart +++ b/packages/material_ui/test/search_anchor_test.dart @@ -4380,6 +4380,143 @@ void main() { await tester.pump(); expect(find.text('X'), findsOne); }); + + // Regression test for https://github.com/flutter/flutter/issues/186154. + testWidgets('SearchAnchor full-screen view expands to fit screen when rotated', ( + WidgetTester tester, + ) async { + addTearDown(tester.view.reset); + + // Start in portrait mode. + const portraitModeWidth = 360.0; + const portraitModeHeight = 800.0; + tester.view.physicalSize = const Size(portraitModeWidth, portraitModeHeight); + tester.view.devicePixelRatio = 1.0; + + await tester.pumpWidget( + MaterialApp( + home: Center( + child: SearchAnchor( + isFullScreen: true, + builder: (BuildContext context, SearchController controller) { + return IconButton( + icon: const Icon(Icons.search), + onPressed: () { + controller.openView(); + }, + ); + }, + suggestionsBuilder: (BuildContext context, SearchController controller) { + return []; + }, + ), + ), + ), + ); + + // Open search view. + await tester.tap(find.byIcon(Icons.search)); + await tester.pumpAndSettle(); + + // Verify starting sizes match portrait mode. + final Size startingSize = getSearchViewSize(tester); + expect(startingSize.width, portraitModeWidth); + expect(startingSize.height, portraitModeHeight); + + // Rotate to landscape mode. + const landscapeModeWidth = portraitModeHeight; + const landscapeModeHeight = portraitModeWidth; + tester.view.physicalSize = const Size(landscapeModeWidth, landscapeModeHeight); + await tester.pumpAndSettle(); + + // Verify the view expands to match landscape mode. + final Size rotatedSize = getSearchViewSize(tester); + expect(rotatedSize.width, landscapeModeWidth); + expect(rotatedSize.height, landscapeModeHeight); + }); + + // Regression test for https://github.com/flutter/flutter/issues/186154. + testWidgets('SearchAnchor resizes itself to match Navigator parent', (WidgetTester tester) async { + addTearDown(tester.view.reset); + + var parentHeight = 400.0; + var parentWidth = 600.0; + late StateSetter setState; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: StatefulBuilder( + builder: (BuildContext context, StateSetter stateSetter) { + setState = stateSetter; + return SizedBox( + height: parentHeight, + width: parentWidth, + child: Navigator( + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute( + builder: (BuildContext context) { + return Scaffold( + body: SearchAnchor( + isFullScreen: true, + builder: (BuildContext context, SearchController controller) { + return IconButton( + icon: const Icon(Icons.search), + onPressed: () { + controller.openView(); + }, + ); + }, + suggestionsBuilder: + (BuildContext context, SearchController controller) { + return []; + }, + ), + ); + }, + ); + }, + ), + ); + }, + ), + ), + ), + ); + + // Open search view. + await tester.tap(find.byIcon(Icons.search)); + await tester.pumpAndSettle(); + + // Verify search view size matches parent. + Size size = getSearchViewSize(tester); + expect(size.height, 400.0); + expect(size.width, 600.0); + + // Resize the parent container larger. + setState(() { + parentHeight = 500.0; + parentWidth = 700.0; + }); + await tester.pumpAndSettle(); + + // Verify the view expands to match parent. + size = getSearchViewSize(tester); + expect(size.height, 500.0); + expect(size.width, 700.0); + + // Resize the parent container smaller. + setState(() { + parentHeight = 300.0; + parentWidth = 400.0; + }); + await tester.pumpAndSettle(); + + // Verify the view shrinks to match parent. + size = getSearchViewSize(tester); + expect(size.height, 300.0); + expect(size.width, 400.0); + }); } Future checkSearchBarDefaults( @@ -4480,3 +4617,9 @@ Material getSearchViewMaterial(WidgetTester tester) { find.descendant(of: findViewContent(), matching: find.byType(Material)).first, ); } + +Size getSearchViewSize(WidgetTester tester) { + return tester.getSize( + find.descendant(of: findViewContent(), matching: find.byType(ConstrainedBox)).first, + ); +}