Skip to content

Commit

Permalink
Add Scaffold drawers escape dismiss action. (#106186)
Browse files Browse the repository at this point in the history
  • Loading branch information
TahaTesser authored Jun 29, 2022
1 parent 1d5c189 commit 8fe82bb
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 36 deletions.
5 changes: 3 additions & 2 deletions packages/flutter/lib/src/material/drawer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,9 @@ const Duration _kBaseSettleDuration = Duration(milliseconds: 246);
/// ```
/// {@end-tool}
///
/// An open drawer can be closed by calling [Navigator.pop]. For example
/// a drawer item might close the drawer when tapped:
/// An open drawer may be closed with a swipe to close gesture, pressing the
/// the escape key, by tapping the scrim, or by calling pop route function such as
/// [Navigator.pop]. For example a drawer item might close the drawer when tapped:
///
/// ```dart
/// ListTile(
Expand Down
62 changes: 42 additions & 20 deletions packages/flutter/lib/src/material/scaffold.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1614,8 +1614,8 @@ class Scaffold extends StatefulWidget {
///
/// To open the drawer, use the [ScaffoldState.openDrawer] function.
///
/// To close the drawer, use either [ScaffoldState.closeDrawer] or
/// [Navigator.pop].
/// To close the drawer, use either [ScaffoldState.closeDrawer], [Navigator.pop]
/// or press the escape key on the keyboard.
///
/// {@tool dartpad}
/// To disable the drawer edge swipe on mobile, set the
Expand All @@ -1638,8 +1638,8 @@ class Scaffold extends StatefulWidget {
///
/// To open the drawer, use the [ScaffoldState.openEndDrawer] function.
///
/// To close the drawer, use either [ScaffoldState.closeEndDrawer] or
/// [Navigator.pop].
/// To close the drawer, use either [ScaffoldState.closeEndDrawer], [Navigator.pop]
/// or press the escape key on the keyboard.
///
/// {@tool dartpad}
/// To disable the drawer edge swipe, set the
Expand Down Expand Up @@ -2875,23 +2875,28 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
child: Material(
color: widget.backgroundColor ?? themeData.scaffoldBackgroundColor,
child: AnimatedBuilder(animation: _floatingActionButtonMoveController, builder: (BuildContext context, Widget? child) {
return CustomMultiChildLayout(
delegate: _ScaffoldLayout(
extendBody: extendBody,
extendBodyBehindAppBar: widget.extendBodyBehindAppBar,
minInsets: minInsets,
minViewPadding: minViewPadding,
currentFloatingActionButtonLocation: _floatingActionButtonLocation!,
floatingActionButtonMoveAnimationProgress: _floatingActionButtonMoveController.value,
floatingActionButtonMotionAnimator: _floatingActionButtonAnimator,
geometryNotifier: _geometryNotifier,
previousFloatingActionButtonLocation: _previousFloatingActionButtonLocation!,
textDirection: textDirection,
isSnackBarFloating: isSnackBarFloating,
extendBodyBehindMaterialBanner: extendBodyBehindMaterialBanner,
snackBarWidth: snackBarWidth,
return Actions(
actions: <Type, Action<Intent>>{
DismissIntent: _DismissDrawerAction(context),
},
child: CustomMultiChildLayout(
delegate: _ScaffoldLayout(
extendBody: extendBody,
extendBodyBehindAppBar: widget.extendBodyBehindAppBar,
minInsets: minInsets,
minViewPadding: minViewPadding,
currentFloatingActionButtonLocation: _floatingActionButtonLocation!,
floatingActionButtonMoveAnimationProgress: _floatingActionButtonMoveController.value,
floatingActionButtonMotionAnimator: _floatingActionButtonAnimator,
geometryNotifier: _geometryNotifier,
previousFloatingActionButtonLocation: _previousFloatingActionButtonLocation!,
textDirection: textDirection,
isSnackBarFloating: isSnackBarFloating,
extendBodyBehindMaterialBanner: extendBodyBehindMaterialBanner,
snackBarWidth: snackBarWidth,
),
children: children,
),
children: children,
);
}),
),
Expand All @@ -2900,6 +2905,23 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
}
}

class _DismissDrawerAction extends DismissAction {
_DismissDrawerAction(this.context);

final BuildContext context;

@override
bool isEnabled(DismissIntent intent) {
return Scaffold.of(context).isDrawerOpen || Scaffold.of(context).isEndDrawerOpen;
}

@override
void invoke(DismissIntent intent) {
Scaffold.of(context).closeDrawer();
Scaffold.of(context).closeEndDrawer();
}
}

/// An interface for controlling a feature of a [Scaffold].
///
/// Commonly obtained from [ScaffoldMessengerState.showSnackBar] or
Expand Down
2 changes: 2 additions & 0 deletions packages/flutter/test/material/debug_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,8 @@ void main() {
' MediaQuery\n'
' LayoutId-[<_ScaffoldSlot.snackBar>]\n'
' CustomMultiChildLayout\n'
' _ActionsMarker\n'
' Actions\n'
' AnimatedBuilder\n'
' DefaultTextStyle\n'
' AnimatedDefaultTextStyle\n'
Expand Down
79 changes: 65 additions & 14 deletions packages/flutter/test/material/scaffold_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';

import '../widgets/semantics_tester.dart';
Expand Down Expand Up @@ -37,17 +38,17 @@ void main() {

scaffoldState.openDrawer();
await tester.pumpAndSettle();
expect(true, isDrawerOpen);
expect(isDrawerOpen, true);
scaffoldState.openEndDrawer();
await tester.pumpAndSettle();
expect(false, isDrawerOpen);
expect(isDrawerOpen, false);

scaffoldState.openEndDrawer();
await tester.pumpAndSettle();
expect(true, isEndDrawerOpen);
expect(isEndDrawerOpen, true);
scaffoldState.openDrawer();
await tester.pumpAndSettle();
expect(false, isEndDrawerOpen);
expect(isEndDrawerOpen, false);
});

testWidgets('Scaffold drawer callback test - only call when changed', (WidgetTester tester) async {
Expand All @@ -74,14 +75,14 @@ void main() {
));

await tester.flingFrom(Offset.zero, const Offset(10.0, 0.0), 10.0);
expect(false, onDrawerChangedCalled);
expect(onDrawerChangedCalled, false);

await tester.pumpAndSettle();

final double width = tester.getSize(find.byType(MaterialApp)).width;
await tester.flingFrom(Offset(width - 1, 0.0), const Offset(-10.0, 0.0), 10.0);
await tester.pumpAndSettle();
expect(false, onEndDrawerChangedCalled);
expect(onEndDrawerChangedCalled, false);
});

testWidgets('Scaffold control test', (WidgetTester tester) async {
Expand Down Expand Up @@ -1572,29 +1573,29 @@ void main() {

await tester.tap(drawerOpenButton);
await tester.pumpAndSettle();
expect(true, scaffoldState.isDrawerOpen);
expect(scaffoldState.isDrawerOpen, true);
await tester.tap(endDrawerOpenButton, warnIfMissed: false); // hits the modal barrier
await tester.pumpAndSettle();
expect(false, scaffoldState.isDrawerOpen);
expect(scaffoldState.isDrawerOpen, false);

await tester.tap(endDrawerOpenButton);
await tester.pumpAndSettle();
expect(true, scaffoldState.isEndDrawerOpen);
expect(scaffoldState.isEndDrawerOpen, true);
await tester.tap(drawerOpenButton, warnIfMissed: false); // hits the modal barrier
await tester.pumpAndSettle();
expect(false, scaffoldState.isEndDrawerOpen);
expect(scaffoldState.isEndDrawerOpen, false);

scaffoldState.openDrawer();
expect(true, scaffoldState.isDrawerOpen);
expect(scaffoldState.isDrawerOpen, true);
await tester.tap(endDrawerOpenButton, warnIfMissed: false); // hits the modal barrier
await tester.pumpAndSettle();
expect(false, scaffoldState.isDrawerOpen);
expect(scaffoldState.isDrawerOpen, false);

scaffoldState.openEndDrawer();
expect(true, scaffoldState.isEndDrawerOpen);
expect(scaffoldState.isEndDrawerOpen, true);

scaffoldState.openDrawer();
expect(true, scaffoldState.isDrawerOpen);
expect(scaffoldState.isDrawerOpen, true);
});

testWidgets('Dual Drawer Opening', (WidgetTester tester) async {
Expand Down Expand Up @@ -2405,6 +2406,8 @@ void main() {
' MediaQuery\n'
' LayoutId-[<_ScaffoldSlot.body>]\n'
' CustomMultiChildLayout\n'
' _ActionsMarker\n'
' Actions\n'
' AnimatedBuilder\n'
' DefaultTextStyle\n'
' AnimatedDefaultTextStyle\n'
Expand Down Expand Up @@ -2497,6 +2500,54 @@ void main() {
await tester.pumpAndSettle();
expect(find.text(snackBarContent), findsNothing);
});

testWidgets('Drawer can be dismissed with escape keyboard shortcut', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/106131
bool isDrawerOpen = false;
bool isEndDrawerOpen = false;

await tester.pumpWidget(MaterialApp(
home: Scaffold(
drawer: Container(
color: Colors.blue,
),
onDrawerChanged: (bool isOpen) {
isDrawerOpen = isOpen;
},
endDrawer: Container(
color: Colors.green,
),
onEndDrawerChanged: (bool isOpen) {
isEndDrawerOpen = isOpen;
},
body: Container(),
),
));

final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold));

scaffoldState.openDrawer();
await tester.pumpAndSettle();
expect(isDrawerOpen, true);
expect(isEndDrawerOpen, false);

// Try to dismiss the drawer with the shortcut key
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
await tester.pumpAndSettle();
expect(isDrawerOpen, false);
expect(isEndDrawerOpen, false);

scaffoldState.openEndDrawer();
await tester.pumpAndSettle();
expect(isDrawerOpen, false);
expect(isEndDrawerOpen, true);

// Try to dismiss the drawer with the shortcut key
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
await tester.pumpAndSettle();
expect(isDrawerOpen, false);
expect(isEndDrawerOpen, false);
});
}

class _GeometryListener extends StatefulWidget {
Expand Down

0 comments on commit 8fe82bb

Please sign in to comment.