diff --git a/packages/flutter/lib/src/cupertino/sheet.dart b/packages/flutter/lib/src/cupertino/sheet.dart index 1016c2a8d5..e020f87dbe 100644 --- a/packages/flutter/lib/src/cupertino/sheet.dart +++ b/packages/flutter/lib/src/cupertino/sheet.dart @@ -104,6 +104,11 @@ final Animatable _kScaleTween = Tween(begin: 1.0, end: 1.0 - _kS /// The whole sheet can be popped at once by either dragging down on the sheet, /// or calling [CupertinoSheetRoute.popSheet]. /// +/// When `enableDrag` is set to `true` (the default), users can dismiss the sheet +/// by dragging it down or by calling [CupertinoSheetRoute.popSheet]. When +/// `enableDrag` is `false`, users cannot dismiss the sheet by dragging, and it +/// can only be closed by calling [CupertinoSheetRoute.popSheet]. +/// /// iOS sheet widgets are generally designed to be tightly coupled to the context /// of the widget that opened the sheet. As such, it is not recommended to push /// a non-sheet route that covers the sheet without first popping the sheet. If @@ -135,6 +140,7 @@ Future showCupertinoSheet({ required BuildContext context, required WidgetBuilder pageBuilder, bool useNestedNavigation = false, + bool enableDrag = true, }) { final WidgetBuilder builder; final GlobalKey nestedNavigatorKey = GlobalKey(); @@ -175,7 +181,7 @@ Future showCupertinoSheet({ return Navigator.of( context, rootNavigator: true, - ).push(CupertinoSheetRoute(builder: builder)); + ).push(CupertinoSheetRoute(builder: builder, enableDrag: enableDrag)); } /// Provides an iOS-style sheet transition. @@ -478,15 +484,17 @@ class _CupertinoSheetTransitionState extends State { /// `CupertinoSheetRoute`, with optional nested navigation built in. class CupertinoSheetRoute extends PageRoute with _CupertinoSheetRouteTransitionMixin { /// Creates a page route that displays an iOS styled sheet. - CupertinoSheetRoute({super.settings, required this.builder}); + CupertinoSheetRoute({super.settings, required this.builder, this.enableDrag = true}); /// Builds the primary contents of the sheet route. final WidgetBuilder builder; + @override + final bool enableDrag; + @override Widget buildContent(BuildContext context) { final double bottomPadding = MediaQuery.sizeOf(context).height * _kTopGapRatio; - return MediaQuery.removePadding( context: context, removeTop: true, @@ -563,6 +571,11 @@ mixin _CupertinoSheetRouteTransitionMixin on PageRoute { DelegatedTransitionBuilder? get delegatedTransition => CupertinoSheetTransition.delegateTransition; + /// Determines whether the content can be dragged. + /// + /// If `true`, dragging is enabled; otherwise, it remains fixed. + bool get enableDrag; + @override Widget buildPage( BuildContext context, @@ -588,6 +601,7 @@ mixin _CupertinoSheetRouteTransitionMixin on PageRoute { Animation animation, Animation secondaryAnimation, Widget child, + bool enableDrag, ) { final bool linearTransition = route.popGestureInProgress; return CupertinoSheetTransition( @@ -595,7 +609,7 @@ mixin _CupertinoSheetRouteTransitionMixin on PageRoute { secondaryRouteAnimation: secondaryAnimation, linearTransition: linearTransition, child: _CupertinoDownGestureDetector( - enabledCallback: () => true, + enabledCallback: () => enableDrag, onStartPopGesture: () => _startPopGesture(route), child: child, ), @@ -614,7 +628,7 @@ mixin _CupertinoSheetRouteTransitionMixin on PageRoute { Animation secondaryAnimation, Widget child, ) { - return buildPageTransitions(this, context, animation, secondaryAnimation, child); + return buildPageTransitions(this, context, animation, secondaryAnimation, child, enableDrag); } } diff --git a/packages/flutter/test/cupertino/sheet_test.dart b/packages/flutter/test/cupertino/sheet_test.dart index da8ad9f1cf..42a671a76b 100644 --- a/packages/flutter/test/cupertino/sheet_test.dart +++ b/packages/flutter/test/cupertino/sheet_test.dart @@ -1076,5 +1076,74 @@ void main() { await gesture.up(); await tester.pumpAndSettle(); }); + + testWidgets('dragging does not move the sheet when enableDrag is false', ( + WidgetTester tester, + ) async { + Widget nonDragGestureApp(GlobalKey homeScaffoldKey, GlobalKey sheetScaffoldKey) { + return CupertinoApp( + home: CupertinoPageScaffold( + key: homeScaffoldKey, + child: Center( + child: Column( + children: [ + const Text('Page 1'), + CupertinoButton( + onPressed: () { + showCupertinoSheet( + context: homeScaffoldKey.currentContext!, + pageBuilder: (BuildContext context) { + return CupertinoPageScaffold( + key: sheetScaffoldKey, + child: const Center(child: Text('Page 2')), + ); + }, + enableDrag: false, + ); + }, + child: const Text('Push Page 2'), + ), + ], + ), + ), + ), + ); + } + + final GlobalKey homeKey = GlobalKey(); + final GlobalKey sheetKey = GlobalKey(); + + await tester.pumpWidget(nonDragGestureApp(homeKey, sheetKey)); + + await tester.tap(find.text('Push Page 2')); + await tester.pumpAndSettle(); + + expect(find.text('Page 2'), findsOneWidget); + + RenderBox box = tester.renderObject(find.byKey(sheetKey)) as RenderBox; + final double initialPosition = box.localToGlobal(Offset.zero).dy; + + final TestGesture gesture = await tester.startGesture(const Offset(100, 200)); + // Partial drag down + await gesture.moveBy(const Offset(0, 200)); + await tester.pump(); + + // Release gesture. Sheet should not move. + box = tester.renderObject(find.byKey(sheetKey)) as RenderBox; + final double middlePosition = box.localToGlobal(Offset.zero).dy; + + expect(middlePosition, equals(initialPosition)); + + await gesture.up(); + await tester.pumpAndSettle(); + + expect(find.text('Page 2'), findsOneWidget); + + box = tester.renderObject(find.byKey(sheetKey)) as RenderBox; + final double finalPosition = box.localToGlobal(Offset.zero).dy; + + expect(finalPosition, equals(middlePosition)); + expect(finalPosition, equals(initialPosition)); + }); }); }