Unverified Commit 6a1468db authored by rami-a's avatar rami-a Committed by GitHub

Add BottomSheetTheme to enable theming color, elevation, shape of BottomSheet (#31318)

* Introduce BottomSheetTheme and shape support for bottom sheet

* Add bottom sheet theme to ThemeData. Use theme in bottom sheet build

* Expose color, elevation, shape to showModalBottomSheet helper

* Expose color, elevation, shape to showBottomSheet helper

* Address PR feedback

* Address PR feedback

* Address additional PR feedback
parent 60a1b2b9
......@@ -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';
......
......@@ -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<BottomSheet> {
@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<DraggableScrollableNotification>(
onNotification: extentChanged,
child: widget.builder(context),
......@@ -227,12 +244,18 @@ class _ModalBottomSheet<T> 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<T> route;
final bool isScrollControlled;
final Color backgroundColor;
final double elevation;
final ShapeBorder shape;
@override
_ModalBottomSheetState<T> createState() => _ModalBottomSheetState<T>();
......@@ -279,7 +302,6 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
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<T> extends State<_ModalBottomSheet<T>> {
}
},
builder: widget.route.builder,
backgroundColor: widget.backgroundColor,
elevation: widget.elevation,
shape: widget.shape,
),
),
),
......@@ -302,8 +327,10 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
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<T> extends PopupRoute<T> {
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<T> extends PopupRoute<T> {
AnimationController _animationController;
@override
AnimationController createAnimationController() {
assert(_animationController == null);
......@@ -341,7 +371,13 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
Widget bottomSheet = MediaQuery.removePadding(
context: context,
removeTop: true,
child: _ModalBottomSheet<T>(route: this, isScrollControlled: isScrollControlled),
child: _ModalBottomSheet<T>(
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<T> extends PopupRoute<T> {
Future<T> showModalBottomSheet<T>({
@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<T> showModalBottomSheet<T>({
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<T> showBottomSheet<T>({
@required BuildContext context,
@required WidgetBuilder builder,
Color backgroundColor,
double elevation,
ShapeBorder shape,
}) {
assert(context != null);
assert(builder != null);
......@@ -448,5 +490,7 @@ PersistentBottomSheetController<T> showBottomSheet<T>({
return Scaffold.of(context).showBottomSheet<T>(
builder,
backgroundColor: backgroundColor,
elevation: elevation,
shape: shape,
);
}
// 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<Color>('backgroundColor', backgroundColor, defaultValue: null));
properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
}
}
......@@ -1541,6 +1541,8 @@ class ScaffoldState extends State<Scaffold> 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<Scaffold> with TickerProviderStateMixin {
builder: builder,
isPersistent: isPersistent,
backgroundColor: backgroundColor,
elevation: elevation,
shape: shape,
);
if (!isPersistent)
......@@ -1673,6 +1677,8 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
PersistentBottomSheetController<T> showBottomSheet<T>(
WidgetBuilder builder, {
Color backgroundColor,
double elevation,
ShapeBorder shape,
}) {
assert(() {
if (widget.bottomSheet != null) {
......@@ -1694,6 +1700,8 @@ class ScaffoldState extends State<Scaffold> 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,
),
),
);
......
......@@ -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', typography, defaultValue: defaultData.typography));
properties.add(DiagnosticsProperty<CupertinoThemeData>('cupertinoOverrideTheme', cupertinoOverrideTheme, defaultValue: defaultData.cupertinoOverrideTheme));
properties.add(DiagnosticsProperty<SnackBarThemeData>('snackBarTheme', snackBarTheme, defaultValue: defaultData.snackBarTheme));
properties.add(DiagnosticsProperty<BottomSheetThemeData>('bottomSheetTheme', bottomSheetTheme, defaultValue: defaultData.bottomSheetTheme));
}
}
......
// 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<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[]);
});
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<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[
'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<Material>(
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<Material>(
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<Material>(
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)),
);
}
......@@ -251,6 +251,40 @@ void main() {
semantics.dispose();
});
testWidgets('Verify that visual properties are passed through', (WidgetTester tester) async {
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
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<void>(
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<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
......
......@@ -426,6 +426,39 @@ void main() {
expect(find.byKey(bottomSheetKey), findsNothing);
});
testWidgets('Verify that visual properties are passed through', (WidgetTester tester) async {
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
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<void>((BuildContext context) {
return ListView(
shrinkWrap: true,
primary: false,
children: <Widget>[
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<ScaffoldState> scaffoldKey = GlobalKey();
await tester.pumpWidget(MaterialApp(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment