Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Material 3 in bottom sheet #112466

Merged
merged 10 commits into from
Oct 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions dev/tools/gen_defaults/bin/gen_defaults.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -88,6 +89,7 @@ Future<void> main(List<String> args) async {
'radio_button.json',
'segmented_button_outlined.json',
'shape.json',
'sheet_bottom.json',
'slider.json',
'state.json',
'switch.json',
Expand Down Expand Up @@ -115,6 +117,7 @@ Future<void> main(List<String> 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();
Expand Down
30 changes: 30 additions & 0 deletions dev/tools/gen_defaults/lib/bottom_sheet_template.dart
Original file line number Diff line number Diff line change
@@ -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")};
}
''';
}
6 changes: 6 additions & 0 deletions dev/tools/gen_defaults/lib/template.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice optimization.

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']}), '
Expand Down
Original file line number Diff line number Diff line change
@@ -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<void>(
context: context,
builder: (BuildContext context) {
return SizedBox(
height: 200,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Modal BottomSheet'),
ElevatedButton(
child: const Text('Close BottomSheet'),
onPressed: () => Navigator.pop(context),
),
],
),
),
);
},
);
},
),
);
}
}
47 changes: 41 additions & 6 deletions packages/flutter/lib/src/material/bottom_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -274,16 +274,19 @@ class _BottomSheetState extends State<BottomSheet> {
@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<DraggableScrollableNotification>(
Expand Down Expand Up @@ -526,10 +529,11 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
child: Builder(
builder: (BuildContext context) {
final BottomSheetThemeData sheetTheme = Theme.of(context).bottomSheetTheme;
final BottomSheetThemeData defaults = Theme.of(context).useMaterial3 ? _BottomSheetDefaultsM3(context) : const BottomSheetThemeData();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the M2 defaults really just an empty BottomSheetThemeData? I am surprised we didn't have some static constants for some of these.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I didn't find any default value for M2

return _ModalBottomSheet<T>(
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,
Expand Down Expand Up @@ -727,7 +731,7 @@ Future<T?> showModalBottomSheet<T>({
clipBehavior: clipBehavior,
constraints: constraints,
isDismissible: isDismissible,
modalBarrierColor: barrierColor,
modalBarrierColor: barrierColor ?? Theme.of(context).bottomSheetTheme.modalBarrierColor,
enableDrag: enableDrag,
settings: routeSettings,
transitionAnimationController: transitionAnimationController,
Expand Down Expand Up @@ -806,3 +810,34 @@ PersistentBottomSheetController<T> showBottomSheet<T>({
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
25 changes: 25 additions & 0 deletions packages/flutter/lib/src/material/bottom_sheet_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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}
Expand All @@ -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;
Expand All @@ -77,17 +90,21 @@ class BottomSheetThemeData with Diagnosticable {
/// new values.
BottomSheetThemeData copyWith({
Color? backgroundColor,
Color? surfaceTintColor,
double? elevation,
Color? modalBackgroundColor,
Color? modalBarrierColor,
double? modalElevation,
ShapeBorder? shape,
Clip? clipBehavior,
BoxConstraints? constraints,
}) {
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,
Expand All @@ -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,
Expand All @@ -119,8 +138,10 @@ class BottomSheetThemeData with Diagnosticable {
@override
int get hashCode => Object.hash(
backgroundColor,
surfaceTintColor,
elevation,
modalBackgroundColor,
modalBarrierColor,
modalElevation,
shape,
clipBehavior,
Expand All @@ -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
Expand All @@ -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<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: null));
Expand Down
39 changes: 39 additions & 0 deletions packages/flutter/test/material/bottom_sheet_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<Material>(
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<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
Expand Down
5 changes: 5 additions & 0 deletions packages/flutter/test/material/bottom_sheet_theme_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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 {
Expand Down