Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 112 additions & 100 deletions packages/material_ui/lib/src/search_anchor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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: <Widget>[
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<EdgeInsetsGeometry?>(
effectiveBarPadding,
),
leading: widget.viewLeading ?? defaultLeading,
trailing: widget.viewTrailing ?? defaultTrailing,
hintText: widget.viewHintText,
backgroundColor: const MaterialStatePropertyAll<Color>(
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,
),
Comment on lines +1148 to +1157

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When widget.showFullScreenView is true, the search view is intended to be full screen. If the parent constraints are smaller than minWidth or minHeight (e.g., on very small screens or inside a small nested Navigator), forcing the constraints to minWidth/minHeight via math.max will cause the full-screen view to overflow its parent boundaries.

Instead of forcing the maximum constraints to be at least minWidth/minHeight, we can set the minWidth/minHeight to 0.0 when widget.showFullScreenView is true. This allows the full-screen view to scale down and fit perfectly within the parent constraints without overflowing or throwing assertion errors.

              constraints: BoxConstraints(
                minWidth: widget.showFullScreenView ? 0.0 : minWidth,
                maxWidth: widget.showFullScreenView
                    ? constraints.maxWidth
                    : _viewRect.width,
                minHeight: widget.showFullScreenView ? 0.0 : minHeight,
                maxHeight: widget.showFullScreenView
                    ? constraints.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: 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: <Widget>[
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<EdgeInsetsGeometry?>(
effectiveBarPadding,
),
leading: widget.viewLeading ?? defaultLeading,
trailing: widget.viewTrailing ?? defaultTrailing,
hintText: widget.viewHintText,
backgroundColor: const MaterialStatePropertyAll<Color>(
Colors.transparent,
),
overlayColor: const MaterialStatePropertyAll<Color>(
Colors.transparent,
),
elevation: const MaterialStatePropertyAll<double>(0.0),
textStyle: MaterialStatePropertyAll<TextStyle?>(effectiveTextStyle),
hintStyle: MaterialStatePropertyAll<TextStyle?>(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<Color>(Colors.transparent),
elevation: const MaterialStatePropertyAll<double>(0.0),
textStyle: MaterialStatePropertyAll<TextStyle?>(effectiveTextStyle),
hintStyle: MaterialStatePropertyAll<TextStyle?>(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) ...<Widget>[
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) ...<Widget>[
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),
),
),
],
],
),
),
),
),
),
),
),
),
);
},
);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
143 changes: 143 additions & 0 deletions packages/material_ui/test/search_anchor_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Widget>[];
},
),
),
),
);

// 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<void>(
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 <Widget>[];
},
),
);
},
);
},
),
);
},
),
),
),
);

// 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<void> checkSearchBarDefaults(
Expand Down Expand Up @@ -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,
);
}
Loading