diff --git a/packages/flutter/lib/src/cupertino/sheet.dart b/packages/flutter/lib/src/cupertino/sheet.dart index ef2453b13b0..f2eca5e196e 100644 --- a/packages/flutter/lib/src/cupertino/sheet.dart +++ b/packages/flutter/lib/src/cupertino/sheet.dart @@ -11,13 +11,18 @@ import 'interface_level.dart'; import 'route.dart'; import 'theme.dart'; +// The distance from the top of the open sheet to the top of the screen, as a ratio +// of the total height of the screen. Found from eyeballing a simulator running +// iOS 18.0. +const double _kTopGapRatio = 0.08; + // Tween for animating a Cupertino sheet onto the screen. // // Begins fully offscreen below the screen and ends onscreen with a small gap at // the top of the screen. Values found from eyeballing a simulator running iOS 18.0. final Animatable _kBottomUpTween = Tween( begin: const Offset(0.0, 1.0), - end: const Offset(0.0, 0.08), + end: const Offset(0.0, _kTopGapRatio), ); // Offset change for when a new sheet covers another sheet. '0.0' represents the @@ -456,9 +461,19 @@ class CupertinoSheetRoute extends PageRoute with _CupertinoSheetRouteTrans @override Widget buildContent(BuildContext context) { - return CupertinoUserInterfaceLevel( - data: CupertinoUserInterfaceLevelData.elevated, - child: _CupertinoSheetScope(child: builder(context)), + final double bottomPadding = MediaQuery.sizeOf(context).height * _kTopGapRatio; + + return MediaQuery.removePadding( + context: context, + removeTop: true, + removeBottom: true, + child: Padding( + padding: EdgeInsets.only(bottom: bottomPadding), + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.elevated, + child: _CupertinoSheetScope(child: builder(context)), + ), + ), ); } @@ -638,11 +653,9 @@ class _CupertinoDownGestureDetectorState extends State<_CupertinoDownGestureD void _handleDragUpdate(DragUpdateDetails details) { assert(mounted); assert(_downGestureController != null); - final double topGapRatio = (_kBottomUpTween as Tween).end?.dy ?? 0.08; _downGestureController!.dragUpdate( - // Devide by size of the sheet. The gap between the top of the sheet and - // top of the screen is 0.08. - details.primaryDelta! / (context.size!.height - (context.size!.height * topGapRatio)), + // Divide by size of the sheet. + details.primaryDelta! / (context.size!.height - (context.size!.height * _kTopGapRatio)), ); } diff --git a/packages/flutter/test/cupertino/sheet_test.dart b/packages/flutter/test/cupertino/sheet_test.dart index 217b41c14a5..1b6b8e4bd4d 100644 --- a/packages/flutter/test/cupertino/sheet_test.dart +++ b/packages/flutter/test/cupertino/sheet_test.dart @@ -7,6 +7,9 @@ import 'package:flutter_test/flutter_test.dart'; import '../widgets/navigator_utils.dart'; +// Matches _kTopGapRatio in cupertino/sheet.dart. +const double _kTopGapRatio = 0.08; + void main() { testWidgets('Sheet route does not cover the whole screen', (WidgetTester tester) async { final GlobalKey scaffoldKey = GlobalKey(); @@ -649,6 +652,99 @@ void main() { expect(find.text('Page: /next'), findsOneWidget); }); + testWidgets('content does not go below the bottom of the screen', (WidgetTester tester) async { + final GlobalKey scaffoldKey = GlobalKey(); + + await tester.pumpWidget( + CupertinoApp( + home: CupertinoPageScaffold( + key: scaffoldKey, + child: Center( + child: Column( + children: [ + const Text('Page 1'), + CupertinoButton( + onPressed: () { + Navigator.push( + scaffoldKey.currentContext!, + CupertinoSheetRoute( + builder: (BuildContext context) { + return CupertinoPageScaffold(child: Container()); + }, + ), + ); + }, + child: const Text('Push Page 2'), + ), + ], + ), + ), + ), + ), + ); + + await tester.tap(find.text('Push Page 2')); + await tester.pumpAndSettle(); + + expect(tester.getSize(find.byType(Container)).height, 600.0 - (600.0 * _kTopGapRatio)); + }); + + testWidgets('nested navbars remove MediaQuery top padding', (WidgetTester tester) async { + final GlobalKey scaffoldKey = GlobalKey(); + final GlobalKey appBarKey = GlobalKey(); + final GlobalKey sheetBarKey = GlobalKey(); + + await tester.pumpWidget( + CupertinoApp( + home: MediaQuery( + data: const MediaQueryData(padding: EdgeInsets.fromLTRB(0, 20, 0, 0)), + child: CupertinoPageScaffold( + key: scaffoldKey, + navigationBar: CupertinoNavigationBar( + key: appBarKey, + middle: const Text('Navbar'), + backgroundColor: const Color(0xFFF8F8F8), + ), + child: Center( + child: Column( + children: [ + const Text('Page 1'), + CupertinoButton( + onPressed: () { + Navigator.push( + scaffoldKey.currentContext!, + CupertinoSheetRoute( + builder: (BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + key: sheetBarKey, + middle: const Text('Navbar'), + ), + child: Container(), + ); + }, + ), + ); + }, + child: const Text('Push Page 2'), + ), + ], + ), + ), + ), + ), + ), + ); + final double homeNavBardHeight = tester.getSize(find.byKey(appBarKey)).height; + + await tester.tap(find.text('Push Page 2')); + await tester.pumpAndSettle(); + + final double sheetNavBarHeight = tester.getSize(find.byKey(sheetBarKey)).height; + + expect(sheetNavBarHeight, lessThan(homeNavBardHeight)); + }); + group('drag dismiss gesture', () { Widget dragGestureApp(GlobalKey homeScaffoldKey, GlobalKey sheetScaffoldKey) { return CupertinoApp(