Normalize BottomAppBarTheme (#168586)

This PR is to make `BottomAppBarTheme` conform to Flutter Material's
conventions for component themes:

- Added a `BottomAppBarThemeData` class which defines overrides for the
defaults for `BottomAppBar` properties.
- Added `BottomAppBarTheme` constructor parameters:
`BottomAppBarThemeData? data` and `Widget? child`. This is now the
preferred way to configure a `BottomAppBarTheme`:

```dart
BottomAppBarTheme(
	data: BottomAppBarThemeData(
	    color: xxx,
	    height: xxx,
	    elevation: xxx,
	    shape: xxx,
	    ...
	  ),
  	child: const BottomAppBar()
)
```
These two properties are made nullable to not break existing apps which
has customized `ThemeData.bottomAppBarTheme`.

- Update `BottomAppBarTheme` to be an `InheritedWidget` subclass.
- Changed the type of component theme defaults from `BottomAppBarTheme`
to `BottomAppBarThemeData`.
- Changed the `BottomAppBarTheme bottomAppBarTheme` property to
`BottomAppBarThemeData bottomAppBarTheme` in `ThemeData` and
`ThemeData.copyWith()`. This may cause breaking changes, a migration
guide will be created on website repo.
- Add new tests for `BottomAppBarThemeData` and update the existing
`BottomAppBarTheme` tests. And also turn to `true` for `useMaterial3`
(along with usages accordingly) at the common method `_withTheme()`
since `useMaterial3` is true by default.
- Addresses the "theme normalization" sub-project within #91772.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Signed-off-by: huycozy <huy@nevercode.io>
This commit is contained in:
Huy 2025-05-15 00:38:18 +07:00 committed by GitHub
parent a3176830dc
commit 494b08b420
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 400 additions and 80 deletions

View File

@ -152,7 +152,7 @@ class _RestorableEmailState extends RestorableListenable<EmailStore> {
ThemeData _buildReplyLightTheme(BuildContext context) {
final ThemeData base = ThemeData();
return base.copyWith(
bottomAppBarTheme: const BottomAppBarTheme(color: ReplyColors.blue700),
bottomAppBarTheme: const BottomAppBarThemeData(color: ReplyColors.blue700),
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: ReplyColors.blue700,
modalBackgroundColor: Colors.white.withOpacity(0.7),
@ -192,7 +192,7 @@ ThemeData _buildReplyLightTheme(BuildContext context) {
ThemeData _buildReplyDarkTheme(BuildContext context) {
final ThemeData base = ThemeData.dark();
return base.copyWith(
bottomAppBarTheme: const BottomAppBarTheme(color: ReplyColors.darkBottomAppBarBackground),
bottomAppBarTheme: const BottomAppBarThemeData(color: ReplyColors.darkBottomAppBarBackground),
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: ReplyColors.darkDrawerBackground,
modalBackgroundColor: Colors.black.withOpacity(0.7),

View File

@ -20,7 +20,7 @@ class MaterialDemoThemeData {
color: _colorScheme.primary,
iconTheme: IconThemeData(color: _colorScheme.onPrimary),
),
bottomAppBarTheme: BottomAppBarTheme(color: _colorScheme.primary),
bottomAppBarTheme: BottomAppBarThemeData(color: _colorScheme.primary),
checkboxTheme: CheckboxThemeData(
fillColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {

View File

@ -14,7 +14,7 @@ class BottomAppBarTemplate extends TokenTemplate {
@override
String generate() => '''
class _${blockName}DefaultsM3 extends BottomAppBarTheme {
class _${blockName}DefaultsM3 extends BottomAppBarThemeData {
_${blockName}DefaultsM3(this.context)
: super(
elevation: ${elevation('md.comp.bottom-app-bar.container')},

View File

@ -67,8 +67,8 @@ class BottomAppBar extends StatefulWidget {
/// The [clipBehavior] argument defaults to [Clip.none].
/// Additionally, [elevation] must be non-negative.
///
/// If [color], [elevation], or [shape] are null, their [BottomAppBarTheme] values will be used.
/// If the corresponding [BottomAppBarTheme] property is null, then the default
/// If [color], [elevation], or [shape] are null, their [BottomAppBarThemeData] values will be used.
/// If the corresponding [BottomAppBarThemeData] property is null, then the default
/// specified in the property's documentation will be used.
const BottomAppBar({
super.key,
@ -100,7 +100,7 @@ class BottomAppBar extends StatefulWidget {
/// The bottom app bar's background color.
///
/// If this property is null then [BottomAppBarTheme.color] of
/// If this property is null then [BottomAppBarThemeData.color] of
/// [ThemeData.bottomAppBarTheme] is used. If that's null and [ThemeData.useMaterial3]
/// is true, the default value is [ColorScheme.surface]; if [ThemeData.useMaterial3]
/// is false, then the default value is `Color(0xFF424242)` in dark theme and
@ -113,14 +113,14 @@ class BottomAppBar extends StatefulWidget {
/// This controls the size of the shadow below the bottom app bar. The
/// value is non-negative.
///
/// If this property is null then [BottomAppBarTheme.elevation] of
/// If this property is null then [BottomAppBarThemeData.elevation] of
/// [ThemeData.bottomAppBarTheme] is used. If that's null and
/// [ThemeData.useMaterial3] is true, than the default value is 3 else is 8.
final double? elevation;
/// The notch that is made for the floating action button.
///
/// If this property is null then [BottomAppBarTheme.shape] of
/// If this property is null then [BottomAppBarThemeData.shape] of
/// [ThemeData.bottomAppBarTheme] is used. If that's null then the shape will
/// be rectangular with no notch.
final NotchedShape? shape;
@ -143,7 +143,7 @@ class BottomAppBar extends StatefulWidget {
/// which provide more flexibility. The intention is to eventually remove surface tint color from
/// the framework.
///
/// If this property is null, then [BottomAppBarTheme.surfaceTintColor]
/// If this property is null, then [BottomAppBarThemeData.surfaceTintColor]
/// of [ThemeData.bottomAppBarTheme] is used. If that is also null, the default
/// value is [Colors.transparent].
///
@ -154,7 +154,7 @@ class BottomAppBar extends StatefulWidget {
/// The color of the shadow below the app bar.
///
/// If this property is null, then [BottomAppBarTheme.shadowColor] of
/// If this property is null, then [BottomAppBarThemeData.shadowColor] of
/// [ThemeData.bottomAppBarTheme] is used. If that is also null, the default value
/// is fully opaque black for Material 2, and transparent for Material 3.
///
@ -188,8 +188,8 @@ class _BottomAppBarState extends State<BottomAppBar> {
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final bool isMaterial3 = theme.useMaterial3;
final BottomAppBarTheme babTheme = BottomAppBarTheme.of(context);
final BottomAppBarTheme defaults =
final BottomAppBarThemeData babTheme = BottomAppBarTheme.of(context);
final BottomAppBarThemeData defaults =
isMaterial3 ? _BottomAppBarDefaultsM3(context) : _BottomAppBarDefaultsM2(context);
final bool hasFab = Scaffold.of(context).hasFloatingActionButton;
@ -291,7 +291,7 @@ class _BottomAppBarClipper extends CustomClipper<Path> {
}
}
class _BottomAppBarDefaultsM2 extends BottomAppBarTheme {
class _BottomAppBarDefaultsM2 extends BottomAppBarThemeData {
const _BottomAppBarDefaultsM2(this.context) : super(elevation: 8.0);
final BuildContext context;
@ -315,7 +315,7 @@ class _BottomAppBarDefaultsM2 extends BottomAppBarTheme {
// dev/tools/gen_defaults/bin/gen_defaults.dart.
// dart format off
class _BottomAppBarDefaultsM3 extends BottomAppBarTheme {
class _BottomAppBarDefaultsM3 extends BottomAppBarThemeData {
_BottomAppBarDefaultsM3(this.context)
: super(
elevation: 3.0,

View File

@ -15,11 +15,11 @@ import 'theme.dart';
/// Defines default property values for descendant [BottomAppBar] widgets.
///
/// Descendant widgets obtain the current [BottomAppBarTheme] object using
/// `BottomAppBarTheme.of(context)`. Instances of [BottomAppBarTheme] can be
/// customized with [BottomAppBarTheme.copyWith].
/// Descendant widgets obtain the current [BottomAppBarThemeData] object using
/// `[BottomAppBarTheme.of]`. Instances of [BottomAppBarThemeData] can be
/// customized with [BottomAppBarThemeData.copyWith].
///
/// Typically a [BottomAppBarTheme] is specified as part of the overall [Theme]
/// Typically a [BottomAppBarThemeData] is specified as part of the overall [Theme]
/// with [ThemeData.bottomAppBarTheme].
///
/// All [BottomAppBarTheme] properties are `null` by default. When null, the
@ -30,9 +30,192 @@ import 'theme.dart';
/// * [ThemeData], which describes the overall theme information for the
/// application.
@immutable
class BottomAppBarTheme with Diagnosticable {
class BottomAppBarTheme extends InheritedTheme with Diagnosticable {
/// Creates a theme that can be used for [ThemeData.bottomAppBarTheme].
const BottomAppBarTheme({
super.key,
Color? color,
double? elevation,
NotchedShape? shape,
double? height,
Color? surfaceTintColor,
Color? shadowColor,
EdgeInsetsGeometry? padding,
BottomAppBarThemeData? data,
Widget? child,
}) : assert(
data == null ||
(color ??
elevation ??
shape ??
height ??
surfaceTintColor ??
shadowColor ??
padding) ==
null,
),
_color = color,
_elevation = elevation,
_shape = shape,
_height = height,
_surfaceTintColor = surfaceTintColor,
_shadowColor = shadowColor,
_padding = padding,
_data = data,
super(child: child ?? const SizedBox.shrink());
final BottomAppBarThemeData? _data;
final Color? _color;
final double? _elevation;
final NotchedShape? _shape;
final double? _height;
final Color? _surfaceTintColor;
final Color? _shadowColor;
final EdgeInsetsGeometry? _padding;
/// Overrides the default value for [BottomAppBar.color].
///
/// This property is obsolete and will be deprecated in a future release:
/// please use the [BottomAppBarThemeData.color] property in [data] instead.
Color? get color => _data != null ? _data.color : _color;
/// Overrides the default value for [BottomAppBar.elevation].
///
/// This property is obsolete and will be deprecated in a future release:
/// please use the [BottomAppBarThemeData.elevation] property in [data] instead.
double? get elevation => _data != null ? _data.elevation : _elevation;
/// Overrides the default value for [BottomAppBar.shape].
///
/// This property is obsolete and will be deprecated in a future release:
/// please use the [BottomAppBarThemeData.shape] property in [data] instead.
NotchedShape? get shape => _data != null ? _data.shape : _shape;
/// Overrides the default value for [BottomAppBar.height].
///
/// This property is obsolete and will be deprecated in a future release:
/// please use the [BottomAppBarThemeData.height] property in [data] instead.
double? get height => _data != null ? _data.height : _height;
/// Overrides the default value for [BottomAppBar.surfaceTintColor].
///
/// This property is obsolete and will be deprecated in a future release:
/// please use the [BottomAppBarThemeData.surfaceTintColor] property in [data] instead.
///
/// If null, [BottomAppBar] will not display an overlay color.
///
/// See [Material.surfaceTintColor] for more details.
Color? get surfaceTintColor => _data != null ? _data.surfaceTintColor : _surfaceTintColor;
/// Overrides the default value for [BottomAppBar.shadowColor].
///
/// This property is obsolete and will be deprecated in a future release:
/// please use the [BottomAppBarThemeData.shadowColor] property in [data] instead.
Color? get shadowColor => _data != null ? _data.shadowColor : _shadowColor;
/// Overrides the default value for [BottomAppBar.padding].
///
/// This property is obsolete and will be deprecated in a future release:
/// please use the [BottomAppBarThemeData.padding] property in [data] instead.
EdgeInsetsGeometry? get padding => _data != null ? _data.padding : _padding;
/// The properties used for all descendant [BottomAppBar] widgets.
BottomAppBarThemeData get data =>
_data ??
BottomAppBarThemeData(
color: _color,
elevation: _elevation,
shape: _shape,
height: _height,
surfaceTintColor: _surfaceTintColor,
shadowColor: _shadowColor,
padding: _padding,
);
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
///
/// This method is obsolete and will be deprecated in a future release:
/// please use the [BottomAppBarThemeData.copyWith] method instead.
BottomAppBarTheme copyWith({
Color? color,
double? elevation,
NotchedShape? shape,
double? height,
Color? surfaceTintColor,
Color? shadowColor,
EdgeInsetsGeometry? padding,
}) {
return BottomAppBarTheme(
color: color ?? this.color,
elevation: elevation ?? this.elevation,
shape: shape ?? this.shape,
height: height ?? this.height,
surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
shadowColor: shadowColor ?? this.shadowColor,
padding: padding ?? this.padding,
);
}
/// Returns the closest [BottomAppBarThemeData] instance given the build context.
static BottomAppBarThemeData of(BuildContext context) {
final BottomAppBarTheme? bottomAppBarTheme =
context.dependOnInheritedWidgetOfExactType<BottomAppBarTheme>();
return bottomAppBarTheme?.data ?? Theme.of(context).bottomAppBarTheme;
}
/// Linearly interpolate between two bottom app bar themes.
///
/// {@macro dart.ui.shadow.lerp}
///
/// This method is obsolete and will be deprecated in a future release:
/// please use the [BottomAppBarThemeData.lerp] instead.
static BottomAppBarTheme lerp(BottomAppBarTheme? a, BottomAppBarTheme? b, double t) {
if (identical(a, b) && a != null) {
return a;
}
return BottomAppBarTheme(
color: Color.lerp(a?.color, b?.color, t),
elevation: lerpDouble(a?.elevation, b?.elevation, t),
shape: t < 0.5 ? a?.shape : b?.shape,
height: lerpDouble(a?.height, b?.height, t),
surfaceTintColor: Color.lerp(a?.surfaceTintColor, b?.surfaceTintColor, t),
shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
);
}
@override
bool updateShouldNotify(BottomAppBarTheme oldWidget) => data != oldWidget.data;
@override
Widget wrap(BuildContext context, Widget child) {
return BottomAppBarTheme(data: data, child: child);
}
}
/// Defines default property values for descendant [BottomAppBar] widgets.
///
/// Descendant widgets obtain the current [BottomAppBarThemeData] object using
/// [BottomAppBarTheme.of]. Instances of [BottomAppBarThemeData] can be
/// customized with [BottomAppBarThemeData.copyWith].
///
/// Typically a [BottomAppBarThemeData] is specified as part of the overall [Theme]
/// with [ThemeData.bottomAppBarTheme].
///
/// All [BottomAppBarThemeData] properties are `null` by default. When null, the [BottomAppBar]
/// will use the values from [ThemeData] if they exist, otherwise it will
/// provide its own defaults. See the individual [BottomAppBar] properties for details.
///
/// See also:
///
/// * [BottomAppBar], which is the widget that this theme configures.
/// * [ThemeData], which describes the overall theme information for the
/// application.
@immutable
class BottomAppBarThemeData with Diagnosticable {
/// Creates a bottom app bar theme that can be used with [ThemeData.bottomAppBarTheme].
const BottomAppBarThemeData({
this.color,
this.elevation,
this.shape,
@ -69,7 +252,7 @@ class BottomAppBarTheme with Diagnosticable {
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
BottomAppBarTheme copyWith({
BottomAppBarThemeData copyWith({
Color? color,
double? elevation,
NotchedShape? shape,
@ -78,7 +261,7 @@ class BottomAppBarTheme with Diagnosticable {
Color? shadowColor,
EdgeInsetsGeometry? padding,
}) {
return BottomAppBarTheme(
return BottomAppBarThemeData(
color: color ?? this.color,
elevation: elevation ?? this.elevation,
shape: shape ?? this.shape,
@ -89,19 +272,14 @@ class BottomAppBarTheme with Diagnosticable {
);
}
/// The [ThemeData.bottomAppBarTheme] property of the ambient [Theme].
static BottomAppBarTheme of(BuildContext context) {
return Theme.of(context).bottomAppBarTheme;
}
/// Linearly interpolate between two BAB themes.
/// Linearly interpolate between two bottom app bar themes.
///
/// {@macro dart.ui.shadow.lerp}
static BottomAppBarTheme lerp(BottomAppBarTheme? a, BottomAppBarTheme? b, double t) {
static BottomAppBarThemeData lerp(BottomAppBarThemeData? a, BottomAppBarThemeData? b, double t) {
if (identical(a, b) && a != null) {
return a;
}
return BottomAppBarTheme(
return BottomAppBarThemeData(
color: Color.lerp(a?.color, b?.color, t),
elevation: lerpDouble(a?.elevation, b?.elevation, t),
shape: t < 0.5 ? a?.shape : b?.shape,
@ -124,7 +302,7 @@ class BottomAppBarTheme with Diagnosticable {
if (other.runtimeType != runtimeType) {
return false;
}
return other is BottomAppBarTheme &&
return other is BottomAppBarThemeData &&
other.color == color &&
other.elevation == elevation &&
other.shape == shape &&
@ -138,11 +316,13 @@ class BottomAppBarTheme with Diagnosticable {
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(ColorProperty('color', color, defaultValue: null));
properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
properties.add(DiagnosticsProperty<NotchedShape>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<double>('height', height, defaultValue: null));
properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
properties.add(DiagnosticsProperty<NotchedShape?>('shape', shape, defaultValue: null));
properties.add(DoubleProperty('height', height, defaultValue: null));
properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null));
properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
properties.add(
DiagnosticsProperty<EdgeInsetsGeometry?>('padding', padding, defaultValue: null),
);
}
}

View File

@ -320,7 +320,8 @@ class ThemeData with Diagnosticable {
AppBarTheme? appBarTheme,
BadgeThemeData? badgeTheme,
MaterialBannerThemeData? bannerTheme,
BottomAppBarTheme? bottomAppBarTheme,
// TODO(huycozy): Change the parameter type to BottomAppBarThemeData
Object? bottomAppBarTheme,
BottomNavigationBarThemeData? bottomNavigationBarTheme,
BottomSheetThemeData? bottomSheetTheme,
ButtonThemeData? buttonTheme,
@ -526,7 +527,16 @@ class ThemeData with Diagnosticable {
appBarTheme ??= const AppBarTheme();
badgeTheme ??= const BadgeThemeData();
bannerTheme ??= const MaterialBannerThemeData();
bottomAppBarTheme ??= const BottomAppBarTheme();
// TODO(huycozy): Clean this up once the type of `bottomAppBarTheme` is changed to `BottomAppBarThemeData`
if (bottomAppBarTheme != null) {
if (bottomAppBarTheme is BottomAppBarTheme) {
bottomAppBarTheme = bottomAppBarTheme.data;
} else if (bottomAppBarTheme is! BottomAppBarThemeData) {
throw ArgumentError(
'bottomAppBarTheme must be either a BottomAppBarThemeData or a BottomAppBarTheme',
);
}
}
bottomNavigationBarTheme ??= const BottomNavigationBarThemeData();
bottomSheetTheme ??= const BottomSheetThemeData();
cardTheme ??= const CardThemeData();
@ -620,7 +630,9 @@ class ThemeData with Diagnosticable {
appBarTheme: appBarTheme,
badgeTheme: badgeTheme,
bannerTheme: bannerTheme,
bottomAppBarTheme: bottomAppBarTheme,
// TODO(huycozy): Remove this type cast when bottomAppBarTheme is explicitly set to BottomAppBarThemeData
bottomAppBarTheme:
(bottomAppBarTheme as BottomAppBarThemeData?) ?? const BottomAppBarThemeData(),
bottomNavigationBarTheme: bottomNavigationBarTheme,
bottomSheetTheme: bottomSheetTheme,
buttonTheme: buttonTheme,
@ -1292,7 +1304,7 @@ class ThemeData with Diagnosticable {
final MaterialBannerThemeData bannerTheme;
/// A theme for customizing the shape, elevation, and color of a [BottomAppBar].
final BottomAppBarTheme bottomAppBarTheme;
final BottomAppBarThemeData bottomAppBarTheme;
/// A theme for customizing the appearance and layout of [BottomNavigationBar]
/// widgets.
@ -1519,7 +1531,8 @@ class ThemeData with Diagnosticable {
AppBarTheme? appBarTheme,
BadgeThemeData? badgeTheme,
MaterialBannerThemeData? bannerTheme,
BottomAppBarTheme? bottomAppBarTheme,
// TODO(huycozy): Change the parameter type to BottomAppBarThemeData
Object? bottomAppBarTheme,
BottomNavigationBarThemeData? bottomNavigationBarTheme,
BottomSheetThemeData? bottomSheetTheme,
ButtonThemeData? buttonTheme,
@ -1638,7 +1651,19 @@ class ThemeData with Diagnosticable {
appBarTheme: appBarTheme ?? this.appBarTheme,
badgeTheme: badgeTheme ?? this.badgeTheme,
bannerTheme: bannerTheme ?? this.bannerTheme,
bottomAppBarTheme: bottomAppBarTheme ?? this.bottomAppBarTheme,
// TODO(huycozy): Remove these checks when bottomAppBarTheme is a BottomAppBarThemeData
bottomAppBarTheme: () {
if (bottomAppBarTheme != null) {
if (bottomAppBarTheme is BottomAppBarTheme) {
return bottomAppBarTheme.data;
} else if (bottomAppBarTheme is! BottomAppBarThemeData) {
throw ArgumentError(
'bottomAppBarTheme must be either a BottomAppBarThemeData or a BottomAppBarTheme',
);
}
}
return bottomAppBarTheme as BottomAppBarThemeData? ?? this.bottomAppBarTheme;
}(),
bottomNavigationBarTheme: bottomNavigationBarTheme ?? this.bottomNavigationBarTheme,
bottomSheetTheme: bottomSheetTheme ?? this.bottomSheetTheme,
buttonTheme: buttonTheme ?? this.buttonTheme,
@ -1955,7 +1980,7 @@ class ThemeData with Diagnosticable {
appBarTheme: AppBarTheme.lerp(a.appBarTheme, b.appBarTheme, t),
badgeTheme: BadgeThemeData.lerp(a.badgeTheme, b.badgeTheme, t),
bannerTheme: MaterialBannerThemeData.lerp(a.bannerTheme, b.bannerTheme, t),
bottomAppBarTheme: BottomAppBarTheme.lerp(a.bottomAppBarTheme, b.bottomAppBarTheme, t),
bottomAppBarTheme: BottomAppBarThemeData.lerp(a.bottomAppBarTheme, b.bottomAppBarTheme, t),
bottomNavigationBarTheme: BottomNavigationBarThemeData.lerp(
a.bottomNavigationBarTheme,
b.bottomNavigationBarTheme,
@ -2036,7 +2061,6 @@ class ThemeData with Diagnosticable {
// order in every place that they are separated by section comments (e.g.
// GENERAL CONFIGURATION). Each section except for deprecations should be
// alphabetical by symbol name.
// GENERAL CONFIGURATION
mapEquals(other.adaptationMap, adaptationMap) &&
other.applyElevationOverlayColor == applyElevationOverlayColor &&
other.cupertinoOverrideTheme == cupertinoOverrideTheme &&
@ -2529,7 +2553,7 @@ class ThemeData with Diagnosticable {
),
);
properties.add(
DiagnosticsProperty<BottomAppBarTheme>(
DiagnosticsProperty<BottomAppBarThemeData>(
'bottomAppBarTheme',
bottomAppBarTheme,
defaultValue: defaultData.bottomAppBarTheme,

View File

@ -205,7 +205,7 @@ void main() {
return Theme(
data: Theme.of(
context,
).copyWith(bottomAppBarTheme: const BottomAppBarTheme(color: Color(0xffffff00))),
).copyWith(bottomAppBarTheme: const BottomAppBarThemeData(color: Color(0xffffff00))),
child: const Scaffold(
floatingActionButton: FloatingActionButton(onPressed: null),
bottomNavigationBar: BottomAppBar(),
@ -230,7 +230,7 @@ void main() {
return Theme(
data: Theme.of(
context,
).copyWith(bottomAppBarTheme: const BottomAppBarTheme(color: Color(0xffffff00))),
).copyWith(bottomAppBarTheme: const BottomAppBarThemeData(color: Color(0xffffff00))),
child: const Scaffold(
floatingActionButton: FloatingActionButton(onPressed: null),
bottomNavigationBar: BottomAppBar(color: Color(0xff0000ff)),
@ -251,7 +251,7 @@ void main() {
testWidgets('Material3 - Color overrides theme color', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(bottomAppBarTheme: const BottomAppBarTheme(color: Color(0xffffff00))),
theme: ThemeData(bottomAppBarTheme: const BottomAppBarThemeData(color: Color(0xffffff00))),
home: Builder(
builder: (BuildContext context) {
return const Scaffold(

View File

@ -7,22 +7,133 @@
@Tags(<String>['reduced-test-set'])
library;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('BottomAppBarTheme lerp special cases', () {
expect(BottomAppBarTheme.lerp(null, null, 0), const BottomAppBarTheme());
const BottomAppBarTheme data = BottomAppBarTheme();
expect(identical(BottomAppBarTheme.lerp(data, data, 0.5), data), true);
test('BottomAppBarThemeData copyWith, ==, hashCode, defaults', () {
expect(const BottomAppBarThemeData(), const BottomAppBarThemeData().copyWith());
expect(
const BottomAppBarThemeData().hashCode,
const BottomAppBarThemeData().copyWith().hashCode,
);
expect(const BottomAppBarThemeData().color, null);
expect(const BottomAppBarThemeData().elevation, null);
expect(const BottomAppBarThemeData().shadowColor, null);
expect(const BottomAppBarThemeData().shape, null);
expect(const BottomAppBarThemeData().height, null);
expect(const BottomAppBarThemeData().surfaceTintColor, null);
expect(const BottomAppBarThemeData().padding, null);
});
test('BottomAppBarThemeData lerp special cases', () {
const BottomAppBarThemeData theme = BottomAppBarThemeData();
expect(identical(BottomAppBarThemeData.lerp(theme, theme, 0.5), theme), true);
});
testWidgets('Default BottomAppBarThemeData debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const BottomAppBarThemeData().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('BottomAppBarThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const BottomAppBarThemeData(
color: Color(0xffff0000),
elevation: 1.0,
shape: CircularNotchedRectangle(),
height: 1.0,
shadowColor: Color(0xff0000ff),
surfaceTintColor: Color(0xff00ff00),
padding: EdgeInsets.all(8),
).debugFillProperties(builder);
final List<String> description =
builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[
'color: ${const Color(0xffff0000)}',
'elevation: 1.0',
"shape: Instance of 'CircularNotchedRectangle'",
'height: 1.0',
'surfaceTintColor: ${const Color(0xff00ff00)}',
'shadowColor: ${const Color(0xff0000ff)}',
'padding: EdgeInsets.all(8.0)',
]);
});
testWidgets('Local BottomAppBarTheme overrides defaults', (WidgetTester tester) async {
const Color color = Colors.blueAccent;
const double elevation = 1.0;
const Color shadowColor = Colors.black87;
const double height = 100.0;
const Color surfaceTintColor = Colors.transparent;
const NotchedShape shape = CircularNotchedRectangle();
const EdgeInsetsGeometry padding = EdgeInsets.all(8);
const BottomAppBarThemeData themeData = BottomAppBarThemeData(
color: color,
elevation: elevation,
shadowColor: shadowColor,
shape: shape,
height: height,
surfaceTintColor: surfaceTintColor,
padding: padding,
);
await tester.pumpWidget(_withTheme(localBABTheme: themeData));
final PhysicalShape widget = _getBabRenderObject(tester);
expect(widget.color, themeData.color);
expect(widget.elevation, themeData.elevation);
expect(widget.shadowColor, themeData.shadowColor);
final RenderBox renderBox = tester.renderObject<RenderBox>(find.byType(BottomAppBar));
expect(renderBox.size.height, themeData.height);
final bool hasFab =
Scaffold.of(tester.element(find.byType(BottomAppBar))).hasFloatingActionButton;
if (hasFab) {
expect(widget.clipper.toString(), '_BottomAppBarClipper');
} else {
expect(widget.clipper, isA<ShapeBorderClipper>());
final ShapeBorderClipper clipper = widget.clipper as ShapeBorderClipper;
expect(clipper.shape, isA<RoundedRectangleBorder>());
}
final Color effectiveColor = ElevationOverlay.applySurfaceTint(
themeData.color!,
themeData.surfaceTintColor,
themeData.elevation!,
);
expect(widget.color, effectiveColor);
// The BottomAppBar has two Padding widgets in its hierarchy:
// 1. The first Padding is from the SafeArea widget.
// 2. The second Padding is the one that applies the theme's padding.
final Padding paddingWidget = tester.widget<Padding>(
find.descendant(of: find.byType(BottomAppBar), matching: find.byType(Padding).at(1)),
);
expect(paddingWidget.padding, padding);
});
group('Material 2 tests', () {
testWidgets('Material2 - BAB theme overrides color', (WidgetTester tester) async {
const Color themedColor = Colors.black87;
const BottomAppBarTheme theme = BottomAppBarTheme(color: themedColor);
const BottomAppBarThemeData theme = BottomAppBarThemeData(color: themedColor);
await tester.pumpWidget(_withTheme(theme, useMaterial3: false));
await tester.pumpWidget(_withTheme(babTheme: theme, useMaterial3: false));
final PhysicalShape widget = _getBabRenderObject(tester);
expect(widget.color, themedColor);
@ -31,7 +142,7 @@ void main() {
testWidgets('Material2 - BAB color - Widget', (WidgetTester tester) async {
const Color babThemeColor = Colors.black87;
const Color babColor = Colors.pink;
const BottomAppBarTheme theme = BottomAppBarTheme(color: babThemeColor);
const BottomAppBarThemeData theme = BottomAppBarThemeData(color: babThemeColor);
await tester.pumpWidget(
MaterialApp(
@ -46,7 +157,7 @@ void main() {
testWidgets('Material2 - BAB color - BabTheme', (WidgetTester tester) async {
const Color babThemeColor = Colors.black87;
const BottomAppBarTheme theme = BottomAppBarTheme(color: babThemeColor);
const BottomAppBarThemeData theme = BottomAppBarThemeData(color: babThemeColor);
await tester.pumpWidget(
MaterialApp(
@ -66,7 +177,7 @@ void main() {
MaterialApp(
theme: ThemeData(
useMaterial3: false,
bottomAppBarTheme: const BottomAppBarTheme(color: themeColor),
bottomAppBarTheme: const BottomAppBarThemeData(color: themeColor),
),
home: const Scaffold(body: BottomAppBar()),
),
@ -90,13 +201,13 @@ void main() {
});
testWidgets('Material2 - BAB theme customizes shape', (WidgetTester tester) async {
const BottomAppBarTheme theme = BottomAppBarTheme(
const BottomAppBarThemeData theme = BottomAppBarThemeData(
color: Colors.white30,
shape: CircularNotchedRectangle(),
elevation: 1.0,
);
await tester.pumpWidget(_withTheme(theme, useMaterial3: false));
await tester.pumpWidget(_withTheme(babTheme: theme, useMaterial3: false));
await expectLater(
find.byKey(_painterKey),
@ -122,8 +233,8 @@ void main() {
group('Material 3 tests', () {
testWidgets('Material3 - BAB theme overrides color', (WidgetTester tester) async {
const Color themedColor = Colors.black87;
const BottomAppBarTheme theme = BottomAppBarTheme(color: themedColor, elevation: 0);
await tester.pumpWidget(_withTheme(theme, useMaterial3: true));
const BottomAppBarThemeData theme = BottomAppBarThemeData(color: themedColor, elevation: 0);
await tester.pumpWidget(_withTheme(babTheme: theme));
final PhysicalShape widget = _getBabRenderObject(tester);
expect(widget.color, themedColor);
@ -132,7 +243,7 @@ void main() {
testWidgets('Material3 - BAB color - Widget', (WidgetTester tester) async {
const Color babThemeColor = Colors.black87;
const Color babColor = Colors.pink;
const BottomAppBarTheme theme = BottomAppBarTheme(color: babThemeColor);
const BottomAppBarThemeData theme = BottomAppBarThemeData(color: babThemeColor);
await tester.pumpWidget(
MaterialApp(
@ -149,7 +260,7 @@ void main() {
testWidgets('Material3 - BAB color - BabTheme', (WidgetTester tester) async {
const Color babThemeColor = Colors.black87;
const BottomAppBarTheme theme = BottomAppBarTheme(color: babThemeColor);
const BottomAppBarThemeData theme = BottomAppBarThemeData(color: babThemeColor);
await tester.pumpWidget(
MaterialApp(
@ -180,12 +291,12 @@ void main() {
testWidgets('Material3 - BAB theme overrides surfaceTintColor', (WidgetTester tester) async {
const Color color = Colors.blue; // base color that the surface tint will be applied to
const Color babThemeSurfaceTintColor = Colors.black87;
const BottomAppBarTheme theme = BottomAppBarTheme(
const BottomAppBarThemeData theme = BottomAppBarThemeData(
color: color,
surfaceTintColor: babThemeSurfaceTintColor,
elevation: 0,
);
await tester.pumpWidget(_withTheme(theme, useMaterial3: true));
await tester.pumpWidget(_withTheme(babTheme: theme));
final PhysicalShape widget = _getBabRenderObject(tester);
expect(widget.color, ElevationOverlay.applySurfaceTint(color, babThemeSurfaceTintColor, 0));
@ -193,11 +304,11 @@ void main() {
testWidgets('Material3 - BAB theme overrides shadowColor', (WidgetTester tester) async {
const Color babThemeShadowColor = Colors.yellow;
const BottomAppBarTheme theme = BottomAppBarTheme(
const BottomAppBarThemeData theme = BottomAppBarThemeData(
shadowColor: babThemeShadowColor,
elevation: 0,
);
await tester.pumpWidget(_withTheme(theme, useMaterial3: true));
await tester.pumpWidget(_withTheme(babTheme: theme));
final PhysicalShape widget = _getBabRenderObject(tester);
expect(widget.shadowColor, babThemeShadowColor);
@ -207,7 +318,9 @@ void main() {
const Color color = Colors.white10; // base color that the surface tint will be applied to
const Color babThemeSurfaceTintColor = Colors.black87;
const Color babSurfaceTintColor = Colors.pink;
const BottomAppBarTheme theme = BottomAppBarTheme(surfaceTintColor: babThemeSurfaceTintColor);
const BottomAppBarThemeData theme = BottomAppBarThemeData(
surfaceTintColor: babThemeSurfaceTintColor,
);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(bottomAppBarTheme: theme),
@ -224,7 +337,7 @@ void main() {
testWidgets('Material3 - BAB surfaceTintColor - BabTheme', (WidgetTester tester) async {
const Color color = Colors.blue; // base color that the surface tint will be applied to
const Color babThemeColor = Colors.black87;
const BottomAppBarTheme theme = BottomAppBarTheme(surfaceTintColor: babThemeColor);
const BottomAppBarThemeData theme = BottomAppBarThemeData(surfaceTintColor: babThemeColor);
await tester.pumpWidget(
MaterialApp(
@ -247,20 +360,23 @@ PhysicalShape _getBabRenderObject(WidgetTester tester) {
final Key _painterKey = UniqueKey();
Widget _withTheme(BottomAppBarTheme theme, {required bool useMaterial3}) {
Widget _withTheme({
BottomAppBarThemeData? babTheme,
BottomAppBarThemeData? localBABTheme,
bool useMaterial3 = true,
}) {
Widget babWidget = const BottomAppBar(
child: Row(children: <Widget>[Icon(Icons.add), Expanded(child: SizedBox()), Icon(Icons.add)]),
);
if (localBABTheme != null) {
babWidget = BottomAppBarTheme(data: localBABTheme, child: babWidget);
}
return MaterialApp(
theme: ThemeData(useMaterial3: useMaterial3, bottomAppBarTheme: theme),
theme: ThemeData(useMaterial3: useMaterial3, bottomAppBarTheme: babTheme),
home: Scaffold(
floatingActionButton: const FloatingActionButton(onPressed: null),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: RepaintBoundary(
key: _painterKey,
child: const BottomAppBar(
child: Row(
children: <Widget>[Icon(Icons.add), Expanded(child: SizedBox()), Icon(Icons.add)],
),
),
),
bottomNavigationBar: RepaintBoundary(key: _painterKey, child: babWidget),
),
);
}

View File

@ -1332,7 +1332,7 @@ void main() {
appBarTheme: const AppBarTheme(backgroundColor: Colors.black),
badgeTheme: const BadgeThemeData(backgroundColor: Colors.black),
bannerTheme: const MaterialBannerThemeData(backgroundColor: Colors.black),
bottomAppBarTheme: const BottomAppBarTheme(color: Colors.black),
bottomAppBarTheme: const BottomAppBarThemeData(color: Colors.black),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
type: BottomNavigationBarType.fixed,
),
@ -1461,7 +1461,7 @@ void main() {
appBarTheme: const AppBarTheme(backgroundColor: Colors.white),
badgeTheme: const BadgeThemeData(backgroundColor: Colors.black),
bannerTheme: const MaterialBannerThemeData(backgroundColor: Colors.white),
bottomAppBarTheme: const BottomAppBarTheme(color: Colors.white),
bottomAppBarTheme: const BottomAppBarThemeData(color: Colors.white),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
type: BottomNavigationBarType.shifting,
),