diff --git a/dev/tools/gen_defaults/bin/gen_defaults.dart b/dev/tools/gen_defaults/bin/gen_defaults.dart index dd716e5ccf9..ed9e100889e 100644 --- a/dev/tools/gen_defaults/bin/gen_defaults.dart +++ b/dev/tools/gen_defaults/bin/gen_defaults.dart @@ -20,6 +20,7 @@ import 'dart:io'; import 'package:gen_defaults/action_chip_template.dart'; import 'package:gen_defaults/app_bar_template.dart'; import 'package:gen_defaults/banner_template.dart'; +import 'package:gen_defaults/bottom_sheet_template.dart'; import 'package:gen_defaults/button_template.dart'; import 'package:gen_defaults/card_template.dart'; import 'package:gen_defaults/checkbox_template.dart'; @@ -88,6 +89,7 @@ Future main(List args) async { 'radio_button.json', 'segmented_button_outlined.json', 'shape.json', + 'sheet_bottom.json', 'slider.json', 'state.json', 'switch.json', @@ -115,6 +117,7 @@ Future main(List args) async { ActionChipTemplate('ActionChip', '$materialLib/action_chip.dart', tokens).updateFile(); AppBarTemplate('AppBar', '$materialLib/app_bar.dart', tokens).updateFile(); BannerTemplate('Banner', '$materialLib/banner.dart', tokens).updateFile(); + BottomSheetTemplate('BottomSheet', '$materialLib/bottom_sheet.dart', tokens).updateFile(); ButtonTemplate('md.comp.elevated-button', 'ElevatedButton', '$materialLib/elevated_button.dart', tokens).updateFile(); ButtonTemplate('md.comp.filled-button', 'FilledButton', '$materialLib/filled_button.dart', tokens).updateFile(); ButtonTemplate('md.comp.filled-tonal-button', 'FilledTonalButton', '$materialLib/filled_button.dart', tokens).updateFile(); diff --git a/dev/tools/gen_defaults/lib/bottom_sheet_template.dart b/dev/tools/gen_defaults/lib/bottom_sheet_template.dart new file mode 100644 index 00000000000..9be720bce27 --- /dev/null +++ b/dev/tools/gen_defaults/lib/bottom_sheet_template.dart @@ -0,0 +1,30 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'template.dart'; + +class BottomSheetTemplate extends TokenTemplate { + const BottomSheetTemplate(super.blockName, super.fileName, super.tokens); + + @override + String generate() => ''' +// Generated version ${tokens["version"]} +class _${blockName}DefaultsM3 extends BottomSheetThemeData { + const _${blockName}DefaultsM3(this.context) + : super( + elevation: ${elevation("md.comp.sheet.bottom.docked.standard.container")}, + modalElevation: ${elevation("md.comp.sheet.bottom.docked.modal.container")}, + shape: ${shape("md.comp.sheet.bottom.docked.container")}, + ); + + final BuildContext context; + + @override + Color? get backgroundColor => ${componentColor("md.comp.sheet.bottom.docked.container")}; + + @override + Color? get surfaceTintColor => ${componentColor("md.comp.sheet.bottom.docked.container.surface-tint-layer")}; +} +'''; +} diff --git a/dev/tools/gen_defaults/lib/template.dart b/dev/tools/gen_defaults/lib/template.dart index 3ceb2be33f3..093688747a1 100644 --- a/dev/tools/gen_defaults/lib/template.dart +++ b/dev/tools/gen_defaults/lib/template.dart @@ -177,6 +177,12 @@ abstract class TokenTemplate { if (topLeft == topRight && topLeft == bottomLeft && topLeft == bottomRight) { return '${prefix}RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular($topLeft)))'; } + if (topLeft == topRight && bottomLeft == bottomRight) { + return '${prefix}RoundedRectangleBorder(borderRadius: BorderRadius.vertical(' + '${topLeft > 0 ? 'top: Radius.circular($topLeft),':''}' + '${bottomLeft > 0 ? 'bottom: Radius.circular($bottomLeft),':''}' + '))'; + } return '${prefix}RoundedRectangleBorder(borderRadius: ' 'BorderRadius.only(' 'topLeft: Radius.circular(${shape['topLeft']}), ' diff --git a/examples/api/lib/material/bottom_sheet/show_modal_bottom_sheet.1.dart b/examples/api/lib/material/bottom_sheet/show_modal_bottom_sheet.1.dart new file mode 100644 index 00000000000..392094e3304 --- /dev/null +++ b/examples/api/lib/material/bottom_sheet/show_modal_bottom_sheet.1.dart @@ -0,0 +1,61 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flutter code sample for Material Design 3 TextFields. +/// Flutter code sample for [showModalBottomSheet]. + +import 'package:flutter/material.dart'; + +void main() => runApp(const MyApp()); + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true), + home: Scaffold( + appBar: AppBar(title: const Text('Bottom Sheet Sample')), + body: const MyStatelessWidget(), + ), + ); + } +} + +class MyStatelessWidget extends StatelessWidget { + const MyStatelessWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: ElevatedButton( + child: const Text('showModalBottomSheet'), + onPressed: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return SizedBox( + height: 200, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Modal BottomSheet'), + ElevatedButton( + child: const Text('Close BottomSheet'), + onPressed: () => Navigator.pop(context), + ), + ], + ), + ), + ); + }, + ); + }, + ), + ); + } +} diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart index 9b9761df8bd..33daed77b3a 100644 --- a/packages/flutter/lib/src/material/bottom_sheet.dart +++ b/packages/flutter/lib/src/material/bottom_sheet.dart @@ -274,16 +274,19 @@ class _BottomSheetState extends State { @override Widget build(BuildContext context) { final BottomSheetThemeData bottomSheetTheme = Theme.of(context).bottomSheetTheme; + final BottomSheetThemeData defaults = Theme.of(context).useMaterial3 ? _BottomSheetDefaultsM3(context) : const BottomSheetThemeData(); final BoxConstraints? constraints = widget.constraints ?? bottomSheetTheme.constraints; - final Color? color = widget.backgroundColor ?? bottomSheetTheme.backgroundColor; - final double elevation = widget.elevation ?? bottomSheetTheme.elevation ?? 0; - final ShapeBorder? shape = widget.shape ?? bottomSheetTheme.shape; + final Color? color = widget.backgroundColor ?? bottomSheetTheme.backgroundColor ?? defaults.backgroundColor; + final Color? surfaceTintColor = bottomSheetTheme.surfaceTintColor ?? defaults.surfaceTintColor; + final double elevation = widget.elevation ?? bottomSheetTheme.elevation ?? defaults.elevation ?? 0; + final ShapeBorder? shape = widget.shape ?? bottomSheetTheme.shape ?? defaults.shape; final Clip clipBehavior = widget.clipBehavior ?? bottomSheetTheme.clipBehavior ?? Clip.none; Widget bottomSheet = Material( key: _childKey, color: color, elevation: elevation, + surfaceTintColor: surfaceTintColor, shape: shape, clipBehavior: clipBehavior, child: NotificationListener( @@ -526,10 +529,11 @@ class _ModalBottomSheetRoute extends PopupRoute { child: Builder( builder: (BuildContext context) { final BottomSheetThemeData sheetTheme = Theme.of(context).bottomSheetTheme; + final BottomSheetThemeData defaults = Theme.of(context).useMaterial3 ? _BottomSheetDefaultsM3(context) : const BottomSheetThemeData(); return _ModalBottomSheet( route: this, - backgroundColor: backgroundColor ?? sheetTheme.modalBackgroundColor ?? sheetTheme.backgroundColor, - elevation: elevation ?? sheetTheme.modalElevation ?? sheetTheme.elevation, + backgroundColor: backgroundColor ?? sheetTheme.modalBackgroundColor ?? sheetTheme.backgroundColor ?? defaults.backgroundColor, + elevation: elevation ?? sheetTheme.modalElevation ?? defaults.modalElevation ?? sheetTheme.elevation, shape: shape, clipBehavior: clipBehavior, constraints: constraints, @@ -727,7 +731,7 @@ Future showModalBottomSheet({ clipBehavior: clipBehavior, constraints: constraints, isDismissible: isDismissible, - modalBarrierColor: barrierColor, + modalBarrierColor: barrierColor ?? Theme.of(context).bottomSheetTheme.modalBarrierColor, enableDrag: enableDrag, settings: routeSettings, transitionAnimationController: transitionAnimationController, @@ -806,3 +810,34 @@ PersistentBottomSheetController showBottomSheet({ transitionAnimationController: transitionAnimationController, ); } + + + +// BEGIN GENERATED TOKEN PROPERTIES - BottomSheet + +// Do not edit by hand. The code between the "BEGIN GENERATED" and +// "END GENERATED" comments are generated from data in the Material +// Design token database by the script: +// dev/tools/gen_defaults/bin/gen_defaults.dart. + +// Token database version: v0_132 + +// Generated version v0_132 +class _BottomSheetDefaultsM3 extends BottomSheetThemeData { + const _BottomSheetDefaultsM3(this.context) + : super( + elevation: 1.0, + modalElevation: 1.0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(28.0))), + ); + + final BuildContext context; + + @override + Color? get backgroundColor => Theme.of(context).colorScheme.surface; + + @override + Color? get surfaceTintColor => Theme.of(context).colorScheme.surfaceTint; +} + +// END GENERATED TOKEN PROPERTIES - BottomSheet diff --git a/packages/flutter/lib/src/material/bottom_sheet_theme.dart b/packages/flutter/lib/src/material/bottom_sheet_theme.dart index 2304d28faf7..c3b80b517b1 100644 --- a/packages/flutter/lib/src/material/bottom_sheet_theme.dart +++ b/packages/flutter/lib/src/material/bottom_sheet_theme.dart @@ -29,8 +29,10 @@ class BottomSheetThemeData with Diagnosticable { /// Creates a theme that can be used for [ThemeData.bottomSheetTheme]. const BottomSheetThemeData({ this.backgroundColor, + this.surfaceTintColor, this.elevation, this.modalBackgroundColor, + this.modalBarrierColor, this.modalElevation, this.shape, this.clipBehavior, @@ -42,6 +44,13 @@ class BottomSheetThemeData with Diagnosticable { /// If null, [BottomSheet] defaults to [Material]'s default. final Color? backgroundColor; + /// Default value for surfaceTintColor. + /// + /// If null, [BottomSheet] will not display an overlay color. + /// + /// See [Material.surfaceTintColor] for more details. + final Color? surfaceTintColor; + /// Default value for [BottomSheet.elevation]. /// /// {@macro flutter.material.material.elevation} @@ -53,6 +62,10 @@ class BottomSheetThemeData with Diagnosticable { /// as a modal bottom sheet. final Color? modalBackgroundColor; + /// Default value for barrier color when the Bottom sheet is presented as + /// a modal bottom sheet. + final Color? modalBarrierColor; + /// Value for [BottomSheet.elevation] when the Bottom sheet is presented as a /// modal bottom sheet. final double? modalElevation; @@ -77,8 +90,10 @@ class BottomSheetThemeData with Diagnosticable { /// new values. BottomSheetThemeData copyWith({ Color? backgroundColor, + Color? surfaceTintColor, double? elevation, Color? modalBackgroundColor, + Color? modalBarrierColor, double? modalElevation, ShapeBorder? shape, Clip? clipBehavior, @@ -86,8 +101,10 @@ class BottomSheetThemeData with Diagnosticable { }) { return BottomSheetThemeData( backgroundColor: backgroundColor ?? this.backgroundColor, + surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor, elevation: elevation ?? this.elevation, modalBackgroundColor: modalBackgroundColor ?? this.modalBackgroundColor, + modalBarrierColor: modalBarrierColor ?? this.modalBarrierColor, modalElevation: modalElevation ?? this.modalElevation, shape: shape ?? this.shape, clipBehavior: clipBehavior ?? this.clipBehavior, @@ -107,8 +124,10 @@ class BottomSheetThemeData with Diagnosticable { } return BottomSheetThemeData( backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), + surfaceTintColor: Color.lerp(a?.surfaceTintColor, b?.surfaceTintColor, t), elevation: lerpDouble(a?.elevation, b?.elevation, t), modalBackgroundColor: Color.lerp(a?.modalBackgroundColor, b?.modalBackgroundColor, t), + modalBarrierColor: Color.lerp(a?.modalBarrierColor, b?.modalBarrierColor, t), modalElevation: lerpDouble(a?.modalElevation, b?.modalElevation, t), shape: ShapeBorder.lerp(a?.shape, b?.shape, t), clipBehavior: t < 0.5 ? a?.clipBehavior : b?.clipBehavior, @@ -119,8 +138,10 @@ class BottomSheetThemeData with Diagnosticable { @override int get hashCode => Object.hash( backgroundColor, + surfaceTintColor, elevation, modalBackgroundColor, + modalBarrierColor, modalElevation, shape, clipBehavior, @@ -137,8 +158,10 @@ class BottomSheetThemeData with Diagnosticable { } return other is BottomSheetThemeData && other.backgroundColor == backgroundColor + && other.surfaceTintColor == surfaceTintColor && other.elevation == elevation && other.modalBackgroundColor == modalBackgroundColor + && other.modalBarrierColor == modalBarrierColor && other.modalElevation == modalElevation && other.shape == shape && other.clipBehavior == clipBehavior @@ -149,8 +172,10 @@ class BottomSheetThemeData with Diagnosticable { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null)); + properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null)); properties.add(DoubleProperty('elevation', elevation, defaultValue: null)); properties.add(ColorProperty('modalBackgroundColor', modalBackgroundColor, defaultValue: null)); + properties.add(ColorProperty('modalBarrierColor', modalBarrierColor, defaultValue: null)); properties.add(DoubleProperty('modalElevation', modalElevation, defaultValue: null)); properties.add(DiagnosticsProperty('shape', shape, defaultValue: null)); properties.add(DiagnosticsProperty('clipBehavior', clipBehavior, defaultValue: null)); diff --git a/packages/flutter/test/material/bottom_sheet_test.dart b/packages/flutter/test/material/bottom_sheet_test.dart index 316e4d00734..2701e38ede1 100644 --- a/packages/flutter/test/material/bottom_sheet_test.dart +++ b/packages/flutter/test/material/bottom_sheet_test.dart @@ -833,6 +833,45 @@ void main() { expect(modalBarrier.color, barrierColor); }); + testWidgets('BottomSheet uses fallback values in maretial3', + (WidgetTester tester) async { + const Color surfaceColor = Colors.pink; + const Color surfaceTintColor = Colors.blue; + const ShapeBorder defaultShape = RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(28.0), + )); + + await tester.pumpWidget(MaterialApp( + theme: ThemeData( + colorScheme: const ColorScheme.light( + surface: surfaceColor, + surfaceTint: surfaceTintColor, + ), + useMaterial3: true, + ), + home: Scaffold( + body: BottomSheet( + onClosing: () {}, + builder: (BuildContext context) { + return Container(); + }, + ), + ), + )); + + final Material material = tester.widget( + find.descendant( + of: find.byType(BottomSheet), + matching: find.byType(Material), + ), + ); + expect(material.color, surfaceColor); + expect(material.surfaceTintColor, surfaceTintColor); + expect(material.elevation, 1.0); + expect(material.shape, defaultShape); + }); + testWidgets('modal BottomSheet with scrollController has semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); final GlobalKey scaffoldKey = GlobalKey(); diff --git a/packages/flutter/test/material/bottom_sheet_theme_test.dart b/packages/flutter/test/material/bottom_sheet_theme_test.dart index d01aad537e5..d738e98e830 100644 --- a/packages/flutter/test/material/bottom_sheet_theme_test.dart +++ b/packages/flutter/test/material/bottom_sheet_theme_test.dart @@ -148,12 +148,14 @@ void main() { const double modalElevation = 5.0; const double persistentElevation = 7.0; const Color modalBackgroundColor = Colors.yellow; + const Color modalBarrierColor = Colors.blue; const Color persistentBackgroundColor = Colors.red; const BottomSheetThemeData bottomSheetTheme = BottomSheetThemeData( elevation: persistentElevation, modalElevation: modalElevation, backgroundColor: persistentBackgroundColor, modalBackgroundColor: modalBackgroundColor, + modalBarrierColor:modalBarrierColor, ); await tester.pumpWidget(bottomSheetWithElevations(bottomSheetTheme)); @@ -168,6 +170,9 @@ void main() { ); expect(material.elevation, modalElevation); expect(material.color, modalBackgroundColor); + + final ModalBarrier modalBarrier = tester.widget(find.byType(ModalBarrier).last); + expect(modalBarrier.color, modalBarrierColor); }); testWidgets('General bottom sheet parameters take priority over modal bottom sheet-specific parameters for persistent bottom sheets', (WidgetTester tester) async {