diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index 8f708aa49fe..86b0250b6ba 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart @@ -565,6 +565,19 @@ class DrawerControllerState extends State with SingleTickerPro final bool drawerIsStart = widget.alignment == DrawerAlignment.start; final EdgeInsets padding = MediaQuery.of(context).padding; final TextDirection textDirection = Directionality.of(context); + final bool isDesktop; + switch (Theme.of(context).platform) { + case TargetPlatform.android: + case TargetPlatform.iOS: + case TargetPlatform.fuchsia: + isDesktop = false; + break; + case TargetPlatform.macOS: + case TargetPlatform.linux: + case TargetPlatform.windows: + isDesktop = true; + break; + } double? dragAreaWidth = widget.edgeDragWidth; if (widget.edgeDragWidth == null) { @@ -581,7 +594,7 @@ class DrawerControllerState extends State with SingleTickerPro } if (_controller.status == AnimationStatus.dismissed) { - if (widget.enableOpenDragGesture) { + if (widget.enableOpenDragGesture && !isDesktop) { return Align( alignment: _drawerOuterAlignment, child: GestureDetector( @@ -612,6 +625,47 @@ class DrawerControllerState extends State with SingleTickerPro break; } assert(platformHasBackButton != null); + + final Widget child = RepaintBoundary( + child: Stack( + children: [ + BlockSemantics( + child: ExcludeSemantics( + // On Android, the back button is used to dismiss a modal. + excluding: platformHasBackButton, + child: GestureDetector( + onTap: close, + child: Semantics( + label: MaterialLocalizations.of(context).modalBarrierDismissLabel, + child: Container( // The drawer's "scrim" + color: _scrimColorTween.evaluate(_controller), + ), + ), + ), + ), + ), + Align( + alignment: _drawerOuterAlignment, + child: Align( + alignment: _drawerInnerAlignment, + widthFactor: _controller.value, + child: RepaintBoundary( + child: FocusScope( + key: _drawerKey, + node: _focusScopeNode, + child: widget.child, + ), + ), + ), + ), + ], + ), + ); + + if (isDesktop) { + return child; + } + return GestureDetector( key: _gestureDetectorKey, onHorizontalDragDown: _handleDragDown, @@ -620,43 +674,7 @@ class DrawerControllerState extends State with SingleTickerPro onHorizontalDragCancel: _handleDragCancel, excludeFromSemantics: true, dragStartBehavior: widget.dragStartBehavior, - child: RepaintBoundary( - child: Stack( - children: [ - BlockSemantics( - child: ExcludeSemantics( - // On Android, the back button is used to dismiss a modal. - excluding: platformHasBackButton, - child: GestureDetector( - onTap: close, - child: Semantics( - label: MaterialLocalizations.of(context).modalBarrierDismissLabel, - child: MouseRegion( - child: Container( // The drawer's "scrim" - color: _scrimColorTween.evaluate(_controller), - ), - ), - ), - ), - ), - ), - Align( - alignment: _drawerOuterAlignment, - child: Align( - alignment: _drawerInnerAlignment, - widthFactor: _controller.value, - child: RepaintBoundary( - child: FocusScope( - key: _drawerKey, - node: _focusScopeNode, - child: widget.child, - ), - ), - ), - ), - ], - ), - ), + child: child, ); } } diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 7831159f6e5..56260cfe888 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -1617,7 +1617,7 @@ class Scaffold extends StatefulWidget { /// [Navigator.pop]. /// /// {@tool dartpad} - /// To disable the drawer edge swipe, set the + /// To disable the drawer edge swipe on mobile, set the /// [Scaffold.drawerEnableOpenDragGesture] to false. Then, use /// [ScaffoldState.openDrawer] to open the drawer and [Navigator.pop] to close /// it. @@ -1739,15 +1739,19 @@ class Scaffold extends StatefulWidget { final double? drawerEdgeDragWidth; /// Determines if the [Scaffold.drawer] can be opened with a drag - /// gesture. + /// gesture on mobile. /// - /// By default, the drag gesture is enabled. + /// On desktop platforms, the drawer is not draggable. + /// + /// By default, the drag gesture is enabled on mobile. final bool drawerEnableOpenDragGesture; /// Determines if the [Scaffold.endDrawer] can be opened with a - /// drag gesture. + /// gesture on mobile. /// - /// By default, the drag gesture is enabled. + /// On desktop platforms, the drawer is not draggable. + /// + /// By default, the drag gesture is enabled on mobile. final bool endDrawerEnableOpenDragGesture; /// Restoration ID to save and restore the state of the [Scaffold]. diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart index 9b13c8a6c58..f07b4529777 100644 --- a/packages/flutter/test/material/scaffold_test.dart +++ b/packages/flutter/test/material/scaffold_test.dart @@ -1777,7 +1777,7 @@ void main() { expect(scaffoldState.isDrawerOpen, true); }); - testWidgets('Drawer does not open with a drag gesture when it is disabled', (WidgetTester tester) async { + testWidgets('Drawer does not open with a drag gesture when it is disabled on mobile', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( @@ -1839,7 +1839,47 @@ void main() { await tester.dragFrom(const Offset(300, 100), const Offset(-300, 0)); await tester.pumpAndSettle(); expect(scaffoldState.isDrawerOpen, false); - }); + }, variant: TargetPlatformVariant.mobile()); + + testWidgets('Drawer does not open with a drag gesture on dekstop', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + drawer: const Drawer( + child: Text('Drawer'), + ), + body: const Text('Scaffold Body'), + appBar: AppBar( + centerTitle: true, + title: const Text('Title'), + ), + ), + ), + ); + final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold)); + expect(scaffoldState.isDrawerOpen, false); + + // Test that we cannot open the drawer with a drag gesture. + await tester.dragFrom(const Offset(0, 100), const Offset(300, 0)); + await tester.pumpAndSettle(); + expect(scaffoldState.isDrawerOpen, false); + + // Test that we can open the drawer with a tap gesture on drawer icon button. + final Finder drawerOpenButton = find.byType(IconButton).first; + await tester.tap(drawerOpenButton); + await tester.pumpAndSettle(); + expect(scaffoldState.isDrawerOpen, true); + + // Test that we cannot close the drawer with a drag gesture. + await tester.dragFrom(const Offset(300, 100), const Offset(-300, 0)); + await tester.pumpAndSettle(); + expect(scaffoldState.isDrawerOpen, true); + + // Test that we can close the drawer with a tap gesture in the body. + await tester.tapAt(const Offset(500, 300)); + await tester.pumpAndSettle(); + expect(scaffoldState.isDrawerOpen, false); + }, variant: TargetPlatformVariant.desktop()); testWidgets('End drawer does not open with a drag gesture when it is disabled', (WidgetTester tester) async { late double screenWidth;