From 9a18ac28e9eea0f63f7d5188eed563827e420895 Mon Sep 17 00:00:00 2001 From: byshy <33957976+byshy@users.noreply.github.com> Date: Fri, 4 Sep 2026 10:23:56 +0400 Subject: [PATCH] [material_ui] Update modal barrier color handling across components Enhance the handling of modal barrier colors in bottom sheets, dialogs, and drawers to utilize ColorScheme.scrim with an opacity matching Colors.black54 when no specific color is provided. This change ensures a consistent appearance across components and allows for better theme integration. Additionally, tests have been added to verify the new behavior and precedence rules for barrier colors. --- .../material_ui/lib/src/bottom_sheet.dart | 21 +- packages/material_ui/lib/src/dialog.dart | 23 +- packages/material_ui/lib/src/drawer.dart | 7 +- packages/material_ui/lib/src/scaffold.dart | 4 +- .../change_2026_09_04_scrim_color.yaml | 3 + .../material_ui/test/bottom_sheet_test.dart | 216 ++++++++++++++++++ packages/material_ui/test/dialog_test.dart | 180 +++++++++++++++ packages/material_ui/test/drawer_test.dart | 149 ++++++++++++ 8 files changed, 589 insertions(+), 14 deletions(-) create mode 100644 packages/material_ui/pending_changelogs/change_2026_09_04_scrim_color.yaml diff --git a/packages/material_ui/lib/src/bottom_sheet.dart b/packages/material_ui/lib/src/bottom_sheet.dart index 52a81119732a..1d235479b421 100644 --- a/packages/material_ui/lib/src/bottom_sheet.dart +++ b/packages/material_ui/lib/src/bottom_sheet.dart @@ -971,7 +971,9 @@ class ModalBottomSheetRoute extends PopupRoute { /// Specifies the color of the modal barrier that darkens everything below the /// bottom sheet. /// - /// Defaults to `Colors.black54` if not provided. + /// If this is null, then [BottomSheetThemeData.modalBarrierColor] is used. + /// If that is also null, then [ColorScheme.scrim] is used with an opacity + /// matching [Colors.black54]. final Color? modalBarrierColor; /// Specifies whether the bottom sheet will be dismissed @@ -1103,7 +1105,17 @@ class ModalBottomSheetRoute extends PopupRoute { final String? barrierLabel; @override - Color get barrierColor => modalBarrierColor ?? Colors.black54; + Color get barrierColor { + if (modalBarrierColor != null) { + return modalBarrierColor!; + } + if (navigator != null) { + final ThemeData theme = Theme.of(navigator!.context); + return theme.bottomSheetTheme.modalBarrierColor ?? + theme.colorScheme.scrim.withValues(alpha: Colors.black54.a); + } + return Colors.black54; + } AnimationController? _animationController; @@ -1343,7 +1355,10 @@ Future showModalBottomSheet({ clipBehavior: clipBehavior, constraints: constraints, isDismissible: isDismissible, - modalBarrierColor: barrierColor ?? Theme.of(context).bottomSheetTheme.modalBarrierColor, + modalBarrierColor: + barrierColor ?? + Theme.of(context).bottomSheetTheme.modalBarrierColor ?? + Theme.of(context).colorScheme.scrim.withValues(alpha: Colors.black54.a), enableDrag: enableDrag, showDragHandle: showDragHandle, settings: routeSettings, diff --git a/packages/material_ui/lib/src/dialog.dart b/packages/material_ui/lib/src/dialog.dart index 63f144562a9f..6f9417f3826a 100644 --- a/packages/material_ui/lib/src/dialog.dart +++ b/packages/material_ui/lib/src/dialog.dart @@ -1569,10 +1569,11 @@ class _DialogContentPage extends Page { /// /// The `barrierColor` argument is used to specify the color of the modal /// barrier that darkens everything below the dialog. If `null` the `barrierColor` -/// field from `DialogThemeData` is used. If that is `null` the default color -/// `Colors.black54` is used. If windowing is enabled via `flutter config -/// --enable-windowing`, then this argument is ignored as dialogs are displayed -/// in their own windows which do not have a modal barrier. +/// field from `DialogThemeData` is used. If that is also `null`, +/// [ColorScheme.scrim] is used with an opacity matching [Colors.black54]. +/// If windowing is enabled via `flutter config --enable-windowing`, then this +/// argument is ignored as dialogs are displayed in their own windows which do +/// not have a modal barrier. /// /// The `useSafeArea` argument is used to indicate if the dialog should only /// display in 'safe' areas of the screen not used by the operating system @@ -1706,7 +1707,7 @@ Future showDialog({ barrierColor ?? DialogTheme.of(context).barrierColor ?? Theme.of(context).dialogTheme.barrierColor ?? - Colors.black54, + Theme.of(context).colorScheme.scrim.withValues(alpha: Colors.black54.a), barrierDismissible: barrierDismissible, barrierLabel: barrierLabel, useSafeArea: useSafeArea, @@ -1843,8 +1844,9 @@ bool _debugIsActive(BuildContext context) { /// barrier will dismiss the dialog. It is `true` by default and cannot be `null`. /// /// The `barrierColor` argument is used to specify the color of the modal -/// barrier that darkens everything below the dialog. If `null`, the default -/// color `Colors.black54` is used. +/// barrier that darkens everything below the dialog. If `null`, the +/// `barrierColor` field from [DialogThemeData] is used. If that is also `null`, +/// [ColorScheme.scrim] is used with an opacity matching [Colors.black54]. /// /// The `useSafeArea` argument is used to indicate if the dialog should only /// display in 'safe' areas of the screen not used by the operating system @@ -1872,7 +1874,7 @@ class DialogRoute extends RawDialogRoute { required BuildContext context, required WidgetBuilder builder, CapturedThemes? themes, - super.barrierColor = Colors.black54, + Color? barrierColor, super.barrierDismissible, String? barrierLabel, bool useSafeArea = true, @@ -1884,6 +1886,11 @@ class DialogRoute extends RawDialogRoute { AnimationStyle? animationStyle, }) : _animationStyle = animationStyle, super( + barrierColor: + barrierColor ?? + DialogTheme.of(context).barrierColor ?? + Theme.of(context).dialogTheme.barrierColor ?? + Theme.of(context).colorScheme.scrim.withValues(alpha: Colors.black54.a), pageBuilder: ( BuildContext buildContext, diff --git a/packages/material_ui/lib/src/drawer.dart b/packages/material_ui/lib/src/drawer.dart index f2d8b55ce525..bf2a389aa780 100644 --- a/packages/material_ui/lib/src/drawer.dart +++ b/packages/material_ui/lib/src/drawer.dart @@ -398,7 +398,8 @@ class DrawerController extends StatefulWidget { /// a drawer is open. /// /// If this is null, then [DrawerThemeData.scrimColor] is used. If that - /// is also null, then it defaults to [Colors.black54]. + /// is also null, then [ColorScheme.scrim] is used with an opacity matching + /// [Colors.black54]. final Color? scrimColor; /// Determines if the [Drawer] can be opened with a drag gesture. @@ -713,7 +714,9 @@ class DrawerControllerState extends State with SingleTickerPro }; final Color scrimColor = - widget.scrimColor ?? DrawerTheme.of(context).scrimColor ?? Colors.black54; + widget.scrimColor ?? + DrawerTheme.of(context).scrimColor ?? + Theme.of(context).colorScheme.scrim.withValues(alpha: Colors.black54.a); final Color effectiveScrimColor = scrimColor.withValues( alpha: scrimColor.a * _controller.value, ); diff --git a/packages/material_ui/lib/src/scaffold.dart b/packages/material_ui/lib/src/scaffold.dart index 168fc5b7ab9f..f7e1826caf4c 100644 --- a/packages/material_ui/lib/src/scaffold.dart +++ b/packages/material_ui/lib/src/scaffold.dart @@ -8,6 +8,7 @@ /// @docImport 'bottom_app_bar.dart'; /// @docImport 'bottom_navigation_bar.dart'; /// @docImport 'bottom_sheet_theme.dart'; +/// @docImport 'color_scheme.dart'; /// @docImport 'drawer_theme.dart'; /// @docImport 'icon_button.dart'; /// @docImport 'tab_controller.dart'; @@ -1955,7 +1956,8 @@ class Scaffold extends StatefulWidget { /// The color to use for the scrim that obscures primary content while a drawer is open. /// /// If this is null, then [DrawerThemeData.scrimColor] is used. If that - /// is also null, then it defaults to [Colors.black54]. + /// is also null, then [ColorScheme.scrim] is used with an opacity matching + /// [Colors.black54]. final Color? drawerScrimColor; /// A builder for the widget that obscures primary content while a bottom sheet is open. diff --git a/packages/material_ui/pending_changelogs/change_2026_09_04_scrim_color.yaml b/packages/material_ui/pending_changelogs/change_2026_09_04_scrim_color.yaml new file mode 100644 index 000000000000..bb89f1513613 --- /dev/null +++ b/packages/material_ui/pending_changelogs/change_2026_09_04_scrim_color.yaml @@ -0,0 +1,3 @@ +changelog: | + - Uses `ColorScheme.scrim` as the default fallback for dialog, modal bottom sheet, and drawer barrier/scrim colors. +version: patch diff --git a/packages/material_ui/test/bottom_sheet_test.dart b/packages/material_ui/test/bottom_sheet_test.dart index 59a118eb777e..83d47123e31b 100644 --- a/packages/material_ui/test/bottom_sheet_test.dart +++ b/packages/material_ui/test/bottom_sheet_test.dart @@ -1018,6 +1018,222 @@ void main() { expect(modalBarrier.color, barrierColor); }); + testWidgets('Modal bottom sheet barrier color uses ColorScheme.scrim', ( + WidgetTester tester, + ) async { + const Color scrim = Colors.red; + late BuildContext savedContext; + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + ), + home: Builder( + builder: (BuildContext context) { + savedContext = context; + return const SizedBox.expand(); + }, + ), + ), + ); + + unawaited( + showModalBottomSheet( + context: savedContext, + builder: (BuildContext context) { + return const SizedBox(height: 200, child: Text('BottomSheet')); + }, + ), + ); + await tester.pumpAndSettle(); + + final ModalBarrier modalBarrier = tester.widget(find.byType(ModalBarrier).last); + expect(modalBarrier.color, scrim.withValues(alpha: Colors.black54.a)); + }); + + testWidgets('BottomSheetTheme.modalBarrierColor takes precedence over ColorScheme.scrim', ( + WidgetTester tester, + ) async { + const Color scrim = Colors.red; + const Color themeBarrierColor = Colors.blue; + late BuildContext savedContext; + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + bottomSheetTheme: const BottomSheetThemeData(modalBarrierColor: themeBarrierColor), + ), + home: Builder( + builder: (BuildContext context) { + savedContext = context; + return const SizedBox.expand(); + }, + ), + ), + ); + + unawaited( + showModalBottomSheet( + context: savedContext, + builder: (BuildContext context) { + return const SizedBox(height: 200, child: Text('BottomSheet')); + }, + ), + ); + await tester.pumpAndSettle(); + + final ModalBarrier modalBarrier = tester.widget(find.byType(ModalBarrier).last); + expect(modalBarrier.color, themeBarrierColor); + }); + + testWidgets('Explicit barrierColor takes precedence over ColorScheme.scrim for bottom sheets', ( + WidgetTester tester, + ) async { + const Color scrim = Colors.red; + const Color explicitBarrierColor = Colors.green; + late BuildContext savedContext; + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + bottomSheetTheme: const BottomSheetThemeData(modalBarrierColor: Colors.blue), + ), + home: Builder( + builder: (BuildContext context) { + savedContext = context; + return const SizedBox.expand(); + }, + ), + ), + ); + + unawaited( + showModalBottomSheet( + context: savedContext, + barrierColor: explicitBarrierColor, + builder: (BuildContext context) { + return const SizedBox(height: 200, child: Text('BottomSheet')); + }, + ), + ); + await tester.pumpAndSettle(); + + final ModalBarrier modalBarrier = tester.widget(find.byType(ModalBarrier).last); + expect(modalBarrier.color, explicitBarrierColor); + }); + + testWidgets('Bottom sheet ColorScheme.scrim alpha is normalized to Colors.black54 opacity', ( + WidgetTester tester, + ) async { + final Color scrim = Colors.red.withValues(alpha: 0.2); + late BuildContext savedContext; + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + ), + home: Builder( + builder: (BuildContext context) { + savedContext = context; + return const SizedBox.expand(); + }, + ), + ), + ); + + unawaited( + showModalBottomSheet( + context: savedContext, + builder: (BuildContext context) { + return const SizedBox(height: 200, child: Text('BottomSheet')); + }, + ), + ); + await tester.pumpAndSettle(); + + final ModalBarrier modalBarrier = tester.widget(find.byType(ModalBarrier).last); + expect(modalBarrier.color, scrim.withValues(alpha: Colors.black54.a)); + }); + + testWidgets('ModalBottomSheetRoute barrier color uses ColorScheme.scrim', ( + WidgetTester tester, + ) async { + const Color scrim = Colors.red; + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + ), + home: Builder( + builder: (BuildContext context) { + return TextButton( + onPressed: () { + Navigator.of(context).push( + ModalBottomSheetRoute( + isScrollControlled: false, + builder: (BuildContext context) { + return const SizedBox(height: 200, child: Text('BottomSheet')); + }, + ), + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ); + + await tester.tap(find.text('Open')); + await tester.pumpAndSettle(); + + final ModalBarrier modalBarrier = tester.widget(find.byType(ModalBarrier).last); + expect(modalBarrier.color, scrim.withValues(alpha: Colors.black54.a)); + }); + + testWidgets( + 'ModalBottomSheetRoute uses BottomSheetTheme.modalBarrierColor over ColorScheme.scrim', + (WidgetTester tester) async { + const Color scrim = Colors.red; + const Color themeBarrierColor = Colors.blue; + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + bottomSheetTheme: const BottomSheetThemeData(modalBarrierColor: themeBarrierColor), + ), + home: Builder( + builder: (BuildContext context) { + return TextButton( + onPressed: () { + Navigator.of(context).push( + ModalBottomSheetRoute( + isScrollControlled: false, + builder: (BuildContext context) { + return const SizedBox(height: 200, child: Text('BottomSheet')); + }, + ), + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ); + + await tester.tap(find.text('Open')); + await tester.pumpAndSettle(); + + final ModalBarrier modalBarrier = tester.widget(find.byType(ModalBarrier).last); + expect(modalBarrier.color, themeBarrierColor); + }, + ); + testWidgets('Material3 - BottomSheet uses fallback values', (WidgetTester tester) async { const Color surfaceColor = Colors.pink; const Color surfaceTintColor = Colors.blue; diff --git a/packages/material_ui/test/dialog_test.dart b/packages/material_ui/test/dialog_test.dart index ab59e78b1081..d0d80f321706 100644 --- a/packages/material_ui/test/dialog_test.dart +++ b/packages/material_ui/test/dialog_test.dart @@ -603,6 +603,186 @@ void main() { expect(tester.widget(find.byType(ModalBarrier).last).color, Colors.pink); }); + testWidgets('Barrier color uses ColorScheme.scrim', (WidgetTester tester) async { + const Color scrim = Colors.red; + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + ), + home: const Center(child: Text('Test')), + ), + ); + final BuildContext context = tester.element(find.text('Test')); + + unawaited( + showDialog( + context: context, + builder: (BuildContext context) { + return const Text('Dialog'); + }, + ), + ); + await tester.pumpAndSettle(); + expect( + tester.widget(find.byType(ModalBarrier).last).color, + scrim.withValues(alpha: Colors.black54.a), + ); + }); + + testWidgets('DialogTheme.barrierColor takes precedence over ColorScheme.scrim', ( + WidgetTester tester, + ) async { + const Color scrim = Colors.red; + const Color themeBarrierColor = Colors.blue; + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + dialogTheme: const DialogThemeData(barrierColor: themeBarrierColor), + ), + home: const Center(child: Text('Test')), + ), + ); + final BuildContext context = tester.element(find.text('Test')); + + unawaited( + showDialog( + context: context, + builder: (BuildContext context) { + return const Text('Dialog'); + }, + ), + ); + await tester.pumpAndSettle(); + expect(tester.widget(find.byType(ModalBarrier).last).color, themeBarrierColor); + }); + + testWidgets('Explicit barrierColor takes precedence over ColorScheme.scrim', ( + WidgetTester tester, + ) async { + const Color scrim = Colors.red; + const Color explicitBarrierColor = Colors.green; + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + dialogTheme: const DialogThemeData(barrierColor: Colors.blue), + ), + home: const Center(child: Text('Test')), + ), + ); + final BuildContext context = tester.element(find.text('Test')); + + unawaited( + showDialog( + context: context, + barrierColor: explicitBarrierColor, + builder: (BuildContext context) { + return const Text('Dialog'); + }, + ), + ); + await tester.pumpAndSettle(); + expect(tester.widget(find.byType(ModalBarrier).last).color, explicitBarrierColor); + }); + + testWidgets('ColorScheme.scrim alpha is normalized to Colors.black54 opacity', ( + WidgetTester tester, + ) async { + final Color scrim = Colors.red.withValues(alpha: 0.2); + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + ), + home: const Center(child: Text('Test')), + ), + ); + final BuildContext context = tester.element(find.text('Test')); + + unawaited( + showDialog( + context: context, + builder: (BuildContext context) { + return const Text('Dialog'); + }, + ), + ); + await tester.pumpAndSettle(); + expect( + tester.widget(find.byType(ModalBarrier).last).color, + scrim.withValues(alpha: Colors.black54.a), + ); + }); + + testWidgets('DialogRoute barrier color uses ColorScheme.scrim', (WidgetTester tester) async { + const Color scrim = Colors.red; + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + ), + home: Builder( + builder: (BuildContext context) { + return TextButton( + onPressed: () { + Navigator.of(context).push( + DialogRoute( + context: context, + builder: (BuildContext context) => const Text('Dialog'), + ), + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ); + + await tester.tap(find.text('Open')); + await tester.pumpAndSettle(); + expect( + tester.widget(find.byType(ModalBarrier).last).color, + scrim.withValues(alpha: Colors.black54.a), + ); + }); + + testWidgets('DialogRoute uses DialogTheme.barrierColor over ColorScheme.scrim', ( + WidgetTester tester, + ) async { + const Color scrim = Colors.red; + const Color themeBarrierColor = Colors.blue; + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + dialogTheme: const DialogThemeData(barrierColor: themeBarrierColor), + ), + home: Builder( + builder: (BuildContext context) { + return TextButton( + onPressed: () { + Navigator.of(context).push( + DialogRoute( + context: context, + builder: (BuildContext context) => const Text('Dialog'), + ), + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ); + + await tester.tap(find.text('Open')); + await tester.pumpAndSettle(); + expect(tester.widget(find.byType(ModalBarrier).last).color, themeBarrierColor); + }); + testWidgets('Dialog hides underlying semantics tree', (WidgetTester tester) async { final semantics = SemanticsTester(tester); const buttonText = 'A button covered by dialog overlay'; diff --git a/packages/material_ui/test/drawer_test.dart b/packages/material_ui/test/drawer_test.dart index 27c240efd484..2a9e4473f9d9 100644 --- a/packages/material_ui/test/drawer_test.dart +++ b/packages/material_ui/test/drawer_test.dart @@ -209,6 +209,155 @@ void main() { await checkScrim(const Color(0xFF323232)); }); + testWidgets('Drawer scrim uses ColorScheme.scrim', (WidgetTester tester) async { + const Color scrim = Colors.red; + final Color expectedScrim = scrim.withValues(alpha: Colors.black54.a); + final scaffoldKey = GlobalKey(); + + Widget getScrim() { + return tester + .widget( + find.descendant( + of: find.byType(DrawerController), + matching: find.byWidgetPredicate((Widget widget) { + return widget is Semantics && widget.properties.label == 'Dismiss'; + }), + ), + ) + .child!; + } + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + ), + home: Scaffold( + key: scaffoldKey, + drawer: const Drawer(child: SizedBox.expand()), + ), + ), + ); + + scaffoldKey.currentState!.openDrawer(); + await tester.pumpAndSettle(); + expect((getScrim() as ColoredBox).color, isSameColorAs(expectedScrim)); + }); + + testWidgets('DrawerTheme.scrimColor takes precedence over ColorScheme.scrim', ( + WidgetTester tester, + ) async { + const Color scrim = Colors.red; + const Color themeScrimColor = Colors.blue; + final scaffoldKey = GlobalKey(); + + Widget getScrim() { + return tester + .widget( + find.descendant( + of: find.byType(DrawerController), + matching: find.byWidgetPredicate((Widget widget) { + return widget is Semantics && widget.properties.label == 'Dismiss'; + }), + ), + ) + .child!; + } + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + drawerTheme: const DrawerThemeData(scrimColor: themeScrimColor), + ), + home: Scaffold( + key: scaffoldKey, + drawer: const Drawer(child: SizedBox.expand()), + ), + ), + ); + + scaffoldKey.currentState!.openDrawer(); + await tester.pumpAndSettle(); + expect((getScrim() as ColoredBox).color, isSameColorAs(themeScrimColor)); + }); + + testWidgets('Scaffold.drawerScrimColor takes precedence over ColorScheme.scrim', ( + WidgetTester tester, + ) async { + const Color scrim = Colors.red; + const Color drawerScrimColor = Colors.green; + final scaffoldKey = GlobalKey(); + + Widget getScrim() { + return tester + .widget( + find.descendant( + of: find.byType(DrawerController), + matching: find.byWidgetPredicate((Widget widget) { + return widget is Semantics && widget.properties.label == 'Dismiss'; + }), + ), + ) + .child!; + } + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + drawerTheme: const DrawerThemeData(scrimColor: Colors.blue), + ), + home: Scaffold( + key: scaffoldKey, + drawerScrimColor: drawerScrimColor, + drawer: const Drawer(child: SizedBox.expand()), + ), + ), + ); + + scaffoldKey.currentState!.openDrawer(); + await tester.pumpAndSettle(); + expect((getScrim() as ColoredBox).color, isSameColorAs(drawerScrimColor)); + }); + + testWidgets('Drawer ColorScheme.scrim alpha is normalized to Colors.black54 opacity', ( + WidgetTester tester, + ) async { + final Color scrim = Colors.red.withValues(alpha: 0.2); + final Color expectedScrim = scrim.withValues(alpha: Colors.black54.a); + final scaffoldKey = GlobalKey(); + + Widget getScrim() { + return tester + .widget( + find.descendant( + of: find.byType(DrawerController), + matching: find.byWidgetPredicate((Widget widget) { + return widget is Semantics && widget.properties.label == 'Dismiss'; + }), + ), + ) + .child!; + } + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple, scrim: scrim), + ), + home: Scaffold( + key: scaffoldKey, + drawer: const Drawer(child: SizedBox.expand()), + ), + ), + ); + + scaffoldKey.currentState!.openDrawer(); + await tester.pumpAndSettle(); + expect((getScrim() as ColoredBox).color, isSameColorAs(expectedScrim)); + }); + testWidgets('Open/close drawers by flinging', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp(