Skip to content

Commit dd9764e

Browse files
authored
Proposal to add barrier configs for showDatePicker, showTimePicker and showAboutDialog. (flutter#131306)
Can configure modal barriers in Flutter's built-in dialogs.
1 parent 4e609f1 commit dd9764e

File tree

7 files changed

+564
-14
lines changed

7 files changed

+564
-14
lines changed

packages/flutter/lib/src/material/about.dart

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,22 +170,28 @@ class AboutListTile extends StatelessWidget {
170170
/// The licenses shown on the [LicensePage] are those returned by the
171171
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
172172
///
173-
/// The [context], [useRootNavigator], [routeSettings] and [anchorPoint]
174-
/// arguments are passed to [showDialog], the documentation for which discusses
175-
/// how it is used.
173+
/// The [context], [barrierDismissible], [barrierColor], [barrierLabel],
174+
/// [useRootNavigator], [routeSettings] and [anchorPoint] arguments are
175+
/// passed to [showDialog], the documentation for which discusses how it is used.
176176
void showAboutDialog({
177177
required BuildContext context,
178178
String? applicationName,
179179
String? applicationVersion,
180180
Widget? applicationIcon,
181181
String? applicationLegalese,
182182
List<Widget>? children,
183+
bool barrierDismissible = true,
184+
Color? barrierColor,
185+
String? barrierLabel,
183186
bool useRootNavigator = true,
184187
RouteSettings? routeSettings,
185188
Offset? anchorPoint,
186189
}) {
187190
showDialog<void>(
188191
context: context,
192+
barrierDismissible: barrierDismissible,
193+
barrierColor: barrierColor,
194+
barrierLabel: barrierLabel,
189195
useRootNavigator: useRootNavigator,
190196
builder: (BuildContext context) {
191197
return AboutDialog(

packages/flutter/lib/src/material/date_picker.dart

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,10 @@ const double _kMaxTextScaleFactor = 1.3;
113113
/// [locale] and [textDirection] are non-null, [textDirection] overrides the
114114
/// direction chosen for the [locale].
115115
///
116-
/// The [context], [useRootNavigator] and [routeSettings] arguments are passed to
117-
/// [showDialog], the documentation for which discusses how it is used. [context]
118-
/// and [useRootNavigator] must be non-null.
116+
/// The [context], [barrierDismissible], [barrierColor], [barrierLabel],
117+
/// [useRootNavigator] and [routeSettings] arguments are passed to [showDialog],
118+
/// the documentation for which discusses how it is used.
119+
/// [context], [barrierDismissible] and [useRootNavigator] must be non-null.
119120
///
120121
/// The [builder] parameter can be used to wrap the dialog widget
121122
/// to add inherited widgets like [Theme].
@@ -169,6 +170,9 @@ Future<DateTime?> showDatePicker({
169170
String? cancelText,
170171
String? confirmText,
171172
Locale? locale,
173+
bool barrierDismissible = true,
174+
Color? barrierColor,
175+
String? barrierLabel,
172176
bool useRootNavigator = true,
173177
RouteSettings? routeSettings,
174178
TextDirection? textDirection,
@@ -243,6 +247,9 @@ Future<DateTime?> showDatePicker({
243247

244248
return showDialog<DateTime>(
245249
context: context,
250+
barrierDismissible: barrierDismissible,
251+
barrierColor: barrierColor,
252+
barrierLabel: barrierLabel,
246253
useRootNavigator: useRootNavigator,
247254
routeSettings: routeSettings,
248255
builder: (BuildContext context) {
@@ -967,9 +974,10 @@ class _DatePickerHeader extends StatelessWidget {
967974
/// [locale] and [textDirection] are non-null, [textDirection] overrides the
968975
/// direction chosen for the [locale].
969976
///
970-
/// The [context], [useRootNavigator] and [routeSettings] arguments are passed
971-
/// to [showDialog], the documentation for which discusses how it is used.
972-
/// [context] and [useRootNavigator] must be non-null.
977+
/// The [context], [barrierDismissible], [barrierColor], [barrierLabel],
978+
/// [useRootNavigator] and [routeSettings] arguments are passed to [showDialog],
979+
/// the documentation for which discusses how it is used.
980+
/// [context], [barrierDismissible] and [useRootNavigator] must be non-null.
973981
///
974982
/// The [builder] parameter can be used to wrap the dialog widget
975983
/// to add inherited widgets like [Theme].
@@ -1022,6 +1030,9 @@ Future<DateTimeRange?> showDateRangePicker({
10221030
String? fieldStartLabelText,
10231031
String? fieldEndLabelText,
10241032
Locale? locale,
1033+
bool barrierDismissible = true,
1034+
Color? barrierColor,
1035+
String? barrierLabel,
10251036
bool useRootNavigator = true,
10261037
RouteSettings? routeSettings,
10271038
TextDirection? textDirection,
@@ -1100,6 +1111,9 @@ Future<DateTimeRange?> showDateRangePicker({
11001111

11011112
return showDialog<DateTimeRange>(
11021113
context: context,
1114+
barrierDismissible: barrierDismissible,
1115+
barrierColor: barrierColor,
1116+
barrierLabel: barrierLabel,
11031117
useRootNavigator: useRootNavigator,
11041118
routeSettings: routeSettings,
11051119
useSafeArea: false,

packages/flutter/lib/src/material/dialog.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,7 +1403,7 @@ Future<T?> showDialog<T>({
14031403
required BuildContext context,
14041404
required WidgetBuilder builder,
14051405
bool barrierDismissible = true,
1406-
Color? barrierColor = Colors.black54,
1406+
Color? barrierColor,
14071407
String? barrierLabel,
14081408
bool useSafeArea = true,
14091409
bool useRootNavigator = true,
@@ -1425,7 +1425,7 @@ Future<T?> showDialog<T>({
14251425
return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(DialogRoute<T>(
14261426
context: context,
14271427
builder: builder,
1428-
barrierColor: barrierColor,
1428+
barrierColor: barrierColor ?? Colors.black54,
14291429
barrierDismissible: barrierDismissible,
14301430
barrierLabel: barrierLabel,
14311431
useSafeArea: useSafeArea,
@@ -1448,7 +1448,7 @@ Future<T?> showAdaptiveDialog<T>({
14481448
required BuildContext context,
14491449
required WidgetBuilder builder,
14501450
bool? barrierDismissible,
1451-
Color? barrierColor = Colors.black54,
1451+
Color? barrierColor,
14521452
String? barrierLabel,
14531453
bool useSafeArea = true,
14541454
bool useRootNavigator = true,

packages/flutter/lib/src/material/time_picker.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2874,8 +2874,9 @@ class _TimePickerState extends State<_TimePicker> with RestorationMixin {
28742874
/// ```
28752875
/// {@end-tool}
28762876
///
2877-
/// The [context], [useRootNavigator] and [routeSettings] arguments are passed
2878-
/// to [showDialog], the documentation for which discusses how it is used.
2877+
/// The [context], [barrierDismissible], [barrierColor], [barrierLabel],
2878+
/// [useRootNavigator] and [routeSettings] arguments are passed to [showDialog],
2879+
/// the documentation for which discusses how it is used.
28792880
///
28802881
/// The [builder] parameter can be used to wrap the dialog widget to add
28812882
/// inherited widgets like [Localizations.override], [Directionality], or
@@ -2954,6 +2955,9 @@ Future<TimeOfDay?> showTimePicker({
29542955
required BuildContext context,
29552956
required TimeOfDay initialTime,
29562957
TransitionBuilder? builder,
2958+
bool barrierDismissible = true,
2959+
Color? barrierColor,
2960+
String? barrierLabel,
29572961
bool useRootNavigator = true,
29582962
TimePickerEntryMode initialEntryMode = TimePickerEntryMode.dial,
29592963
String? cancelText,
@@ -2983,6 +2987,9 @@ Future<TimeOfDay?> showTimePicker({
29832987
);
29842988
return showDialog<TimeOfDay>(
29852989
context: context,
2990+
barrierDismissible: barrierDismissible,
2991+
barrierColor: barrierColor,
2992+
barrierLabel: barrierLabel,
29862993
useRootNavigator: useRootNavigator,
29872994
builder: (BuildContext context) {
29882995
return builder == null ? dialog : builder(context, dialog);

packages/flutter/test/material/about_test.dart

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,160 @@ void main() {
742742
expect(nestedObserver.licensePageCount, 0);
743743
});
744744

745+
group('Barrier dismissible', () {
746+
late AboutDialogObserver rootObserver;
747+
748+
setUp(() {
749+
rootObserver = AboutDialogObserver();
750+
});
751+
752+
testWidgets('Barrier is dismissible with default parameter', (WidgetTester tester) async {
753+
await tester.pumpWidget(
754+
MaterialApp(
755+
navigatorObservers: <NavigatorObserver>[rootObserver],
756+
home: Material(
757+
child: Center(
758+
child: Builder(
759+
builder: (BuildContext context) {
760+
return ElevatedButton(
761+
child: const Text('X'),
762+
onPressed: () => showAboutDialog(
763+
context: context,
764+
),
765+
);
766+
},
767+
),
768+
),
769+
),
770+
),
771+
);
772+
773+
// Open the dialog.
774+
await tester.tap(find.byType(ElevatedButton));
775+
await tester.pumpAndSettle();
776+
expect(rootObserver.dialogCount, 1);
777+
778+
// Tap on the barrier.
779+
await tester.tapAt(const Offset(10.0, 10.0));
780+
await tester.pumpAndSettle();
781+
expect(rootObserver.dialogCount, 0);
782+
});
783+
784+
testWidgets('Barrier is not dismissible with barrierDismissible is false', (WidgetTester tester) async {
785+
await tester.pumpWidget(
786+
MaterialApp(
787+
navigatorObservers: <NavigatorObserver>[rootObserver],
788+
home: Material(
789+
child: Center(
790+
child: Builder(
791+
builder: (BuildContext context) {
792+
return ElevatedButton(
793+
child: const Text('X'),
794+
onPressed: () => showAboutDialog(
795+
context: context,
796+
barrierDismissible: false
797+
),
798+
);
799+
},
800+
),
801+
),
802+
),
803+
),
804+
);
805+
806+
// Open the dialog.
807+
await tester.tap(find.byType(ElevatedButton));
808+
await tester.pumpAndSettle();
809+
expect(rootObserver.dialogCount, 1);
810+
811+
// Tap on the barrier, which shouldn't do anything this time.
812+
await tester.tapAt(const Offset(10.0, 10.0));
813+
await tester.pumpAndSettle();
814+
expect(rootObserver.dialogCount, 1);
815+
});
816+
});
817+
818+
testWidgets('Barrier color', (WidgetTester tester) async {
819+
await tester.pumpWidget(
820+
MaterialApp(
821+
home: Material(
822+
child: Center(
823+
child: Builder(
824+
builder: (BuildContext context) {
825+
return ElevatedButton(
826+
child: const Text('X'),
827+
onPressed: () => showAboutDialog(
828+
context: context,
829+
),
830+
);
831+
},
832+
),
833+
),
834+
),
835+
),
836+
);
837+
838+
// Open the dialog.
839+
await tester.tap(find.byType(ElevatedButton));
840+
await tester.pumpAndSettle();
841+
expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, Colors.black54);
842+
843+
// Dismiss the dialog.
844+
await tester.tapAt(const Offset(10.0, 10.0));
845+
846+
await tester.pumpWidget(
847+
MaterialApp(
848+
home: Material(
849+
child: Center(
850+
child: Builder(
851+
builder: (BuildContext context) {
852+
return ElevatedButton(
853+
child: const Text('X'),
854+
onPressed: () => showAboutDialog(
855+
context: context,
856+
barrierColor: Colors.pink,
857+
),
858+
);
859+
},
860+
),
861+
),
862+
),
863+
),
864+
);
865+
866+
// Open the dialog.
867+
await tester.tap(find.byType(ElevatedButton));
868+
await tester.pumpAndSettle();
869+
expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, Colors.pink);
870+
});
871+
872+
testWidgets('Barrier Label', (WidgetTester tester) async {
873+
await tester.pumpWidget(
874+
MaterialApp(
875+
home: Material(
876+
child: Center(
877+
child: Builder(
878+
builder: (BuildContext context) {
879+
return ElevatedButton(
880+
child: const Text('X'),
881+
onPressed: () => showAboutDialog(
882+
context: context,
883+
barrierLabel: 'Custom Label',
884+
),
885+
);
886+
},
887+
),
888+
),
889+
),
890+
),
891+
);
892+
893+
// Open the dialog.
894+
await tester.tap(find.byType(ElevatedButton));
895+
await tester.pumpAndSettle();
896+
expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).semanticsLabel, 'Custom Label');
897+
});
898+
745899
testWidgetsWithLeakTracking('showAboutDialog uses root navigator by default', (WidgetTester tester) async {
746900
final AboutDialogObserver rootObserver = AboutDialogObserver();
747901
final AboutDialogObserver nestedObserver = AboutDialogObserver();
@@ -1741,4 +1895,12 @@ class AboutDialogObserver extends NavigatorObserver {
17411895
}
17421896
super.didPush(route, previousRoute);
17431897
}
1898+
1899+
@override
1900+
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
1901+
if (route is DialogRoute) {
1902+
dialogCount--;
1903+
}
1904+
super.didPop(route, previousRoute);
1905+
}
17441906
}

0 commit comments

Comments
 (0)