Unverified Commit 9fc1fd15 authored by hangyu's avatar hangyu Committed by GitHub

Update Material 3 bottom sheet (#122445)

* M3 bottomsheet

* Update bottom_sheet.dart

* Update bottom_sheet.dart

* Update bottom_sheet.dart

* Update bottom_sheet.dart

* Update bottom_sheet.dart

* Update bottom_sheet.dart

* Update bottom_sheet.dart

* Update bottom_sheet.dart

* Update bottom_sheet.dart

* Update bottom_sheet.dart

* Update packages/flutter/lib/src/material/bottom_sheet.dart
Co-authored-by: 's avatarPierre-Louis <6655696+guidezpl@users.noreply.github.com>

* Update packages/flutter/lib/src/material/bottom_sheet.dart
Co-authored-by: 's avatarPierre-Louis <6655696+guidezpl@users.noreply.github.com>

* Update packages/flutter/lib/src/material/bottom_sheet.dart
Co-authored-by: 's avatarPierre-Louis <6655696+guidezpl@users.noreply.github.com>

* Update packages/flutter/lib/src/material/bottom_sheet.dart
Co-authored-by: 's avatarPierre-Louis <6655696+guidezpl@users.noreply.github.com>

* Update packages/flutter/lib/src/material/bottom_sheet.dart
Co-authored-by: 's avatarPierre-Louis <6655696+guidezpl@users.noreply.github.com>

* Update packages/flutter/lib/src/material/bottom_sheet.dart
Co-authored-by: 's avatarPierre-Louis <6655696+guidezpl@users.noreply.github.com>

* Update bottom_sheet.dart

Update bottom_sheet_test.dart

Update bottom_sheet.dart

* showDragHandle defaults to false

* fix test

---------
Co-authored-by: 's avatarPierre-Louis <6655696+guidezpl@users.noreply.github.com>
parent d58be8c7
......@@ -17,6 +17,7 @@ class _${blockName}DefaultsM3 extends BottomSheetThemeData {
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")},
constraints: const BoxConstraints(maxWidth: 640),
);
final BuildContext context;
......@@ -30,6 +31,12 @@ class _${blockName}DefaultsM3 extends BottomSheetThemeData {
@override
Color? get shadowColor => Colors.transparent;
@override
Color? get dragHandleColor => ${componentColor("md.comp.sheet.bottom.docked.drag-handle")};
@override
Size? get dragHandleSize => ${size("md.comp.sheet.bottom.docked.drag-handle")};
}
''';
}
......@@ -5,16 +5,19 @@
import 'dart:ui' show lerpDouble;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'bottom_sheet_theme.dart';
import 'color_scheme.dart';
import 'colors.dart';
import 'constants.dart';
import 'curves.dart';
import 'debug.dart';
import 'material.dart';
import 'material_localizations.dart';
import 'material_state.dart';
import 'scaffold.dart';
import 'theme.dart';
......@@ -65,7 +68,8 @@ typedef BottomSheetDragEndHandler = void Function(
/// sheet.
/// * [BottomSheetThemeData], which can be used to customize the default
/// bottom sheet property values.
/// * <https://material.io/design/components/sheets-bottom.html>
/// * The Material 2 spec at <https://m2.material.io/components/sheets-bottom>.
/// * The Material 3 spec at <https://m3.material.io/components/bottom-sheets/overview>.
class BottomSheet extends StatefulWidget {
/// Creates a bottom sheet.
///
......@@ -76,6 +80,9 @@ class BottomSheet extends StatefulWidget {
super.key,
this.animationController,
this.enableDrag = true,
this.showDragHandle,
this.dragHandleColor,
this.dragHandleSize,
this.onDragStart,
this.onDragEnd,
this.backgroundColor,
......@@ -111,9 +118,34 @@ class BottomSheet extends StatefulWidget {
/// If true, the bottom sheet can be dragged up and down and dismissed by
/// swiping downwards.
///
/// If [showDragHandle] is true, this only applies to the content below the drag handle,
/// because the drag handle is always draggable.
///
/// Default is true.
final bool enableDrag;
/// Specifies whether a drag handle is shown.
///
/// The drag handle appears at the top of the bottom sheet. The default color is
/// [ColorScheme.onSurfaceVariant] with an opacity of 0.4 and can be customized
/// using [dragHandleColor]. The default size is `Size(32,4)` and can be customized
/// with [dragHandleSize].
///
/// If null, then the value of [BottomSheetThemeData.showDragHandle] is used. If
/// that is also null, defaults to false.
final bool? showDragHandle;
/// The bottom sheet drag handle's color.
///
/// Defaults to [BottomSheetThemeData.dragHandleColor].
/// If that is also null, defaults to [ColorScheme.onSurfaceVariant]
/// with an opacity of 0.4.
final Color? dragHandleColor;
/// Defaults to [BottomSheetThemeData.dragHandleSize].
/// If that is also null, defaults to Size(32, 4).
final Size? dragHandleSize;
/// Called when the user begins dragging the bottom sheet vertically, if
/// [enableDrag] is true.
///
......@@ -222,14 +254,19 @@ class _BottomSheetState extends State<BottomSheet> {
bool get _dismissUnderway => widget.animationController!.status == AnimationStatus.reverse;
Set<MaterialState> dragHandleMaterialState = <MaterialState>{};
void _handleDragStart(DragStartDetails details) {
setState(() {
dragHandleMaterialState.add(MaterialState.dragged);
});
widget.onDragStart?.call(details);
}
void _handleDragUpdate(DragUpdateDetails details) {
assert(
widget.enableDrag && widget.animationController != null,
"'BottomSheet.animationController' can not be null when 'BottomSheet.enableDrag' is true. "
(widget.enableDrag || (widget.showDragHandle?? false)) && widget.animationController != null,
"'BottomSheet.animationController' cannot be null when 'BottomSheet.enableDrag' or 'BottomSheet.showDragHandle' is true. "
"Use 'BottomSheet.createAnimationController' to create one, or provide another AnimationController.",
);
if (_dismissUnderway) {
......@@ -240,13 +277,16 @@ class _BottomSheetState extends State<BottomSheet> {
void _handleDragEnd(DragEndDetails details) {
assert(
widget.enableDrag && widget.animationController != null,
"'BottomSheet.animationController' can not be null when 'BottomSheet.enableDrag' is true. "
(widget.enableDrag || (widget.showDragHandle?? false)) && widget.animationController != null,
"'BottomSheet.animationController' cannot be null when 'BottomSheet.enableDrag' or 'BottomSheet.showDragHandle' is true. "
"Use 'BottomSheet.createAnimationController' to create one, or provide another AnimationController.",
);
if (_dismissUnderway) {
return;
}
setState(() {
dragHandleMaterialState.remove(MaterialState.dragged);
});
bool isClosing = false;
if (details.velocity.pixelsPerSecond.dy > _minFlingVelocity) {
final double flingVelocity = -details.velocity.pixelsPerSecond.dy / _childHeight;
......@@ -282,17 +322,55 @@ class _BottomSheetState extends State<BottomSheet> {
return false;
}
void _handleDragHandleHover(bool hovering) {
if (hovering != dragHandleMaterialState.contains(MaterialState.hovered)) {
setState(() {
if(hovering){
dragHandleMaterialState.add(MaterialState.hovered);
}
else{
dragHandleMaterialState.remove(MaterialState.hovered);
}
});
}
}
@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 bool useMaterial3 = Theme.of(context).useMaterial3;
final BottomSheetThemeData defaults = useMaterial3 ? _BottomSheetDefaultsM3(context) : const BottomSheetThemeData();
final BoxConstraints? constraints = widget.constraints ?? bottomSheetTheme.constraints ?? defaults.constraints;
final Color? color = widget.backgroundColor ?? bottomSheetTheme.backgroundColor ?? defaults.backgroundColor;
final Color? surfaceTintColor = bottomSheetTheme.surfaceTintColor ?? defaults.surfaceTintColor;
final Color? shadowColor = widget.shadowColor ?? bottomSheetTheme.shadowColor ?? defaults.shadowColor;
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;
final bool showDragHandle = widget.showDragHandle ?? (widget.enableDrag && (bottomSheetTheme.showDragHandle ?? false));
Widget? dragHandle;
if (showDragHandle){
dragHandle = _DragHandle(
onSemanticsTap: widget.onClosing,
handleHover: _handleDragHandleHover,
materialState: dragHandleMaterialState,
dragHandleColor: widget.dragHandleColor,
dragHandleSize: widget.dragHandleSize,
);
// Only add [GestureDetector] to the drag handle when the rest of the
// bottom sheet is not draggable. If the whole bottom sheet is draggable,
// no need to add it.
if(!widget.enableDrag) {
dragHandle = GestureDetector(
onVerticalDragStart: _handleDragStart,
onVerticalDragUpdate: _handleDragUpdate,
onVerticalDragEnd: _handleDragEnd,
excludeFromSemantics: true,
child: dragHandle,
);
}
}
Widget bottomSheet = Material(
key: _childKey,
......@@ -304,8 +382,19 @@ class _BottomSheetState extends State<BottomSheet> {
clipBehavior: clipBehavior,
child: NotificationListener<DraggableScrollableNotification>(
onNotification: extentChanged,
child: !showDragHandle
? widget.builder(context)
: Stack(
alignment: Alignment.topCenter,
children: <Widget>[
dragHandle!,
Padding(
padding: const EdgeInsets.only(top: kMinInteractiveDimension),
child: widget.builder(context),
),
],
),
),
);
if (constraints != null) {
......@@ -335,6 +424,55 @@ class _BottomSheetState extends State<BottomSheet> {
typedef _SizeChangeCallback<Size> = void Function(Size);
class _DragHandle extends StatelessWidget {
const _DragHandle({
required this.onSemanticsTap,
required this.handleHover,
required this.materialState,
this.dragHandleColor,
this.dragHandleSize,
});
final VoidCallback? onSemanticsTap;
final Function(bool) handleHover;
final Set<MaterialState> materialState;
final Color? dragHandleColor;
final Size? dragHandleSize;
@override
Widget build(BuildContext context) {
final BottomSheetThemeData bottomSheetTheme = Theme.of(context).bottomSheetTheme;
final BottomSheetThemeData m3Defaults = _BottomSheetDefaultsM3(context);
final Size handleSize = dragHandleSize ?? bottomSheetTheme.dragHandleSize ?? m3Defaults.dragHandleSize!;
return MouseRegion(
onEnter: (PointerEnterEvent event) => handleHover(true),
onExit: (PointerExitEvent event) => handleHover(false),
child: Semantics(
label: MaterialLocalizations.of(context).modalBarrierDismissLabel,
container: true,
onTap: onSemanticsTap,
child: SizedBox(
height: kMinInteractiveDimension,
width: kMinInteractiveDimension,
child: Center(
child: Container(
height: handleSize.height,
width: handleSize.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(handleSize.height/2),
color: MaterialStateProperty.resolveAs<Color?>(dragHandleColor, materialState)
?? MaterialStateProperty.resolveAs<Color?>(bottomSheetTheme.dragHandleColor, materialState)
?? m3Defaults.dragHandleColor,
),
),
),
),
),
);
}
}
class _BottomSheetLayoutWithSizeListener extends SingleChildRenderObjectWidget {
const _BottomSheetLayoutWithSizeListener({
......@@ -500,6 +638,7 @@ class _ModalBottomSheet<T> extends StatefulWidget {
this.constraints,
this.isScrollControlled = false,
this.enableDrag = true,
this.showDragHandle = false,
});
final ModalBottomSheetRoute<T> route;
......@@ -510,6 +649,7 @@ class _ModalBottomSheet<T> extends StatefulWidget {
final Clip? clipBehavior;
final BoxConstraints? constraints;
final bool enableDrag;
final bool showDragHandle;
@override
_ModalBottomSheetState<T> createState() => _ModalBottomSheetState<T>();
......@@ -571,6 +711,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
clipBehavior: widget.clipBehavior,
constraints: widget.constraints,
enableDrag: widget.enableDrag,
showDragHandle: widget.showDragHandle,
onDragStart: handleDragStart,
onDragEnd: handleDragEnd,
),
......@@ -658,7 +799,8 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
/// and then becomes scrollable once it reaches its maximum size.
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
/// * <https://material.io/design/components/sheets-bottom.html#modal-bottom-sheet>
/// * The Material 2 spec at <https://m2.material.io/components/sheets-bottom>.
/// * The Material 3 spec at <https://m3.material.io/components/bottom-sheets/overview>.
class ModalBottomSheetRoute<T> extends PopupRoute<T> {
/// A modal bottom sheet route.
ModalBottomSheetRoute({
......@@ -674,6 +816,7 @@ class ModalBottomSheetRoute<T> extends PopupRoute<T> {
this.modalBarrierColor,
this.isDismissible = true,
this.enableDrag = true,
this.showDragHandle,
required this.isScrollControlled,
super.settings,
this.transitionAnimationController,
......@@ -773,9 +916,22 @@ class ModalBottomSheetRoute<T> extends PopupRoute<T> {
/// If true, the bottom sheet can be dragged up and down and dismissed by
/// swiping downwards.
///
/// This applies to the content below the drag handle, if showDragHandle is true.
///
/// Defaults is true.
final bool enableDrag;
/// Specifies whether a drag handle is shown.
///
/// The drag handle appears at the top of the bottom sheet. The default color is
/// [ColorScheme.onSurfaceVariant] with an opacity of 0.4 and can be customized
/// using dragHandleColor. The default size is `Size(32,4)` and can be customized
/// with dragHandleSize.
///
/// If null, then the value of [BottomSheetThemeData.showDragHandle] is used. If
/// that is also null, defaults to false.
final bool? showDragHandle;
/// The animation controller that controls the bottom sheet's entrance and
/// exit animations.
///
......@@ -875,6 +1031,7 @@ class ModalBottomSheetRoute<T> extends PopupRoute<T> {
constraints: constraints,
isScrollControlled: isScrollControlled,
enableDrag: enableDrag,
showDragHandle: showDragHandle ?? (enableDrag && (sheetTheme.showDragHandle ?? false)),
);
},
),
......@@ -1023,7 +1180,8 @@ class _BottomSheetSuspendedCurve extends ParametricCurve<double> {
/// and then becomes scrollable once it reaches its maximum size.
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
/// * <https://material.io/design/components/sheets-bottom.html#modal-bottom-sheet>
/// * The Material 2 spec at <https://m2.material.io/components/sheets-bottom>.
/// * The Material 3 spec at <https://m3.material.io/components/bottom-sheets/overview>.
Future<T?> showModalBottomSheet<T>({
required BuildContext context,
required WidgetBuilder builder,
......@@ -1037,6 +1195,7 @@ Future<T?> showModalBottomSheet<T>({
bool useRootNavigator = false,
bool isDismissible = true,
bool enableDrag = true,
bool? showDragHandle,
bool useSafeArea = false,
RouteSettings? routeSettings,
AnimationController? transitionAnimationController,
......@@ -1061,6 +1220,7 @@ Future<T?> showModalBottomSheet<T>({
isDismissible: isDismissible,
modalBarrierColor: barrierColor ?? Theme.of(context).bottomSheetTheme.modalBarrierColor,
enableDrag: enableDrag,
showDragHandle: showDragHandle,
settings: routeSettings,
transitionAnimationController: transitionAnimationController,
anchorPoint: anchorPoint,
......@@ -1111,7 +1271,8 @@ Future<T?> showModalBottomSheet<T>({
/// * [showModalBottomSheet], which can be used to display a modal bottom
/// sheet.
/// * [Scaffold.of], for information about how to obtain the [BuildContext].
/// * <https://material.io/design/components/sheets-bottom.html#standard-bottom-sheet>
/// * The Material 2 spec at <https://m2.material.io/components/sheets-bottom>.
/// * The Material 3 spec at <https://m3.material.io/components/bottom-sheets/overview>.
PersistentBottomSheetController<T> showBottomSheet<T>({
required BuildContext context,
required WidgetBuilder builder,
......@@ -1154,19 +1315,26 @@ class _BottomSheetDefaultsM3 extends BottomSheetThemeData {
elevation: 1.0,
modalElevation: 1.0,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(28.0))),
constraints: const BoxConstraints(maxWidth: 640),
);
final BuildContext context;
late final ColorScheme _colors = Theme.of(context).colorScheme;
@override
Color? get backgroundColor => _colors.surface;
Color get backgroundColor => _colors.surface;
@override
Color get surfaceTintColor => _colors.surfaceTint;
@override
Color get shadowColor => Colors.transparent;
@override
Color? get surfaceTintColor => _colors.surfaceTint;
Color get dragHandleColor => Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.4);
@override
Color? get shadowColor => Colors.transparent;
Size get dragHandleSize => const Size(32, 4);
}
// END GENERATED TOKEN PROPERTIES - BottomSheet
......@@ -36,6 +36,9 @@ class BottomSheetThemeData with Diagnosticable {
this.shadowColor,
this.modalElevation,
this.shape,
this.showDragHandle,
this.dragHandleColor,
this.dragHandleSize,
this.clipBehavior,
this.constraints,
});
......@@ -80,6 +83,15 @@ class BottomSheetThemeData with Diagnosticable {
/// [BottomSheet] is rectangular.
final ShapeBorder? shape;
/// Overrides the default value for [BottomSheet.showDragHandle].
final bool? showDragHandle;
/// Overrides the default value for [BottomSheet.dragHandleColor].
final Color? dragHandleColor;
/// Overrides the default value for [BottomSheet.dragHandleSize].
final Size? dragHandleSize;
/// Overrides the default value for [BottomSheet.clipBehavior].
///
/// If null, [BottomSheet] uses [Clip.none].
......@@ -101,6 +113,9 @@ class BottomSheetThemeData with Diagnosticable {
Color? shadowColor,
double? modalElevation,
ShapeBorder? shape,
bool? showDragHandle,
Color? dragHandleColor,
Size? dragHandleSize,
Clip? clipBehavior,
BoxConstraints? constraints,
}) {
......@@ -113,6 +128,9 @@ class BottomSheetThemeData with Diagnosticable {
shadowColor: shadowColor ?? this.shadowColor,
modalElevation: modalElevation ?? this.modalElevation,
shape: shape ?? this.shape,
showDragHandle: showDragHandle ?? this.showDragHandle,
dragHandleColor: dragHandleColor ?? this.dragHandleColor,
dragHandleSize: dragHandleSize ?? this.dragHandleSize,
clipBehavior: clipBehavior ?? this.clipBehavior,
constraints: constraints ?? this.constraints,
);
......@@ -136,6 +154,9 @@ class BottomSheetThemeData with Diagnosticable {
shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
modalElevation: lerpDouble(a?.modalElevation, b?.modalElevation, t),
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
showDragHandle: t < 0.5 ? a?.showDragHandle : b?.showDragHandle,
dragHandleColor: Color.lerp(a?.dragHandleColor, b?.dragHandleColor, t),
dragHandleSize: Size.lerp(a?.dragHandleSize, b?.dragHandleSize, t),
clipBehavior: t < 0.5 ? a?.clipBehavior : b?.clipBehavior,
constraints: BoxConstraints.lerp(a?.constraints, b?.constraints, t),
);
......@@ -151,6 +172,9 @@ class BottomSheetThemeData with Diagnosticable {
shadowColor,
modalElevation,
shape,
showDragHandle,
dragHandleColor,
dragHandleSize,
clipBehavior,
constraints,
);
......@@ -172,6 +196,9 @@ class BottomSheetThemeData with Diagnosticable {
&& other.modalBarrierColor == modalBarrierColor
&& other.modalElevation == modalElevation
&& other.shape == shape
&& other.showDragHandle == showDragHandle
&& other.dragHandleColor == dragHandleColor
&& other.dragHandleSize == dragHandleSize
&& other.clipBehavior == clipBehavior
&& other.constraints == constraints;
}
......@@ -187,6 +214,9 @@ class BottomSheetThemeData with Diagnosticable {
properties.add(ColorProperty('modalBarrierColor', modalBarrierColor, defaultValue: null));
properties.add(DoubleProperty('modalElevation', modalElevation, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('showDragHandle', showDragHandle, defaultValue: null));
properties.add(ColorProperty('dragHandleColor', dragHandleColor, defaultValue: null));
properties.add(DiagnosticsProperty<Size>('dragHandleSize', dragHandleSize, defaultValue: null));
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: null));
properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
}
......
......@@ -2425,7 +2425,8 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
/// * [showModalBottomSheet], which can be used to display a modal bottom
/// sheet.
/// * [Scaffold.of], for information about how to obtain the [ScaffoldState].
/// * <https://material.io/design/components/sheets-bottom.html#standard-bottom-sheet>
/// * The Material 2 spec at <https://m2.material.io/components/sheets-bottom>.
/// * The Material 3 spec at <https://m3.material.io/components/bottom-sheets/overview>.
PersistentBottomSheetController<T> showBottomSheet<T>(
WidgetBuilder builder, {
Color? backgroundColor,
......
......@@ -1008,6 +1008,138 @@ void main() {
semantics.dispose();
});
testWidgets('modal BottomSheet with drag handle has semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
await tester.pumpWidget(MaterialApp(
theme: ThemeData.light(useMaterial3: true),
home: Scaffold(
key: scaffoldKey,
body: const Center(child: Text('body')),
),
));
showModalBottomSheet<void>(
context: scaffoldKey.currentContext!,
showDragHandle: true,
builder: (BuildContext context) {
return const Text('BottomSheet');
},
);
await tester.pump(); // bottom sheet show animation starts
await tester.pump(const Duration(seconds: 1)); // animation done
expect(semantics, hasSemantics(TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
children: <TestSemantics>[
TestSemantics(
children: <TestSemantics>[
TestSemantics(
label: 'Dialog',
textDirection: TextDirection.ltr,
flags: <SemanticsFlag>[
SemanticsFlag.scopesRoute,
SemanticsFlag.namesRoute,
],
children: <TestSemantics>[
TestSemantics(
label: 'BottomSheet',
textDirection: TextDirection.ltr,
children: <TestSemantics>[
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'Dismiss',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
TestSemantics(
children: <TestSemantics>[
TestSemantics(
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.dismiss],
label: 'Scrim',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
), ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Drag handle color can take MaterialStateProperty', (WidgetTester tester) async {
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
const Color defaultColor=Colors.blue;
const Color hoveringColor=Colors.green;
await tester.pumpWidget(MaterialApp(
theme: ThemeData.light(useMaterial3: true).copyWith(
bottomSheetTheme: BottomSheetThemeData(
dragHandleColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) {
return hoveringColor;
}
return defaultColor;
}),
),
),
home: Scaffold(
key: scaffoldKey,
body: const Center(child: Text('body')),
),
));
showModalBottomSheet<void>(
context: scaffoldKey.currentContext!,
showDragHandle: true,
builder: (BuildContext context) {
return const Text('BottomSheet');
},
);
await tester.pump(); // bottom sheet show animation starts
await tester.pump(const Duration(seconds: 1)); // animation done
final Finder dragHandle = find.bySemanticsLabel('Dismiss');
expect(
tester.getSize(dragHandle),
const Size(48, 48),
);
final Offset center = tester.getCenter(dragHandle);
final Offset edge = tester.getTopLeft(dragHandle) - const Offset(1, 1);
// Shows default drag handle color
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
await gesture.addPointer(location: edge);
await tester.pump();
BoxDecoration boxDecoration=tester.widget<Container>(find.descendant(
of: dragHandle,
matching: find.byWidgetPredicate((Widget widget) => widget is Container && widget.decoration != null),
)).decoration! as BoxDecoration;
expect(boxDecoration.color, defaultColor);
// Shows hovering drag handle color
await gesture.moveTo(center);
await tester.pump();
boxDecoration = tester.widget<Container>(find.descendant(
of: dragHandle,
matching: find.byWidgetPredicate((Widget widget) => widget is Container && widget.decoration != null),
)).decoration! as BoxDecoration;
expect(boxDecoration.color, hoveringColor);
});
testWidgets('showModalBottomSheet does not use root Navigator by default', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
home: Scaffold(
......@@ -1617,6 +1749,21 @@ void main() {
});
group('constraints', () {
testWidgets('default constraints are max width 640 in material 3', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(useMaterial3: true),
home: const MediaQuery(
data: MediaQueryData(size: Size(1000, 1000)),
child: Scaffold(
body: Center(child: Text('body')),
bottomSheet: Placeholder(fallbackWidth: 800),
),
),
),
);
expect(tester.getSize(find.byType(Placeholder)).width, 640);
});
testWidgets('No constraints by default for bottomSheet property', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
......
......@@ -32,6 +32,8 @@ void main() {
expect(bottomSheetTheme.shape, null);
expect(bottomSheetTheme.clipBehavior, null);
expect(bottomSheetTheme.constraints, null);
expect(bottomSheetTheme.dragHandleColor, null);
expect(bottomSheetTheme.dragHandleSize, null);
});
testWidgets('Default BottomSheetThemeData debugFillProperties', (WidgetTester tester) async {
......@@ -55,6 +57,8 @@ void main() {
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0))),
clipBehavior: Clip.antiAlias,
constraints: BoxConstraints(minWidth: 200, maxWidth: 640),
dragHandleColor: Color(0xFFFFFFFF),
dragHandleSize: Size(20, 20)
).debugFillProperties(builder);
final List<String> description = builder.properties
......@@ -67,6 +71,8 @@ void main() {
'elevation: 2.0',
'shadowColor: Color(0xff00ffff)',
'shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(2.0))',
'dragHandleColor: Color(0xffffffff)',
'dragHandleSize: Size(20.0, 20.0)',
'clipBehavior: Clip.antiAlias',
'constraints: BoxConstraints(200.0<=w<=640.0, 0.0<=h<=Infinity)',
]);
......
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