diff --git a/packages/flutter/lib/material.dart b/packages/flutter/lib/material.dart index 0d710a25f0..97d4431709 100644 --- a/packages/flutter/lib/material.dart +++ b/packages/flutter/lib/material.dart @@ -27,6 +27,7 @@ export 'src/material/bottom_app_bar.dart'; export 'src/material/bottom_app_bar_theme.dart'; export 'src/material/bottom_navigation_bar.dart'; export 'src/material/bottom_sheet.dart'; +export 'src/material/bottom_sheet_theme.dart'; export 'src/material/button.dart'; export 'src/material/button_bar.dart'; export 'src/material/button_theme.dart'; diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart index 496c9083fc..63b7d1169d 100644 --- a/packages/flutter/lib/src/material/bottom_sheet.dart +++ b/packages/flutter/lib/src/material/bottom_sheet.dart @@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; +import 'bottom_sheet_theme.dart'; import 'colors.dart'; import 'debug.dart'; import 'material.dart'; @@ -56,14 +57,15 @@ class BottomSheet extends StatefulWidget { Key key, this.animationController, this.enableDrag = true, - this.elevation = 0.0, this.backgroundColor, + this.elevation, + this.shape, @required this.onClosing, @required this.builder, }) : assert(enableDrag != null), assert(onClosing != null), assert(builder != null), - assert(elevation != null && elevation >= 0.0), + assert(elevation == null || elevation >= 0.0), super(key: key); /// The animation controller that controls the bottom sheet's entrance and @@ -92,6 +94,13 @@ class BottomSheet extends StatefulWidget { /// Default is true. final bool enableDrag; + /// The bottom sheet's background color. + /// + /// Defines the bottom sheet's [Material.color]. + /// + /// Defaults to null and falls back to [Material]'s default. + final Color backgroundColor; + /// The z-coordinate at which to place this material relative to its parent. /// /// This controls the size of the shadow below the material. @@ -99,10 +108,12 @@ class BottomSheet extends StatefulWidget { /// Defaults to 0. The value is non-negative. final double elevation; - /// The color for the [Material] of the bottom sheet. + /// The shape of the bottom sheet. /// - /// Defaults to [Colors.white]. The value must not be null. - final Color backgroundColor; + /// Defines the bottom sheet's [Material.shape]. + /// + /// Defaults to null and falls back to [Material]'s default. + final ShapeBorder shape; @override _BottomSheetState createState() => _BottomSheetState(); @@ -170,10 +181,16 @@ class _BottomSheetState extends State { @override Widget build(BuildContext context) { + final BottomSheetThemeData bottomSheetTheme = Theme.of(context).bottomSheetTheme; + final Color color = widget.backgroundColor ?? bottomSheetTheme.backgroundColor; + final double elevation = widget.elevation ?? bottomSheetTheme.elevation ?? 0; + final ShapeBorder shape = widget.shape ?? bottomSheetTheme.shape; + final Widget bottomSheet = Material( key: _childKey, - color: widget.backgroundColor, - elevation: widget.elevation, + color: color, + elevation: elevation, + shape: shape, child: NotificationListener( onNotification: extentChanged, child: widget.builder(context), @@ -227,12 +244,18 @@ class _ModalBottomSheet extends StatefulWidget { const _ModalBottomSheet({ Key key, this.route, + this.backgroundColor, + this.elevation, + this.shape, this.isScrollControlled = false, }) : assert(isScrollControlled != null), super(key: key); final _ModalBottomSheetRoute route; final bool isScrollControlled; + final Color backgroundColor; + final double elevation; + final ShapeBorder shape; @override _ModalBottomSheetState createState() => _ModalBottomSheetState(); @@ -279,7 +302,6 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> { child: CustomSingleChildLayout( delegate: _ModalBottomSheetLayout(animationValue, widget.isScrollControlled), child: BottomSheet( - backgroundColor: widget.route.backgroundColor, animationController: widget.route._animationController, onClosing: () { if (widget.route.isCurrent) { @@ -287,6 +309,9 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> { } }, builder: widget.route.builder, + backgroundColor: widget.backgroundColor, + elevation: widget.elevation, + shape: widget.shape, ), ), ), @@ -302,8 +327,10 @@ class _ModalBottomSheetRoute extends PopupRoute { this.builder, this.theme, this.barrierLabel, - @required this.isScrollControlled, this.backgroundColor, + this.elevation, + this.shape, + @required this.isScrollControlled, RouteSettings settings, }) : assert(isScrollControlled != null), super(settings: settings); @@ -312,6 +339,8 @@ class _ModalBottomSheetRoute extends PopupRoute { final ThemeData theme; final bool isScrollControlled; final Color backgroundColor; + final double elevation; + final ShapeBorder shape; @override Duration get transitionDuration => _bottomSheetDuration; @@ -327,6 +356,7 @@ class _ModalBottomSheetRoute extends PopupRoute { AnimationController _animationController; + @override AnimationController createAnimationController() { assert(_animationController == null); @@ -341,7 +371,13 @@ class _ModalBottomSheetRoute extends PopupRoute { Widget bottomSheet = MediaQuery.removePadding( context: context, removeTop: true, - child: _ModalBottomSheet(route: this, isScrollControlled: isScrollControlled), + child: _ModalBottomSheet( + route: this, + backgroundColor: backgroundColor, + elevation: elevation, + shape: shape, + isScrollControlled: isScrollControlled + ), ); if (theme != null) bottomSheet = Theme(data: theme, child: bottomSheet); @@ -384,8 +420,10 @@ class _ModalBottomSheetRoute extends PopupRoute { Future showModalBottomSheet({ @required BuildContext context, @required WidgetBuilder builder, - bool isScrollControlled = false, Color backgroundColor, + double elevation, + ShapeBorder shape, + bool isScrollControlled = false, }) { assert(context != null); assert(builder != null); @@ -397,8 +435,10 @@ Future showModalBottomSheet({ builder: builder, theme: Theme.of(context, shadowThemeOnly: true), isScrollControlled: isScrollControlled, - backgroundColor: backgroundColor, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, + backgroundColor: backgroundColor, + elevation: elevation, + shape: shape, )); } @@ -440,6 +480,8 @@ PersistentBottomSheetController showBottomSheet({ @required BuildContext context, @required WidgetBuilder builder, Color backgroundColor, + double elevation, + ShapeBorder shape, }) { assert(context != null); assert(builder != null); @@ -448,5 +490,7 @@ PersistentBottomSheetController showBottomSheet({ return Scaffold.of(context).showBottomSheet( builder, backgroundColor: backgroundColor, + elevation: elevation, + shape: shape, ); } diff --git a/packages/flutter/lib/src/material/bottom_sheet_theme.dart b/packages/flutter/lib/src/material/bottom_sheet_theme.dart new file mode 100644 index 0000000000..0ba47eabc4 --- /dev/null +++ b/packages/flutter/lib/src/material/bottom_sheet_theme.dart @@ -0,0 +1,111 @@ +// Copyright 2019 The Chromium 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 'dart:ui' show lerpDouble; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; + +/// Defines default property values for [BottomSheet]'s [Material]. +/// +/// Descendant widgets obtain the current [BottomSheetThemeData] object +/// using `Theme.of(context).bottomSheetTheme`. Instances of +/// [BottomSheetThemeData] can be customized with +/// [BottomSheetThemeData.copyWith]. +/// +/// Typically a [BottomSheetThemeData] is specified as part of the +/// overall [Theme] with [ThemeData.bottomSheetTheme]. +/// +/// All [BottomSheetThemeData] properties are `null` by default. +/// When null, the [BottomSheet] will provide its own defaults. +/// +/// See also: +/// +/// * [ThemeData], which describes the overall theme information for the +/// application. +class BottomSheetThemeData extends Diagnosticable { + /// Creates a theme that can be used for [ThemeData.bottomSheetTheme]. + const BottomSheetThemeData({ + this.backgroundColor, + this.elevation, + this.shape, + }); + + /// Default value for [BottomSheet.backgroundColor]. + /// + /// If null, [BottomSheet] defaults to [Material]'s default. + final Color backgroundColor; + + /// Default value for [BottomSheet.elevation]. + /// + /// {@macro flutter.material.material.elevation} + /// + /// If null, [BottomSheet] defaults to 0.0. + final double elevation; + + /// Default value for [BottomSheet.shape]. + /// + /// If null, no overriding shape is specified for [BottomSheet], so the + /// [BottomSheet] is rectangular. + final ShapeBorder shape; + + /// Creates a copy of this object with the given fields replaced with the + /// new values. + BottomSheetThemeData copyWith({ + Color backgroundColor, + double elevation, + ShapeBorder shape, + }) { + return BottomSheetThemeData( + backgroundColor: backgroundColor ?? this.backgroundColor, + elevation: elevation ?? this.elevation, + shape: shape ?? this.shape, + ); + } + + /// Linearly interpolate between two bottom sheet themes. + /// + /// If both arguments are null then null is returned. + /// + /// {@macro dart.ui.shadow.lerp} + static BottomSheetThemeData lerp(BottomSheetThemeData a, BottomSheetThemeData b, double t) { + assert(t != null); + if (a == null && b == null) + return null; + return BottomSheetThemeData( + backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), + elevation: lerpDouble(a?.elevation, b?.elevation, t), + shape: ShapeBorder.lerp(a?.shape, b?.shape, t), + ); + } + + @override + int get hashCode { + return hashValues( + backgroundColor, + elevation, + shape, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) + return true; + if (other.runtimeType != runtimeType) + return false; + final BottomSheetThemeData typedOther = other; + return typedOther.backgroundColor == backgroundColor + && typedOther.elevation == elevation + && typedOther.shape == shape; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('backgroundColor', backgroundColor, defaultValue: null)); + properties.add(DiagnosticsProperty('elevation', elevation, defaultValue: null)); + properties.add(DiagnosticsProperty('shape', shape, defaultValue: null)); + } +} diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 0066cb96a8..55e1f417d3 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -1541,6 +1541,8 @@ class ScaffoldState extends State with TickerProviderStateMixin { bool isPersistent, { AnimationController animationController, Color backgroundColor, + double elevation, + ShapeBorder shape, }) { assert(() { if (widget.bottomSheet != null && isPersistent && _currentBottomSheet != null) { @@ -1619,6 +1621,8 @@ class ScaffoldState extends State with TickerProviderStateMixin { builder: builder, isPersistent: isPersistent, backgroundColor: backgroundColor, + elevation: elevation, + shape: shape, ); if (!isPersistent) @@ -1673,6 +1677,8 @@ class ScaffoldState extends State with TickerProviderStateMixin { PersistentBottomSheetController showBottomSheet( WidgetBuilder builder, { Color backgroundColor, + double elevation, + ShapeBorder shape, }) { assert(() { if (widget.bottomSheet != null) { @@ -1694,6 +1700,8 @@ class ScaffoldState extends State with TickerProviderStateMixin { false, animationController: controller, backgroundColor: backgroundColor, + elevation: elevation, + shape: shape, ); }); return _currentBottomSheet; @@ -2216,6 +2224,8 @@ class _StandardBottomSheet extends StatefulWidget { this.builder, this.isPersistent = false, this.backgroundColor, + this.elevation, + this.shape, }) : super(key: key); final AnimationController animationController; // we control it, but it must be disposed by whoever created it. @@ -2225,6 +2235,8 @@ class _StandardBottomSheet extends StatefulWidget { final WidgetBuilder builder; final bool isPersistent; final Color backgroundColor; + final double elevation; + final ShapeBorder shape; @override _StandardBottomSheetState createState() => _StandardBottomSheetState(); @@ -2311,6 +2323,8 @@ class _StandardBottomSheetState extends State<_StandardBottomSheet> { onClosing: widget.onClosing, builder: widget.builder, backgroundColor: widget.backgroundColor, + elevation: widget.elevation, + shape: widget.shape, ), ), ); diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index b1d7db2b9a..b766d34dad 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -11,6 +11,7 @@ import 'package:flutter/widgets.dart'; import 'app_bar_theme.dart'; import 'bottom_app_bar_theme.dart'; +import 'bottom_sheet_theme.dart'; import 'button_theme.dart'; import 'card_theme.dart'; import 'chip_theme.dart'; @@ -165,6 +166,7 @@ class ThemeData extends Diagnosticable { Typography typography, CupertinoThemeData cupertinoOverrideTheme, SnackBarThemeData snackBarTheme, + BottomSheetThemeData bottomSheetTheme, }) { brightness ??= Brightness.light; final bool isDark = brightness == Brightness.dark; @@ -259,6 +261,7 @@ class ThemeData extends Diagnosticable { floatingActionButtonTheme ??= const FloatingActionButtonThemeData(); cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault(); snackBarTheme ??= const SnackBarThemeData(); + bottomSheetTheme ??= const BottomSheetThemeData(); return ThemeData.raw( brightness: brightness, @@ -313,6 +316,7 @@ class ThemeData extends Diagnosticable { typography: typography, cupertinoOverrideTheme: cupertinoOverrideTheme, snackBarTheme: snackBarTheme, + bottomSheetTheme: bottomSheetTheme, ); } @@ -379,6 +383,7 @@ class ThemeData extends Diagnosticable { @required this.typography, @required this.cupertinoOverrideTheme, @required this.snackBarTheme, + @required this.bottomSheetTheme, }) : assert(brightness != null), assert(primaryColor != null), assert(primaryColorBrightness != null), @@ -428,7 +433,8 @@ class ThemeData extends Diagnosticable { assert(dialogTheme != null), assert(floatingActionButtonTheme != null), assert(typography != null), - assert(snackBarTheme != null); + assert(snackBarTheme != null), + assert(bottomSheetTheme != null); // Warning: make sure these properties are in the exact same order as in // hashValues() and in the raw constructor and in the order of fields in @@ -699,6 +705,9 @@ class ThemeData extends Diagnosticable { /// can be overridden using attributes of this [cupertinoOverrideTheme]. final CupertinoThemeData cupertinoOverrideTheme; + /// A theme for customizing the color, elevation, and shape of a bottom sheet. + final BottomSheetThemeData bottomSheetTheme; + /// Creates a copy of this theme but with the given fields replaced with the new values. ThemeData copyWith({ Brightness brightness, @@ -753,6 +762,7 @@ class ThemeData extends Diagnosticable { Typography typography, CupertinoThemeData cupertinoOverrideTheme, SnackBarThemeData snackBarTheme, + BottomSheetThemeData bottomSheetTheme, }) { cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault(); return ThemeData.raw( @@ -808,6 +818,7 @@ class ThemeData extends Diagnosticable { typography: typography ?? this.typography, cupertinoOverrideTheme: cupertinoOverrideTheme ?? this.cupertinoOverrideTheme, snackBarTheme: snackBarTheme ?? this.snackBarTheme, + bottomSheetTheme: bottomSheetTheme ?? this.bottomSheetTheme, ); } @@ -941,6 +952,7 @@ class ThemeData extends Diagnosticable { typography: Typography.lerp(a.typography, b.typography, t), cupertinoOverrideTheme: t < 0.5 ? a.cupertinoOverrideTheme : b.cupertinoOverrideTheme, snackBarTheme: SnackBarThemeData.lerp(a.snackBarTheme, b.snackBarTheme, t), + bottomSheetTheme: BottomSheetThemeData.lerp(a.bottomSheetTheme, b.bottomSheetTheme, t), ); } @@ -1003,7 +1015,8 @@ class ThemeData extends Diagnosticable { (otherData.floatingActionButtonTheme == floatingActionButtonTheme) && (otherData.typography == typography) && (otherData.cupertinoOverrideTheme == cupertinoOverrideTheme) && - (otherData.snackBarTheme == snackBarTheme); + (otherData.snackBarTheme == snackBarTheme) && + (otherData.bottomSheetTheme == bottomSheetTheme); } @override @@ -1067,6 +1080,7 @@ class ThemeData extends Diagnosticable { typography, cupertinoOverrideTheme, snackBarTheme, + bottomSheetTheme, ), ), ); @@ -1125,6 +1139,7 @@ class ThemeData extends Diagnosticable { properties.add(DiagnosticsProperty('typography', typography, defaultValue: defaultData.typography)); properties.add(DiagnosticsProperty('cupertinoOverrideTheme', cupertinoOverrideTheme, defaultValue: defaultData.cupertinoOverrideTheme)); properties.add(DiagnosticsProperty('snackBarTheme', snackBarTheme, defaultValue: defaultData.snackBarTheme)); + properties.add(DiagnosticsProperty('bottomSheetTheme', bottomSheetTheme, defaultValue: defaultData.bottomSheetTheme)); } } diff --git a/packages/flutter/test/material/bottom_sheet_theme_test.dart b/packages/flutter/test/material/bottom_sheet_theme_test.dart new file mode 100644 index 0000000000..0d19a67931 --- /dev/null +++ b/packages/flutter/test/material/bottom_sheet_theme_test.dart @@ -0,0 +1,143 @@ +// Copyright 2019 The Chromium 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 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('BottomSheetThemeData copyWith, ==, hashCode basics', () { + expect(const BottomSheetThemeData(), const BottomSheetThemeData().copyWith()); + expect(const BottomSheetThemeData().hashCode, const BottomSheetThemeData().copyWith().hashCode); + }); + + test('BottomSheetThemeData null fields by default', () { + const BottomSheetThemeData bottomSheetTheme = BottomSheetThemeData(); + expect(bottomSheetTheme.backgroundColor, null); + expect(bottomSheetTheme.elevation, null); + expect(bottomSheetTheme.shape, null); + }); + + testWidgets('Default BottomSheetThemeData debugFillProperties', (WidgetTester tester) async { + final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); + const BottomSheetThemeData().debugFillProperties(builder); + + final List description = builder.properties + .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) + .map((DiagnosticsNode node) => node.toString()) + .toList(); + + expect(description, []); + }); + + testWidgets('BottomSheetThemeData implements debugFillProperties', (WidgetTester tester) async { + final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); + BottomSheetThemeData( + backgroundColor: const Color(0xFFFFFFFF), + elevation: 2.0, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2.0)), + ).debugFillProperties(builder); + + final List description = builder.properties + .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) + .map((DiagnosticsNode node) => node.toString()) + .toList(); + + expect(description, [ + 'backgroundColor: Color(0xffffffff)', + 'elevation: 2.0', + 'shape: RoundedRectangleBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), BorderRadius.circular(2.0))', + ]); + }); + + testWidgets('Passing no BottomSheetThemeData returns defaults', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + 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), + ).first, + ); + expect(material.color, null); + expect(material.elevation, 0.0); + expect(material.shape, null); + }); + + testWidgets('BottomSheet uses values from BottomSheetThemeData', (WidgetTester tester) async { + final BottomSheetThemeData bottomSheetTheme = _bottomSheetTheme(); + + await tester.pumpWidget(MaterialApp( + theme: ThemeData(bottomSheetTheme: bottomSheetTheme), + 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), + ).first, + ); + expect(material.color, bottomSheetTheme.backgroundColor); + expect(material.elevation, bottomSheetTheme.elevation); + expect(material.shape, bottomSheetTheme.shape); + }); + + testWidgets('BottomSheet widget properties take priority over theme', (WidgetTester tester) async { + const Color backgroundColor = Colors.purple; + const double elevation = 7.0; + const ShapeBorder shape = RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(9.0)), + ); + + await tester.pumpWidget(MaterialApp( + theme: ThemeData(bottomSheetTheme: _bottomSheetTheme()), + home: Scaffold( + body: BottomSheet( + backgroundColor: backgroundColor, + elevation: elevation, + shape: shape, + onClosing: () {}, + builder: (BuildContext context) { + return Container(); + }, + ), + ), + )); + + final Material material = tester.widget( + find.descendant( + of: find.byType(BottomSheet), + matching: find.byType(Material), + ).first, + ); + expect(material.color, backgroundColor); + expect(material.elevation, elevation); + expect(material.shape, shape); + }); +} + +BottomSheetThemeData _bottomSheetTheme() { + return BottomSheetThemeData( + backgroundColor: Colors.orange, + elevation: 12.0, + shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(12)), + ); +} diff --git a/packages/flutter/test/material/modal_bottom_sheet_test.dart b/packages/flutter/test/material/modal_bottom_sheet_test.dart index 1562cdfa76..406a1527fd 100644 --- a/packages/flutter/test/material/modal_bottom_sheet_test.dart +++ b/packages/flutter/test/material/modal_bottom_sheet_test.dart @@ -251,6 +251,40 @@ void main() { semantics.dispose(); }); + testWidgets('Verify that visual properties are passed through', (WidgetTester tester) async { + final GlobalKey scaffoldKey = GlobalKey(); + const Color color = Colors.pink; + const double elevation = 9.0; + final ShapeBorder shape = BeveledRectangleBorder(borderRadius: BorderRadius.circular(12)); + + await tester.pumpWidget(MaterialApp( + home: Scaffold( + key: scaffoldKey, + body: const Center(child: Text('body')), + ), + )); + + showModalBottomSheet( + context: scaffoldKey.currentContext, + backgroundColor: color, + elevation: elevation, + shape: shape, + builder: (BuildContext context) { + return Container( + child: const Text('BottomSheet'), + ); + }, + ); + + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + final BottomSheet bottomSheet = tester.widget(find.byType(BottomSheet)); + expect(bottomSheet.backgroundColor, color); + expect(bottomSheet.elevation, elevation); + expect(bottomSheet.shape, shape); + }); + 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/persistent_bottom_sheet_test.dart b/packages/flutter/test/material/persistent_bottom_sheet_test.dart index a2d3a430df..21e0fad565 100644 --- a/packages/flutter/test/material/persistent_bottom_sheet_test.dart +++ b/packages/flutter/test/material/persistent_bottom_sheet_test.dart @@ -426,6 +426,39 @@ void main() { expect(find.byKey(bottomSheetKey), findsNothing); }); + testWidgets('Verify that visual properties are passed through', (WidgetTester tester) async { + final GlobalKey scaffoldKey = GlobalKey(); + const Color color = Colors.pink; + const double elevation = 9.0; + final ShapeBorder shape = BeveledRectangleBorder(borderRadius: BorderRadius.circular(12)); + + await tester.pumpWidget(MaterialApp( + home: Scaffold( + key: scaffoldKey, + body: const Center(child: Text('body')), + ), + )); + + scaffoldKey.currentState.showBottomSheet((BuildContext context) { + return ListView( + shrinkWrap: true, + primary: false, + children: [ + Container(height: 100.0, child: const Text('One')), + Container(height: 100.0, child: const Text('Two')), + Container(height: 100.0, child: const Text('Three')), + ], + ); + }, backgroundColor: color, elevation: elevation, shape: shape); + + await tester.pumpAndSettle(); + + final BottomSheet bottomSheet = tester.widget(find.byType(BottomSheet)); + expect(bottomSheet.backgroundColor, color); + expect(bottomSheet.elevation, elevation); + expect(bottomSheet.shape, shape); + }); + testWidgets('PersistentBottomSheetController.close dismisses the bottom sheet', (WidgetTester tester) async { final GlobalKey scaffoldKey = GlobalKey(); await tester.pumpWidget(MaterialApp(