diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart index b2104aca183..e60f585522a 100644 --- a/packages/flutter/lib/src/widgets/overlay.dart +++ b/packages/flutter/lib/src/widgets/overlay.dart @@ -26,6 +26,7 @@ import 'basic.dart'; import 'framework.dart'; import 'layout_builder.dart'; import 'lookup_boundary.dart'; +import 'media_query.dart'; import 'ticker_provider.dart'; /// The signature of the widget builder callback used in @@ -2029,11 +2030,22 @@ class _OverlayPortalState extends State { child: Semantics(traversalParentIdentifier: this, child: widget.child), ); } + + final _OverlayEntryLocation overlayLocation = _getLocation(zOrderIndex, widget.overlayLocation); + final MediaQueryData overlayData = MediaQuery.of(overlayLocation._childModel.context); + final MediaQueryData data = MediaQuery.of(context).copyWith( + padding: overlayData.padding, + viewInsets: overlayData.viewInsets, + viewPadding: overlayData.viewPadding, + ); return _OverlayPortal( - overlayLocation: _getLocation(zOrderIndex, widget.overlayLocation), + overlayLocation: overlayLocation, overlayChild: _DeferredLayout( childIdentifier: this, - child: Builder(builder: widget.overlayChildBuilder), + child: MediaQuery( + data: data, + child: Builder(builder: widget.overlayChildBuilder), + ), ), child: Semantics(traversalParentIdentifier: this, child: widget.child), ); diff --git a/packages/flutter/test/widgets/overlay_portal_test.dart b/packages/flutter/test/widgets/overlay_portal_test.dart index 0ad0b7f0268..437262cc5ed 100644 --- a/packages/flutter/test/widgets/overlay_portal_test.dart +++ b/packages/flutter/test/widgets/overlay_portal_test.dart @@ -154,6 +154,129 @@ void main() { expect(directionSeenByOverlayChild, textDirection); }); + testWidgets( + 'OverlayPortal overlayChild located in root Overlay receives MediaQuery properties from root Overlay context', + (WidgetTester tester) async { + final controller = OverlayPortalController(); + const rootPadding = EdgeInsets.all(10); + const innerPadding = EdgeInsets.all(20); + + MediaQueryData? overlayChildData; + OverlayEntry? outerEntry; + OverlayEntry? innerEntry; + addTearDown(() { + outerEntry?.remove(); + outerEntry?.dispose(); + innerEntry?.remove(); + innerEntry?.dispose(); + }); + + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(padding: rootPadding), + child: Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + initialEntries: [ + outerEntry = OverlayEntry( + builder: (BuildContext context) { + return MediaQuery( + data: const MediaQueryData(padding: innerPadding), + child: Overlay( + initialEntries: [ + innerEntry = OverlayEntry( + builder: (BuildContext context) { + return OverlayPortal( + controller: controller, + overlayLocation: OverlayChildLocation.rootOverlay, + overlayChildBuilder: (BuildContext context) { + overlayChildData = MediaQuery.of(context); + return const SizedBox(); + }, + child: const SizedBox(), + ); + }, + ), + ], + ), + ); + }, + ), + ], + ), + ), + ), + ); + + controller.show(); + await tester.pump(); + + expect(overlayChildData?.padding, rootPadding); + }, + ); + + testWidgets('OverlayPortal overlayChild receives MediaQuery properties from Overlay context', ( + WidgetTester tester, + ) async { + final controller = OverlayPortalController(); + const expectedPadding = EdgeInsets.all(10); + const expectedViewInsets = EdgeInsets.only(bottom: 300); + const expectedViewPadding = EdgeInsets.only(top: 50, bottom: 20); + const expectedSize = Size(800, 600); + + MediaQueryData? overlayChildData; + OverlayEntry? entry; + addTearDown(() { + entry?.remove(); + entry?.dispose(); + }); + + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData( + padding: expectedPadding, + viewInsets: expectedViewInsets, + viewPadding: expectedViewPadding, + size: expectedSize, + ), + child: Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + initialEntries: [ + entry = OverlayEntry( + builder: (BuildContext context) { + return MediaQuery( + data: MediaQuery.of(context).copyWith( + padding: EdgeInsets.zero, + viewInsets: EdgeInsets.zero, + viewPadding: EdgeInsets.zero, + ), + child: OverlayPortal( + controller: controller, + overlayChildBuilder: (BuildContext context) { + overlayChildData = MediaQuery.of(context); + return const SizedBox(); + }, + child: const SizedBox(), + ), + ); + }, + ), + ], + ), + ), + ), + ); + + controller.show(); + await tester.pump(); + + expect(overlayChildData?.padding, expectedPadding); + expect(overlayChildData?.viewInsets, expectedViewInsets); + expect(overlayChildData?.viewPadding, expectedViewPadding); + expect(overlayChildData?.size, expectedSize); + }); + testWidgets('The overlay portal update semantics does not dirty overlay', ( WidgetTester tester, ) async {