Normalize AppBarTheme (#169130)

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

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

```dart
AppBarTheme(
      data: AppBarThemeData(),
      child: Scaffold(
        appBar: AppBar(
          title: xxx,
          leading: xxx,
          actions: xxx,
     ),
),
```
These two properties are made nullable to not break existing apps which
has customized `ThemeData.appBarTheme`.

- Update `AppBarTheme` to be an `InheritedWidget` subclass.
- Changed the type of component theme defaults from `AppBarTheme` to
`AppBarThemeData`.
- Add new tests for `AppBarThemeData` and update the existing
`AppBarTheme` tests.
- This also temporarily changes `AppBarThemeData` to `Object?` in
`ThemeData` class, to bypass g3 tests, following
https://github.com/flutter/flutter/pull/168586#discussion_r2082606584.
- Addresses the "theme normalization" sub-project within
https://github.com/flutter/flutter/issues/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>
Co-authored-by: Qun Cheng <chengqunq@gmail.com>
This commit is contained in:
Huy 2025-06-21 06:07:37 +07:00 committed by GitHub
parent be8cdeb84f
commit 41acbd244d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 693 additions and 154 deletions

View File

@ -10,7 +10,7 @@ class AppBarTemplate extends TokenTemplate {
@override
String generate() => '''
class _${blockName}DefaultsM3 extends AppBarTheme {
class _${blockName}DefaultsM3 extends AppBarThemeData {
_${blockName}DefaultsM3(this.context)
: super(
elevation: ${elevation('md.comp.top-app-bar.small.container')},

View File

@ -777,7 +777,7 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
/// Whether the color should be animated.
final bool animateColor;
bool _getEffectiveCenterTitle(ThemeData theme) {
bool _getEffectiveCenterTitle(ThemeData theme, AppBarThemeData appbarTheme) {
bool platformCenter() {
switch (theme.platform) {
case TargetPlatform.android:
@ -791,7 +791,7 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
}
}
return centerTitle ?? theme.appBarTheme.centerTitle ?? platformCenter();
return centerTitle ?? appbarTheme.centerTitle ?? platformCenter();
}
@override
@ -882,8 +882,8 @@ class _AppBarState extends State<AppBar> {
assert(debugCheckHasMaterialLocalizations(context));
final ThemeData theme = Theme.of(context);
final IconButtonThemeData iconButtonTheme = IconButtonTheme.of(context);
final AppBarTheme appBarTheme = AppBarTheme.of(context);
final AppBarTheme defaults =
final AppBarThemeData appBarTheme = AppBarTheme.of(context);
final AppBarThemeData defaults =
theme.useMaterial3 ? _AppBarDefaultsM3(context) : _AppBarDefaultsM2(context);
final ScaffoldState? scaffold = Scaffold.maybeOf(context);
final ModalRoute<dynamic>? parentRoute = ModalRoute.of(context);
@ -1119,7 +1119,7 @@ class _AppBarState extends State<AppBar> {
leading: leading,
middle: title,
trailing: actions,
centerMiddle: widget._getEffectiveCenterTitle(theme),
centerMiddle: widget._getEffectiveCenterTitle(theme, appBarTheme),
middleSpacing:
widget.titleSpacing ?? appBarTheme.titleSpacing ?? NavigationToolbar.kMiddleSpacing,
);
@ -2223,8 +2223,8 @@ class _ScrollUnderFlexibleSpace extends StatelessWidget {
@override
Widget build(BuildContext context) {
late final AppBarTheme appBarTheme = AppBarTheme.of(context);
late final AppBarTheme defaults =
late final AppBarThemeData appBarTheme = AppBarTheme.of(context);
late final AppBarThemeData defaults =
Theme.of(context).useMaterial3 ? _AppBarDefaultsM3(context) : _AppBarDefaultsM2(context);
final FlexibleSpaceBarSettings settings =
context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!;
@ -2442,7 +2442,7 @@ mixin _ScrollUnderFlexibleConfig {
}
// Hand coded defaults based on Material Design 2.
class _AppBarDefaultsM2 extends AppBarTheme {
class _AppBarDefaultsM2 extends AppBarThemeData {
_AppBarDefaultsM2(this.context)
: super(
elevation: 4.0,
@ -2484,7 +2484,7 @@ class _AppBarDefaultsM2 extends AppBarTheme {
// dev/tools/gen_defaults/bin/gen_defaults.dart.
// dart format off
class _AppBarDefaultsM3 extends AppBarTheme {
class _AppBarDefaultsM3 extends AppBarThemeData {
_AppBarDefaultsM3(this.context)
: super(
elevation: 0.0,

View File

@ -13,46 +13,111 @@ import 'package:flutter/widgets.dart';
import 'theme.dart';
/// Overrides the default values of visual properties for descendant
/// [AppBar] widgets.
/// Defines default property values for descendant [AppBar] widgets.
///
/// Descendant widgets obtain the current [AppBarTheme] object with
/// `AppBarTheme.of(context)`. Instances of [AppBarTheme] can be customized
/// with [AppBarTheme.copyWith].
/// Descendant widgets obtain the current [AppBarThemeData] object with
/// [AppBarTheme.of]. Instances of [AppBarThemeData] can be customized
/// with [AppBarThemeData.copyWith].
///
/// Typically an [AppBarTheme] is specified as part of the overall [Theme] with
/// Typically an [AppBarThemeData] is specified as part of the overall [Theme] with
/// [ThemeData.appBarTheme].
///
/// All [AppBarTheme] properties are `null` by default. When null, the [AppBar]
/// compute its own default values, typically based on the overall theme's
/// [ThemeData.colorScheme], [ThemeData.textTheme], and [ThemeData.iconTheme].
/// All [AppBarTheme] properties are `null` by default. When null, the
// [AppBar] constructor provides defaults.
///
/// See also:
///
/// * [ThemeData], which describes the overall theme information for the
/// application.
@immutable
class AppBarTheme with Diagnosticable {
class AppBarTheme extends InheritedTheme with Diagnosticable {
/// Creates a theme that can be used for [ThemeData.appBarTheme].
const AppBarTheme({
super.key,
Color? color,
Color? backgroundColor,
this.foregroundColor,
this.elevation,
this.scrolledUnderElevation,
this.shadowColor,
this.surfaceTintColor,
this.shape,
this.iconTheme,
this.actionsIconTheme,
this.centerTitle,
this.titleSpacing,
this.leadingWidth,
this.toolbarHeight,
this.toolbarTextStyle,
this.titleTextStyle,
this.systemOverlayStyle,
this.actionsPadding,
Color? foregroundColor,
double? elevation,
double? scrolledUnderElevation,
Color? shadowColor,
Color? surfaceTintColor,
ShapeBorder? shape,
IconThemeData? iconTheme,
IconThemeData? actionsIconTheme,
bool? centerTitle,
double? titleSpacing,
double? leadingWidth,
double? toolbarHeight,
TextStyle? toolbarTextStyle,
TextStyle? titleTextStyle,
SystemUiOverlayStyle? systemOverlayStyle,
EdgeInsetsGeometry? actionsPadding,
AppBarThemeData? data,
Widget? child,
}) : assert(
color == null || backgroundColor == null,
'The color and backgroundColor parameters mean the same thing. Only specify one.',
),
backgroundColor = backgroundColor ?? color;
assert(
data == null ||
(color ??
backgroundColor ??
foregroundColor ??
elevation ??
scrolledUnderElevation ??
shadowColor ??
surfaceTintColor ??
shape ??
iconTheme ??
actionsIconTheme ??
centerTitle ??
titleSpacing ??
leadingWidth ??
toolbarHeight ??
toolbarTextStyle ??
titleTextStyle ??
systemOverlayStyle ??
actionsPadding) ==
null,
),
_backgroundColor = backgroundColor ?? color,
_foregroundColor = foregroundColor,
_elevation = elevation,
_scrolledUnderElevation = scrolledUnderElevation,
_shadowColor = shadowColor,
_surfaceTintColor = surfaceTintColor,
_shape = shape,
_iconTheme = iconTheme,
_actionsIconTheme = actionsIconTheme,
_centerTitle = centerTitle,
_titleSpacing = titleSpacing,
_leadingWidth = leadingWidth,
_toolbarHeight = toolbarHeight,
_toolbarTextStyle = toolbarTextStyle,
_titleTextStyle = titleTextStyle,
_systemOverlayStyle = systemOverlayStyle,
_actionsPadding = actionsPadding,
_data = data,
super(child: child ?? const SizedBox());
final AppBarThemeData? _data;
final Color? _backgroundColor;
final Color? _foregroundColor;
final double? _elevation;
final double? _scrolledUnderElevation;
final Color? _shadowColor;
final Color? _surfaceTintColor;
final ShapeBorder? _shape;
final IconThemeData? _iconTheme;
final IconThemeData? _actionsIconTheme;
final bool? _centerTitle;
final double? _titleSpacing;
final double? _leadingWidth;
final double? _toolbarHeight;
final TextStyle? _toolbarTextStyle;
final TextStyle? _titleTextStyle;
final SystemUiOverlayStyle? _systemOverlayStyle;
final EdgeInsetsGeometry? _actionsPadding;
/// Overrides the default value of [AppBar.backgroundColor] in all
/// descendant [AppBar] widgets.
@ -61,7 +126,7 @@ class AppBarTheme with Diagnosticable {
///
/// * [foregroundColor], which overrides the default value of
/// [AppBar.foregroundColor] in all descendant [AppBar] widgets.
final Color? backgroundColor;
Color? get backgroundColor => _data != null ? _data.backgroundColor : _backgroundColor;
/// Overrides the default value of [AppBar.foregroundColor] in all
/// descendant [AppBar] widgets.
@ -70,27 +135,28 @@ class AppBarTheme with Diagnosticable {
///
/// * [backgroundColor], which overrides the default value of
/// [AppBar.backgroundColor] in all descendant [AppBar] widgets.
final Color? foregroundColor;
Color? get foregroundColor => _data != null ? _data.foregroundColor : _foregroundColor;
/// Overrides the default value of [AppBar.elevation] in all
/// descendant [AppBar] widgets.
final double? elevation;
double? get elevation => _data != null ? _data.elevation : _elevation;
/// Overrides the default value of [AppBar.scrolledUnderElevation] in all
/// descendant [AppBar] widgets.
final double? scrolledUnderElevation;
double? get scrolledUnderElevation =>
_data != null ? _data.scrolledUnderElevation : _scrolledUnderElevation;
/// Overrides the default value of [AppBar.shadowColor] in all
/// descendant [AppBar] widgets.
final Color? shadowColor;
Color? get shadowColor => _data != null ? _data.shadowColor : _shadowColor;
/// Overrides the default value of [AppBar.surfaceTintColor] in all
/// descendant [AppBar] widgets.
final Color? surfaceTintColor;
Color? get surfaceTintColor => _data != null ? _data.surfaceTintColor : _surfaceTintColor;
/// Overrides the default value of [AppBar.shape] in all
/// descendant [AppBar] widgets.
final ShapeBorder? shape;
ShapeBorder? get shape => _data != null ? _data.shape : _shape;
/// Overrides the default value of [AppBar.iconTheme] in all
/// descendant [AppBar] widgets.
@ -101,7 +167,7 @@ class AppBarTheme with Diagnosticable {
/// [AppBar.actionsIconTheme] in all descendant [AppBar] widgets.
/// * [foregroundColor], which overrides the default value
/// [AppBar.foregroundColor] in all descendant [AppBar] widgets.
final IconThemeData? iconTheme;
IconThemeData? get iconTheme => _data != null ? _data.iconTheme : _iconTheme;
/// Overrides the default value of [AppBar.actionsIconTheme] in all
/// descendant [AppBar] widgets.
@ -112,21 +178,21 @@ class AppBarTheme with Diagnosticable {
/// [AppBar.iconTheme] in all descendant [AppBar] widgets.
/// * [foregroundColor], which overrides the default value
/// [AppBar.foregroundColor] in all descendant [AppBar] widgets.
final IconThemeData? actionsIconTheme;
IconThemeData? get actionsIconTheme => _data != null ? _data.actionsIconTheme : _actionsIconTheme;
/// Overrides the default value of [AppBar.centerTitle]
/// property in all descendant [AppBar] widgets.
final bool? centerTitle;
bool? get centerTitle => _data != null ? _data.centerTitle : _centerTitle;
/// Overrides the default value of the obsolete [AppBar.titleSpacing]
/// property in all descendant [AppBar] widgets.
///
/// If null, [AppBar] uses default value of [NavigationToolbar.kMiddleSpacing].
final double? titleSpacing;
double? get titleSpacing => _data != null ? _data.titleSpacing : _titleSpacing;
/// Overrides the default value of the [AppBar.leadingWidth]
/// property in all descendant [AppBar] widgets.
final double? leadingWidth;
double? get leadingWidth => _data != null ? _data.leadingWidth : _leadingWidth;
/// Overrides the default value of the [AppBar.toolbarHeight]
/// property in all descendant [AppBar] widgets.
@ -135,7 +201,7 @@ class AppBarTheme with Diagnosticable {
///
/// * [AppBar.preferredHeightFor], which computes the overall
/// height of an AppBar widget, taking this value into account.
final double? toolbarHeight;
double? get toolbarHeight => _data != null ? _data.toolbarHeight : _toolbarHeight;
/// Overrides the default value of the obsolete [AppBar.toolbarTextStyle]
/// property in all descendant [AppBar] widgets.
@ -144,7 +210,7 @@ class AppBarTheme with Diagnosticable {
///
/// * [titleTextStyle], which overrides the default of [AppBar.titleTextStyle]
/// in all descendant [AppBar] widgets.
final TextStyle? toolbarTextStyle;
TextStyle? get toolbarTextStyle => _data != null ? _data.toolbarTextStyle : _toolbarTextStyle;
/// Overrides the default value of [AppBar.titleTextStyle]
/// property in all descendant [AppBar] widgets.
@ -153,18 +219,45 @@ class AppBarTheme with Diagnosticable {
///
/// * [toolbarTextStyle], which overrides the default of [AppBar.toolbarTextStyle]
/// in all descendant [AppBar] widgets.
final TextStyle? titleTextStyle;
TextStyle? get titleTextStyle => _data != null ? _data.titleTextStyle : _titleTextStyle;
/// Overrides the default value of [AppBar.systemOverlayStyle]
/// property in all descendant [AppBar] widgets.
final SystemUiOverlayStyle? systemOverlayStyle;
SystemUiOverlayStyle? get systemOverlayStyle =>
_data != null ? _data.systemOverlayStyle : _systemOverlayStyle;
/// Overrides the default value of [AppBar.actionsPadding]
/// property in all descendant [AppBar] widgets.
final EdgeInsetsGeometry? actionsPadding;
EdgeInsetsGeometry? get actionsPadding => _data != null ? _data.actionsPadding : _actionsPadding;
/// The properties used for all descendant [AppBar] widgets.
AppBarThemeData get data =>
_data ??
AppBarThemeData(
backgroundColor: _backgroundColor,
foregroundColor: _foregroundColor,
elevation: _elevation,
scrolledUnderElevation: _scrolledUnderElevation,
shadowColor: _shadowColor,
surfaceTintColor: _surfaceTintColor,
shape: _shape,
iconTheme: _iconTheme,
actionsIconTheme: _actionsIconTheme,
centerTitle: _centerTitle,
titleSpacing: _titleSpacing,
leadingWidth: _leadingWidth,
toolbarHeight: _toolbarHeight,
toolbarTextStyle: _toolbarTextStyle,
titleTextStyle: _titleTextStyle,
systemOverlayStyle: _systemOverlayStyle,
actionsPadding: _actionsPadding,
);
/// Creates a copy of this object with the given fields replaced with the
/// new values.
///
/// This method is obsolete and will be deprecated in a future release:
/// please use the [AppBarThemeData.copyWith] method instead.
AppBarTheme copyWith({
IconThemeData? actionsIconTheme,
Color? color,
@ -210,9 +303,10 @@ class AppBarTheme with Diagnosticable {
);
}
/// The [ThemeData.appBarTheme] property of the ambient [Theme].
static AppBarTheme of(BuildContext context) {
return Theme.of(context).appBarTheme;
/// Returns the closest [AppBarThemeData] instance given the build context.
static AppBarThemeData of(BuildContext context) {
final AppBarTheme? appBarTheme = context.dependOnInheritedWidgetOfExactType<AppBarTheme>();
return appBarTheme?.data ?? Theme.of(context).appBarTheme;
}
/// Linearly interpolate between two AppBar themes.
@ -243,6 +337,182 @@ class AppBarTheme with Diagnosticable {
);
}
@override
bool updateShouldNotify(covariant AppBarTheme oldWidget) => data != oldWidget.data;
@override
Widget wrap(BuildContext context, Widget child) {
return AppBarTheme(data: data, child: child);
}
}
/// Defines default property values for descendant [AppBar] widgets.
///
/// Descendant widgets obtain the current [AppBarThemeData] object using
/// [AppBarTheme.of]. Instances of [AppBarThemeData] can be
/// customized with [AppBarThemeData.copyWith].
///
/// Typically an [AppBarThemeData] is specified as part of the overall [Theme]
/// with [ThemeData.appBarTheme].
///
/// All [AppBarThemeData] properties are `null` by default. When null, the [AppBar]
/// will use the values from [ThemeData] if they exist, otherwise it will
/// provide its own defaults. See the individual [AppBar] properties for details.
///
/// See also:
///
/// * [AppBar], which is the widget that this theme configures.
/// * [ThemeData], which describes the overall theme information for the
/// application.
@immutable
class AppBarThemeData with Diagnosticable {
/// Creates an app bar theme that can be used with [ThemeData.appBarTheme].
const AppBarThemeData({
this.backgroundColor,
this.foregroundColor,
Color? color,
this.elevation,
this.scrolledUnderElevation,
this.shadowColor,
this.surfaceTintColor,
this.shape,
this.iconTheme,
this.actionsIconTheme,
this.centerTitle,
this.titleSpacing,
this.leadingWidth,
this.toolbarHeight,
this.toolbarTextStyle,
this.titleTextStyle,
this.systemOverlayStyle,
this.actionsPadding,
}) : assert(
color == null || backgroundColor == null,
'The color and backgroundColor parameters mean the same thing. Only specify one.',
);
/// Overrides the default value of [AppBar.backgroundColor].
final Color? backgroundColor;
/// Overrides the default value of [AppBar.foregroundColor].
final Color? foregroundColor;
/// Overrides the default value of [AppBar.elevation].
final double? elevation;
/// Overrides the default value of [AppBar.scrolledUnderElevation].
final double? scrolledUnderElevation;
/// Overrides the default value of [AppBar.shadowColor].
final Color? shadowColor;
/// Overrides the default value of [AppBar.surfaceTintColor].
final Color? surfaceTintColor;
/// Overrides the default value of [AppBar.shape].
final ShapeBorder? shape;
/// Overrides the default value of [AppBar.iconTheme].
final IconThemeData? iconTheme;
/// Overrides the default value of [AppBar.actionsIconTheme].
final IconThemeData? actionsIconTheme;
/// Overrides the default value of [AppBar.centerTitle].
final bool? centerTitle;
/// Overrides the default value of [AppBar.titleSpacing].
final double? titleSpacing;
/// Overrides the default value of [AppBar.leadingWidth].
final double? leadingWidth;
/// Overrides the default value of [AppBar.toolbarHeight].
final double? toolbarHeight;
/// Overrides the default value of [AppBar.toolbarTextStyle].
final TextStyle? toolbarTextStyle;
/// Overrides the default value of [AppBar.titleTextStyle].
final TextStyle? titleTextStyle;
/// Overrides the default value of [AppBar.systemOverlayStyle].
final SystemUiOverlayStyle? systemOverlayStyle;
/// Overrides the default value of [AppBar.actionsPadding].
final EdgeInsetsGeometry? actionsPadding;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
AppBarThemeData copyWith({
Color? backgroundColor,
Color? foregroundColor,
Color? color,
double? elevation,
double? scrolledUnderElevation,
Color? shadowColor,
Color? surfaceTintColor,
ShapeBorder? shape,
IconThemeData? iconTheme,
IconThemeData? actionsIconTheme,
bool? centerTitle,
double? titleSpacing,
double? leadingWidth,
double? toolbarHeight,
TextStyle? toolbarTextStyle,
TextStyle? titleTextStyle,
SystemUiOverlayStyle? systemOverlayStyle,
EdgeInsetsGeometry? actionsPadding,
}) {
return AppBarThemeData(
backgroundColor: backgroundColor ?? color ?? this.backgroundColor,
foregroundColor: foregroundColor ?? this.foregroundColor,
elevation: elevation ?? this.elevation,
scrolledUnderElevation: scrolledUnderElevation ?? this.scrolledUnderElevation,
shadowColor: shadowColor ?? this.shadowColor,
surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
shape: shape ?? this.shape,
iconTheme: iconTheme ?? this.iconTheme,
actionsIconTheme: actionsIconTheme ?? this.actionsIconTheme,
centerTitle: centerTitle ?? this.centerTitle,
titleSpacing: titleSpacing ?? this.titleSpacing,
leadingWidth: leadingWidth ?? this.leadingWidth,
toolbarHeight: toolbarHeight ?? this.toolbarHeight,
toolbarTextStyle: toolbarTextStyle ?? this.toolbarTextStyle,
titleTextStyle: titleTextStyle ?? this.titleTextStyle,
systemOverlayStyle: systemOverlayStyle ?? this.systemOverlayStyle,
actionsPadding: actionsPadding ?? this.actionsPadding,
);
}
/// Linearly interpolate between two app bar themes.
///
/// {@macro dart.ui.shadow.lerp}
static AppBarThemeData lerp(AppBarThemeData a, AppBarThemeData b, double t) {
if (identical(a, b)) {
return a;
}
return AppBarThemeData(
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
foregroundColor: Color.lerp(a.foregroundColor, b.foregroundColor, t),
elevation: lerpDouble(a.elevation, b.elevation, t),
scrolledUnderElevation: lerpDouble(a.scrolledUnderElevation, b.scrolledUnderElevation, t),
shadowColor: Color.lerp(a.shadowColor, b.shadowColor, t),
surfaceTintColor: Color.lerp(a.surfaceTintColor, b.surfaceTintColor, t),
shape: ShapeBorder.lerp(a.shape, b.shape, t),
iconTheme: IconThemeData.lerp(a.iconTheme, b.iconTheme, t),
actionsIconTheme: IconThemeData.lerp(a.actionsIconTheme, b.actionsIconTheme, t),
centerTitle: t < 0.5 ? a.centerTitle : b.centerTitle,
titleSpacing: lerpDouble(a.titleSpacing, b.titleSpacing, t),
leadingWidth: lerpDouble(a.leadingWidth, b.leadingWidth, t),
toolbarHeight: lerpDouble(a.toolbarHeight, b.toolbarHeight, t),
toolbarTextStyle: TextStyle.lerp(a.toolbarTextStyle, b.toolbarTextStyle, t),
titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t),
systemOverlayStyle: t < 0.5 ? a.systemOverlayStyle : b.systemOverlayStyle,
actionsPadding: EdgeInsetsGeometry.lerp(a.actionsPadding, b.actionsPadding, t),
);
}
@override
int get hashCode => Object.hash(
backgroundColor,
@ -272,7 +542,7 @@ class AppBarTheme with Diagnosticable {
if (other.runtimeType != runtimeType) {
return false;
}
return other is AppBarTheme &&
return other is AppBarThemeData &&
other.backgroundColor == backgroundColor &&
other.foregroundColor == foregroundColor &&
other.elevation == elevation &&
@ -297,13 +567,9 @@ class AppBarTheme with Diagnosticable {
super.debugFillProperties(properties);
properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
properties.add(ColorProperty('foregroundColor', foregroundColor, defaultValue: null));
properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
properties.add(
DiagnosticsProperty<double>(
'scrolledUnderElevation',
scrolledUnderElevation,
defaultValue: null,
),
DoubleProperty('scrolledUnderElevation', scrolledUnderElevation, defaultValue: null),
);
properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null));
@ -313,9 +579,9 @@ class AppBarTheme with Diagnosticable {
DiagnosticsProperty<IconThemeData>('actionsIconTheme', actionsIconTheme, defaultValue: null),
);
properties.add(DiagnosticsProperty<bool>('centerTitle', centerTitle, defaultValue: null));
properties.add(DiagnosticsProperty<double>('titleSpacing', titleSpacing, defaultValue: null));
properties.add(DiagnosticsProperty<double>('leadingWidth', leadingWidth, defaultValue: null));
properties.add(DiagnosticsProperty<double>('toolbarHeight', toolbarHeight, defaultValue: null));
properties.add(DoubleProperty('titleSpacing', titleSpacing, defaultValue: null));
properties.add(DoubleProperty('leadingWidth', leadingWidth, defaultValue: null));
properties.add(DoubleProperty('toolbarHeight', toolbarHeight, defaultValue: null));
properties.add(
DiagnosticsProperty<TextStyle>('toolbarTextStyle', toolbarTextStyle, defaultValue: null),
);
@ -323,7 +589,22 @@ class AppBarTheme with Diagnosticable {
DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null),
);
properties.add(
DiagnosticsProperty<EdgeInsetsGeometry>('actionsPadding', actionsPadding, defaultValue: null),
DiagnosticsProperty<SystemUiOverlayStyle?>(
'systemOverlayStyle',
systemOverlayStyle,
defaultValue: null,
description:
systemOverlayStyle == null
? null
: 'SystemUiOverlayStyle(${<String>[if (systemOverlayStyle?.systemNavigationBarColor != null) 'systemNavigationBarColor: ${systemOverlayStyle?.systemNavigationBarColor}', if (systemOverlayStyle?.systemNavigationBarDividerColor != null) 'systemNavigationBarDividerColor: ${systemOverlayStyle?.systemNavigationBarDividerColor}', if (systemOverlayStyle?.systemNavigationBarIconBrightness != null) 'systemNavigationBarIconBrightness: ${systemOverlayStyle?.systemNavigationBarIconBrightness}', if (systemOverlayStyle?.statusBarColor != null) 'statusBarColor: ${systemOverlayStyle?.statusBarColor}', if (systemOverlayStyle?.statusBarBrightness != null) 'statusBarBrightness: ${systemOverlayStyle?.statusBarBrightness}', if (systemOverlayStyle?.statusBarIconBrightness != null) 'statusBarIconBrightness: ${systemOverlayStyle?.statusBarIconBrightness}', if (systemOverlayStyle?.systemStatusBarContrastEnforced != null) 'systemStatusBarContrastEnforced: ${systemOverlayStyle?.systemStatusBarContrastEnforced}', if (systemOverlayStyle?.systemNavigationBarContrastEnforced != null) 'systemNavigationBarContrastEnforced: ${systemOverlayStyle?.systemNavigationBarContrastEnforced}'].where((String s) => s.isNotEmpty).join(', ')})',
),
);
properties.add(
DiagnosticsProperty<EdgeInsetsGeometry?>(
'actionsPadding',
actionsPadding,
defaultValue: null,
),
);
}
}

View File

@ -262,7 +262,7 @@ abstract class SearchDelegate<T> {
final ThemeData theme = Theme.of(context);
final ColorScheme colorScheme = theme.colorScheme;
return theme.copyWith(
appBarTheme: AppBarTheme(
appBarTheme: AppBarThemeData(
systemOverlayStyle:
colorScheme.brightness == Brightness.dark
? SystemUiOverlayStyle.light

View File

@ -317,7 +317,8 @@ class ThemeData with Diagnosticable {
Typography? typography,
// COMPONENT THEMES
ActionIconThemeData? actionIconTheme,
AppBarTheme? appBarTheme,
// TODO(huycozy): Change the parameter type to AppBarThemeData
Object? appBarTheme,
BadgeThemeData? badgeTheme,
MaterialBannerThemeData? bannerTheme,
BottomAppBarThemeData? bottomAppBarTheme,
@ -523,7 +524,14 @@ class ThemeData with Diagnosticable {
: const IconThemeData(color: Colors.black);
// COMPONENT THEMES
appBarTheme ??= const AppBarTheme();
// TODO(huycozy): Clean this up once the type of `appBarTheme` is changed to `appBarThemeData`
if (appBarTheme != null) {
if (appBarTheme is AppBarTheme) {
appBarTheme = appBarTheme.data;
} else if (appBarTheme is! AppBarThemeData) {
throw ArgumentError('appBarTheme must be either a AppBarThemeData or a AppBarTheme');
}
}
badgeTheme ??= const BadgeThemeData();
bannerTheme ??= const MaterialBannerThemeData();
bottomAppBarTheme ??= const BottomAppBarThemeData();
@ -617,7 +625,8 @@ class ThemeData with Diagnosticable {
primaryIconTheme: primaryIconTheme,
// COMPONENT THEMES
actionIconTheme: actionIconTheme,
appBarTheme: appBarTheme,
// TODO(huycozy): Remove this type cast when appBarTheme is explicitly set to appBarThemeData
appBarTheme: (appBarTheme as AppBarThemeData?) ?? const AppBarThemeData(),
badgeTheme: badgeTheme,
bannerTheme: bannerTheme,
bottomAppBarTheme: bottomAppBarTheme,
@ -1283,7 +1292,7 @@ class ThemeData with Diagnosticable {
/// A theme for customizing the color, elevation, brightness, iconTheme and
/// textTheme of [AppBar]s.
final AppBarTheme appBarTheme;
final AppBarThemeData appBarTheme;
/// A theme for customizing the color of [Badge]s.
final BadgeThemeData badgeTheme;
@ -1516,7 +1525,8 @@ class ThemeData with Diagnosticable {
Typography? typography,
// COMPONENT THEMES
ActionIconThemeData? actionIconTheme,
AppBarTheme? appBarTheme,
// TODO(huycozy): Change the parameter type to AppBarThemeData
Object? appBarTheme,
BadgeThemeData? badgeTheme,
MaterialBannerThemeData? bannerTheme,
BottomAppBarThemeData? bottomAppBarTheme,
@ -1635,7 +1645,17 @@ class ThemeData with Diagnosticable {
typography: typography ?? this.typography,
// COMPONENT THEMES
actionIconTheme: actionIconTheme ?? this.actionIconTheme,
appBarTheme: appBarTheme ?? this.appBarTheme,
// TODO(huycozy): Remove this check when appBarTheme is a AppBarThemeData
appBarTheme: () {
if (appBarTheme != null) {
if (appBarTheme is AppBarTheme) {
return appBarTheme.data;
} else if (appBarTheme is! AppBarThemeData) {
throw ArgumentError('appBarTheme must be either a AppBarThemeData or a AppBarTheme');
}
}
return appBarTheme as AppBarThemeData? ?? this.appBarTheme;
}(),
badgeTheme: badgeTheme ?? this.badgeTheme,
bannerTheme: bannerTheme ?? this.bannerTheme,
bottomAppBarTheme: bottomAppBarTheme ?? this.bottomAppBarTheme,
@ -1952,7 +1972,7 @@ class ThemeData with Diagnosticable {
typography: Typography.lerp(a.typography, b.typography, t),
// COMPONENT THEMES
actionIconTheme: ActionIconThemeData.lerp(a.actionIconTheme, b.actionIconTheme, t),
appBarTheme: AppBarTheme.lerp(a.appBarTheme, b.appBarTheme, t),
appBarTheme: AppBarThemeData.lerp(a.appBarTheme, b.appBarTheme, t),
badgeTheme: BadgeThemeData.lerp(a.badgeTheme, b.badgeTheme, t),
bannerTheme: MaterialBannerThemeData.lerp(a.bannerTheme, b.bannerTheme, t),
bottomAppBarTheme: BottomAppBarThemeData.lerp(a.bottomAppBarTheme, b.bottomAppBarTheme, t),
@ -2504,7 +2524,7 @@ class ThemeData with Diagnosticable {
),
);
properties.add(
DiagnosticsProperty<AppBarTheme>(
DiagnosticsProperty<AppBarThemeData>(
'appBarTheme',
appBarTheme,
defaultValue: defaultData.appBarTheme,

View File

@ -1515,7 +1515,7 @@ void main() {
await tester.pumpWidget(
MaterialApp(
key: GlobalKey(),
theme: ThemeData(appBarTheme: const AppBarTheme()),
theme: ThemeData(appBarTheme: const AppBarThemeData()),
home: Scaffold(appBar: AppBar(title: const Text('title'))),
),
);
@ -2770,7 +2770,7 @@ void main() {
Widget buildFrame({double? themeToolbarHeight, double? appBarToolbarHeight}) {
final AppBar appBar = AppBar(toolbarHeight: appBarToolbarHeight);
return MaterialApp(
theme: ThemeData(appBarTheme: AppBarTheme(toolbarHeight: themeToolbarHeight)),
theme: ThemeData(appBarTheme: AppBarThemeData(toolbarHeight: themeToolbarHeight)),
home: Builder(
builder: (BuildContext context) {
preferredHeight = AppBar.preferredHeightFor(context, appBar.preferredSize);
@ -3432,7 +3432,7 @@ void main() {
await tester.pumpWidget(
MaterialApp(
key: GlobalKey(),
theme: ThemeData(useMaterial3: false, appBarTheme: const AppBarTheme()),
theme: ThemeData(useMaterial3: false, appBarTheme: const AppBarThemeData()),
home: Scaffold(appBar: AppBar(title: const Text('title'))),
),
);

View File

@ -9,7 +9,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
const AppBarTheme appBarTheme = AppBarTheme(
const AppBarThemeData appBarTheme = AppBarThemeData(
backgroundColor: Color(0xff00ff00),
foregroundColor: Color(0xff00ffff),
elevation: 4.0,
@ -27,9 +27,27 @@ void main() {
return PrimaryScrollController.of(tester.element(find.byType(CustomScrollView)));
}
test('AppBarTheme copyWith, ==, hashCode basics', () {
expect(const AppBarTheme(), const AppBarTheme().copyWith());
expect(const AppBarTheme().hashCode, const AppBarTheme().copyWith().hashCode);
test('AppBarThemeData copyWith, ==, hashCode basics', () {
expect(const AppBarThemeData(), const AppBarThemeData().copyWith());
expect(const AppBarThemeData().hashCode, const AppBarThemeData().copyWith().hashCode);
expect(const AppBarThemeData().backgroundColor, null);
expect(const AppBarThemeData().foregroundColor, null);
expect(const AppBarThemeData().elevation, null);
expect(const AppBarThemeData().scrolledUnderElevation, null);
expect(const AppBarThemeData().shadowColor, null);
expect(const AppBarThemeData().surfaceTintColor, null);
expect(const AppBarThemeData().shape, null);
expect(const AppBarThemeData().iconTheme, null);
expect(const AppBarThemeData().actionsIconTheme, null);
expect(const AppBarThemeData().centerTitle, null);
expect(const AppBarThemeData().titleSpacing, null);
expect(const AppBarThemeData().leadingWidth, null);
expect(const AppBarThemeData().toolbarHeight, null);
expect(const AppBarThemeData().toolbarTextStyle, null);
expect(const AppBarThemeData().titleTextStyle, null);
expect(const AppBarThemeData().systemOverlayStyle, null);
expect(const AppBarThemeData().actionsPadding, null);
});
test('AppBarTheme lerp special cases', () {
@ -123,7 +141,7 @@ void main() {
});
testWidgets('AppBar uses values from AppBarTheme', (WidgetTester tester) async {
final AppBarTheme appBarTheme = _appBarTheme();
final AppBarThemeData appBarTheme = _appBarTheme();
await tester.pumpWidget(
MaterialApp(
@ -238,7 +256,7 @@ void main() {
testWidgets('AppBarTheme properties take priority over ThemeData properties', (
WidgetTester tester,
) async {
final AppBarTheme appBarTheme = _appBarTheme();
final AppBarThemeData appBarTheme = _appBarTheme();
await tester.pumpWidget(
MaterialApp(
@ -497,7 +515,7 @@ void main() {
) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(appBarTheme: const AppBarTheme(centerTitle: true)),
theme: ThemeData(appBarTheme: const AppBarThemeData(centerTitle: true)),
home: Scaffold(appBar: AppBar(title: const Text('Title'))),
),
);
@ -511,64 +529,65 @@ void main() {
) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(appBarTheme: const AppBarTheme(centerTitle: true)),
theme: ThemeData(appBarTheme: const AppBarThemeData(centerTitle: true)),
home: Scaffold(appBar: AppBar(title: const Text('Title'), centerTitle: false)),
),
);
final NavigationToolbar navToolBar = tester.widget(find.byType(NavigationToolbar));
// The AppBar.centerTitle should be used instead of AppBarTheme.centerTitle.
// The AppBar.centerTitle should be used instead of AppBarThemeData.centerTitle.
expect(navToolBar.centerMiddle, false);
});
testWidgets('AppBar.centerTitle adapts to TargetPlatform when AppBarTheme.centerTitle is null', (
testWidgets(
'AppBar.centerTitle adapts to TargetPlatform when AppBarThemeData.centerTitle is null',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(platform: TargetPlatform.iOS),
home: Scaffold(appBar: AppBar(title: const Text('Title'))),
),
);
final NavigationToolbar navToolBar = tester.widget(find.byType(NavigationToolbar));
// When ThemeData.platform is TargetPlatform.iOS, and AppBarThemeData is null,
// the value of NavigationToolBar.centerMiddle should be true.
expect(navToolBar.centerMiddle, true);
},
);
testWidgets('AppBar.shadowColor takes priority over AppBarThemeData.shadowColor', (
WidgetTester tester,
) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(platform: TargetPlatform.iOS),
home: Scaffold(appBar: AppBar(title: const Text('Title'))),
),
);
final NavigationToolbar navToolBar = tester.widget(find.byType(NavigationToolbar));
// When ThemeData.platform is TargetPlatform.iOS, and AppBarTheme is null,
// the value of NavigationToolBar.centerMiddle should be true.
expect(navToolBar.centerMiddle, true);
});
testWidgets('AppBar.shadowColor takes priority over AppBarTheme.shadowColor', (
WidgetTester tester,
) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(appBarTheme: const AppBarTheme(shadowColor: Colors.red)),
theme: ThemeData(appBarTheme: const AppBarThemeData(shadowColor: Colors.red)),
home: Scaffold(appBar: AppBar(title: const Text('Title'), shadowColor: Colors.yellow)),
),
);
final AppBar appBar = tester.widget(find.byType(AppBar));
// The AppBar.shadowColor should be used instead of AppBarTheme.shadowColor.
// The AppBar.shadowColor should be used instead of AppBarThemeData.shadowColor.
expect(appBar.shadowColor, Colors.yellow);
});
testWidgets('AppBar.surfaceTintColor takes priority over AppBarTheme.surfaceTintColor', (
testWidgets('AppBar.surfaceTintColor takes priority over AppBarThemeData.surfaceTintColor', (
WidgetTester tester,
) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(appBarTheme: const AppBarTheme(surfaceTintColor: Colors.red)),
theme: ThemeData(appBarTheme: const AppBarThemeData(surfaceTintColor: Colors.red)),
home: Scaffold(appBar: AppBar(title: const Text('Title'), surfaceTintColor: Colors.yellow)),
),
);
final AppBar appBar = tester.widget(find.byType(AppBar));
// The AppBar.surfaceTintColor should be used instead of AppBarTheme.surfaceTintColor.
// The AppBar.surfaceTintColor should be used instead of AppBarThemeData.surfaceTintColor.
expect(appBar.surfaceTintColor, Colors.yellow);
});
testWidgets(
'Material3 - AppBarTheme.iconTheme.color takes priority over IconButtonTheme.foregroundColor',
'Material3 - AppBarThemeData.iconTheme.color takes priority over IconButtonTheme.foregroundColor',
(WidgetTester tester) async {
const IconThemeData overallIconTheme = IconThemeData(color: Colors.yellow);
await tester.pumpWidget(
@ -577,7 +596,7 @@ void main() {
iconButtonTheme: IconButtonThemeData(
style: IconButton.styleFrom(foregroundColor: Colors.red),
),
appBarTheme: const AppBarTheme(iconTheme: overallIconTheme),
appBarTheme: const AppBarThemeData(iconTheme: overallIconTheme),
),
home: Scaffold(
appBar: AppBar(
@ -598,14 +617,14 @@ void main() {
);
testWidgets(
'Material3 - AppBarTheme.iconTheme.size takes priority over IconButtonTheme.iconSize',
'Material3 - AppBarThemeData.iconTheme.size takes priority over IconButtonTheme.iconSize',
(WidgetTester tester) async {
const IconThemeData overallIconTheme = IconThemeData(size: 30.0);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
iconButtonTheme: IconButtonThemeData(style: IconButton.styleFrom(iconSize: 32.0)),
appBarTheme: const AppBarTheme(iconTheme: overallIconTheme),
appBarTheme: const AppBarThemeData(iconTheme: overallIconTheme),
),
home: Scaffold(
appBar: AppBar(
@ -626,7 +645,7 @@ void main() {
);
testWidgets(
'Material3 - AppBarTheme.actionsIconTheme.color takes priority over IconButtonTheme.foregroundColor',
'Material3 - AppBarThemeData.actionsIconTheme.color takes priority over IconButtonTheme.foregroundColor',
(WidgetTester tester) async {
const IconThemeData actionsIconTheme = IconThemeData(color: Colors.yellow);
final IconButtonThemeData iconButtonTheme = IconButtonThemeData(
@ -637,7 +656,7 @@ void main() {
MaterialApp(
theme: ThemeData(
iconButtonTheme: iconButtonTheme,
appBarTheme: const AppBarTheme(actionsIconTheme: actionsIconTheme),
appBarTheme: const AppBarThemeData(actionsIconTheme: actionsIconTheme),
),
home: Scaffold(
appBar: AppBar(
@ -658,7 +677,7 @@ void main() {
);
testWidgets(
'Material3 - AppBarTheme.actionsIconTheme.size takes priority over IconButtonTheme.iconSize',
'Material3 - AppBarThemeData.actionsIconTheme.size takes priority over IconButtonTheme.iconSize',
(WidgetTester tester) async {
const IconThemeData actionsIconTheme = IconThemeData(size: 30.0);
final IconButtonThemeData iconButtonTheme = IconButtonThemeData(
@ -668,7 +687,7 @@ void main() {
MaterialApp(
theme: ThemeData(
iconButtonTheme: iconButtonTheme,
appBarTheme: const AppBarTheme(actionsIconTheme: actionsIconTheme),
appBarTheme: const AppBarThemeData(actionsIconTheme: actionsIconTheme),
),
home: Scaffold(
appBar: AppBar(
@ -692,12 +711,12 @@ void main() {
);
testWidgets(
'Material3 - AppBarTheme.foregroundColor takes priority over IconButtonTheme.foregroundColor',
'Material3 - AppBarThemeData.foregroundColor takes priority over IconButtonTheme.foregroundColor',
(WidgetTester tester) async {
final IconButtonThemeData iconButtonTheme = IconButtonThemeData(
style: IconButton.styleFrom(foregroundColor: Colors.red),
);
const AppBarTheme appBarTheme = AppBarTheme(foregroundColor: Colors.green);
const AppBarThemeData appBarTheme = AppBarThemeData(foregroundColor: Colors.green);
final ThemeData themeData = ThemeData(
iconButtonTheme: iconButtonTheme,
appBarTheme: appBarTheme,
@ -724,11 +743,11 @@ void main() {
},
);
testWidgets('AppBar uses AppBarTheme.titleSpacing', (WidgetTester tester) async {
testWidgets('AppBar uses AppBarThemeData.titleSpacing', (WidgetTester tester) async {
const double kTitleSpacing = 10;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(appBarTheme: const AppBarTheme(titleSpacing: kTitleSpacing)),
theme: ThemeData(appBarTheme: const AppBarThemeData(titleSpacing: kTitleSpacing)),
home: Scaffold(appBar: AppBar(title: const Text('Title'))),
),
);
@ -737,13 +756,13 @@ void main() {
expect(navToolBar.middleSpacing, kTitleSpacing);
});
testWidgets('AppBar.titleSpacing takes priority over AppBarTheme.titleSpacing', (
testWidgets('AppBar.titleSpacing takes priority over AppBarThemeData.titleSpacing', (
WidgetTester tester,
) async {
const double kTitleSpacing = 10;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(appBarTheme: const AppBarTheme(titleSpacing: kTitleSpacing)),
theme: ThemeData(appBarTheme: const AppBarThemeData(titleSpacing: kTitleSpacing)),
home: Scaffold(appBar: AppBar(title: const Text('Title'), titleSpacing: 40)),
),
);
@ -752,11 +771,11 @@ void main() {
expect(navToolBar.middleSpacing, 40);
});
testWidgets('AppBar uses AppBarTheme.leadingWidth', (WidgetTester tester) async {
testWidgets('AppBar uses AppBarThemeData.leadingWidth', (WidgetTester tester) async {
const double kLeadingWidth = 80;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(appBarTheme: const AppBarTheme(leadingWidth: kLeadingWidth)),
theme: ThemeData(appBarTheme: const AppBarThemeData(leadingWidth: kLeadingWidth)),
home: Scaffold(appBar: AppBar(leading: const Icon(Icons.chevron_left))),
),
);
@ -767,13 +786,13 @@ void main() {
expect(leadingConstraints.minWidth, kLeadingWidth);
});
testWidgets('AppBar.leadingWidth takes priority over AppBarTheme.leadingWidth', (
testWidgets('AppBar.leadingWidth takes priority over AppBarThemeData.leadingWidth', (
WidgetTester tester,
) async {
const double kLeadingWidth = 80;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(appBarTheme: const AppBarTheme(leadingWidth: kLeadingWidth)),
theme: ThemeData(appBarTheme: const AppBarThemeData(leadingWidth: kLeadingWidth)),
home: Scaffold(appBar: AppBar(leading: const Icon(Icons.chevron_left), leadingWidth: 40)),
),
);
@ -784,11 +803,11 @@ void main() {
expect(leadingConstraints.minWidth, 40);
});
testWidgets('SliverAppBar uses AppBarTheme.titleSpacing', (WidgetTester tester) async {
testWidgets('SliverAppBar uses AppBarThemeData.titleSpacing', (WidgetTester tester) async {
const double kTitleSpacing = 10;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(appBarTheme: const AppBarTheme(titleSpacing: kTitleSpacing)),
theme: ThemeData(appBarTheme: const AppBarThemeData(titleSpacing: kTitleSpacing)),
home: const CustomScrollView(slivers: <Widget>[SliverAppBar(title: Text('Title'))]),
),
);
@ -797,13 +816,13 @@ void main() {
expect(navToolBar.middleSpacing, kTitleSpacing);
});
testWidgets('SliverAppBar.titleSpacing takes priority over AppBarTheme.titleSpacing ', (
testWidgets('SliverAppBar.titleSpacing takes priority over AppBarThemeData.titleSpacing ', (
WidgetTester tester,
) async {
const double kTitleSpacing = 10;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(appBarTheme: const AppBarTheme(titleSpacing: kTitleSpacing)),
theme: ThemeData(appBarTheme: const AppBarThemeData(titleSpacing: kTitleSpacing)),
home: const CustomScrollView(
slivers: <Widget>[SliverAppBar(title: Text('Title'), titleSpacing: 40)],
),
@ -814,11 +833,11 @@ void main() {
expect(navToolbar.middleSpacing, 40);
});
testWidgets('SliverAppBar uses AppBarTheme.leadingWidth', (WidgetTester tester) async {
testWidgets('SliverAppBar uses AppBarThemeData.leadingWidth', (WidgetTester tester) async {
const double kLeadingWidth = 80;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(appBarTheme: const AppBarTheme(leadingWidth: kLeadingWidth)),
theme: ThemeData(appBarTheme: const AppBarThemeData(leadingWidth: kLeadingWidth)),
home: const CustomScrollView(
slivers: <Widget>[SliverAppBar(leading: Icon(Icons.chevron_left))],
),
@ -831,13 +850,13 @@ void main() {
expect(leadingConstraints.minWidth, kLeadingWidth);
});
testWidgets('SliverAppBar.leadingWidth takes priority over AppBarTheme.leadingWidth ', (
testWidgets('SliverAppBar.leadingWidth takes priority over AppBarThemeData.leadingWidth ', (
WidgetTester tester,
) async {
const double kLeadingWidth = 80;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(appBarTheme: const AppBarTheme(leadingWidth: kLeadingWidth)),
theme: ThemeData(appBarTheme: const AppBarThemeData(leadingWidth: kLeadingWidth)),
home: const CustomScrollView(
slivers: <Widget>[SliverAppBar(leading: Icon(Icons.chevron_left), leadingWidth: 40)],
),
@ -850,7 +869,7 @@ void main() {
expect(leadingConstraints.minWidth, 40);
});
testWidgets('SliverAppBar.medium uses AppBarTheme properties', (WidgetTester tester) async {
testWidgets('SliverAppBar.medium uses AppBarThemeData properties', (WidgetTester tester) async {
const String title = 'Medium App Bar';
await tester.pumpWidget(
@ -902,7 +921,7 @@ void main() {
expect(titleOffset.dx, iconOffset.dx + appBarTheme.titleSpacing!);
});
testWidgets('SliverAppBar.medium properties take priority over AppBarTheme properties', (
testWidgets('SliverAppBar.medium properties take priority over AppBarThemeData properties', (
WidgetTester tester,
) async {
const String title = 'Medium App Bar';
@ -974,7 +993,7 @@ void main() {
expect(titleOffset.dx, iconOffset.dx + titleSpacing);
});
testWidgets('SliverAppBar.large uses AppBarTheme properties', (WidgetTester tester) async {
testWidgets('SliverAppBar.large uses AppBarThemeData properties', (WidgetTester tester) async {
const String title = 'Large App Bar';
await tester.pumpWidget(
@ -1026,7 +1045,7 @@ void main() {
expect(titleOffset.dx, iconOffset.dx + appBarTheme.titleSpacing!);
});
testWidgets('SliverAppBar.large properties take priority over AppBarTheme properties', (
testWidgets('SliverAppBar.large properties take priority over AppBarThemeData properties', (
WidgetTester tester,
) async {
const String title = 'Large App Bar';
@ -1100,10 +1119,10 @@ void main() {
testWidgets('SliverAppBar medium & large supports foregroundColor', (WidgetTester tester) async {
const String title = 'AppBar title';
const AppBarTheme appBarTheme = AppBarTheme(foregroundColor: Color(0xff00ff20));
const AppBarThemeData appBarTheme = AppBarThemeData(foregroundColor: Color(0xff00ff20));
const Color foregroundColor = Color(0xff001298);
Widget buildWidget({Color? color, AppBarTheme? appBarTheme}) {
Widget buildWidget({Color? color, AppBarThemeData? appBarTheme}) {
return MaterialApp(
theme: ThemeData(appBarTheme: appBarTheme),
home: CustomScrollView(
@ -1118,7 +1137,7 @@ void main() {
await tester.pumpWidget(buildWidget(appBarTheme: appBarTheme));
// Test AppBarTheme.foregroundColor parameter.
// Test AppBarThemeData.foregroundColor parameter.
RichText mediumTitle = tester.widget(find.byType(RichText).first);
expect(mediumTitle.text.style!.color, appBarTheme.foregroundColor);
RichText largeTitle = tester.widget(find.byType(RichText).first);
@ -1133,9 +1152,9 @@ void main() {
expect(largeTitle.text.style!.color, foregroundColor);
});
testWidgets('Default AppBarTheme debugFillProperties', (WidgetTester tester) async {
testWidgets('Default AppBarThemeData debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const AppBarTheme().debugFillProperties(builder);
const AppBarThemeData().debugFillProperties(builder);
final List<String> description =
builder.properties
@ -1146,9 +1165,9 @@ void main() {
expect(description, <String>[]);
});
testWidgets('AppBarTheme implements debugFillProperties', (WidgetTester tester) async {
testWidgets('AppBarThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const AppBarTheme(
const AppBarThemeData(
backgroundColor: Color(0xff000000),
foregroundColor: Color(0xff000001),
elevation: 8.0,
@ -1157,6 +1176,7 @@ void main() {
surfaceTintColor: Color(0xff000003),
shape: StadiumBorder(),
iconTheme: IconThemeData(color: Color(0xff000004)),
actionsIconTheme: IconThemeData(color: Color(0xff000004)),
centerTitle: true,
titleSpacing: 40.0,
leadingWidth: 96,
@ -1164,6 +1184,7 @@ void main() {
toolbarTextStyle: TextStyle(color: Color(0xff000005)),
titleTextStyle: TextStyle(color: Color(0xff000006)),
systemOverlayStyle: SystemUiOverlayStyle(systemNavigationBarColor: Color(0xff000007)),
actionsPadding: EdgeInsets.symmetric(horizontal: 8.0),
).debugFillProperties(builder);
final List<String> description =
@ -1183,12 +1204,15 @@ void main() {
'surfaceTintColor: ${const Color(0xff000003)}',
'shape: StadiumBorder(BorderSide(width: 0.0, style: none))',
'iconTheme: IconThemeData#00000(color: ${const Color(0xff000004)})',
'actionsIconTheme: IconThemeData#00000(color: ${const Color(0xff000004)})',
'centerTitle: true',
'titleSpacing: 40.0',
'leadingWidth: 96.0',
'toolbarHeight: 96.0',
'toolbarTextStyle: TextStyle(inherit: true, color: ${const Color(0xff000005)})',
'titleTextStyle: TextStyle(inherit: true, color: ${const Color(0xff000006)})',
'systemOverlayStyle: SystemUiOverlayStyle(systemNavigationBarColor: ${const Color(0xff000007)})',
'actionsPadding: EdgeInsets(8.0, 0.0, 8.0, 0.0)',
]),
);
@ -1199,13 +1223,227 @@ void main() {
// the web and the rest of Flutter's target platforms.
}, skip: kIsWeb); // https://github.com/flutter/flutter/issues/87364
testWidgets('Local AppBarTheme overrides defaults', (WidgetTester tester) async {
const Color backgroundColor = Colors.blueAccent;
const Color foregroundColor = Colors.white;
const double elevation = 1.0;
const double scrolledUnderElevation = 2.0;
const Color shadowColor = Colors.black87;
const Color surfaceTintColor = Colors.transparent;
const ShapeBorder shape = RoundedRectangleBorder();
const IconThemeData iconTheme = IconThemeData(color: Colors.red);
const IconThemeData actionsIconTheme = IconThemeData(color: Color(0xFF6750A4));
const bool centerTitle = true;
const double titleSpacing = 20.0;
const double leadingWidth = 80.0;
const double toolbarHeight = 100.0;
const TextStyle toolbarTextStyle = TextStyle(color: Colors.yellow);
const TextStyle titleTextStyle = TextStyle(color: Colors.orange);
const SystemUiOverlayStyle systemOverlayStyle = SystemUiOverlayStyle.dark;
const EdgeInsetsGeometry actionsPadding = EdgeInsets.all(8);
const AppBarThemeData appbarThemeData = AppBarThemeData(
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
elevation: elevation,
scrolledUnderElevation: scrolledUnderElevation,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
shape: shape,
iconTheme: iconTheme,
actionsIconTheme: actionsIconTheme,
centerTitle: centerTitle,
titleSpacing: titleSpacing,
leadingWidth: leadingWidth,
toolbarHeight: toolbarHeight,
toolbarTextStyle: toolbarTextStyle,
titleTextStyle: titleTextStyle,
systemOverlayStyle: systemOverlayStyle,
actionsPadding: actionsPadding,
);
await tester.pumpWidget(
MaterialApp(
home: AppBarTheme(
data: appbarThemeData,
child: Scaffold(
appBar: AppBar(
title: const Text('Title'),
leading: IconButton(icon: const Icon(Icons.menu), onPressed: () {}),
actions: <Widget>[IconButton(icon: const Icon(Icons.add), onPressed: () {})],
),
),
),
),
);
final Material material = _getAppBarMaterial(tester);
expect(material.color, backgroundColor);
expect(material.elevation, elevation);
expect(material.shadowColor, shadowColor);
expect(material.surfaceTintColor, surfaceTintColor);
expect(material.shape, shape);
final IconTheme leadingIconTheme = _getAppBarIconTheme(tester);
expect(leadingIconTheme.data, iconTheme);
final IconTheme actionsIconThemeWidget = _getAppBarActionsIconTheme(tester);
expect(actionsIconThemeWidget.data.color, appbarThemeData.actionsIconTheme!.color);
expect(actionsIconThemeWidget.data.size, appbarThemeData.actionsIconTheme!.size);
final NavigationToolbar navToolbar = tester.widget(find.byType(NavigationToolbar));
expect(navToolbar.centerMiddle, centerTitle);
expect(navToolbar.middleSpacing, titleSpacing);
final BoxConstraints leadingConstraints = (navToolbar.leading! as ConstrainedBox).constraints;
expect(leadingConstraints.maxWidth, leadingWidth);
expect(leadingConstraints.minWidth, leadingWidth);
expect(tester.getSize(find.byType(AppBar)).height, toolbarHeight);
final DefaultTextStyle text = _getAppBarText(tester);
expect(text.style, toolbarTextStyle);
final RichText titleText = tester.widget<RichText>(
find.descendant(of: find.text('Title'), matching: find.byType(RichText)),
);
expect(titleText.text.style, titleTextStyle);
expect(SystemChrome.latestStyle, systemOverlayStyle);
final Padding actionsPaddingWidget = tester.widget<Padding>(
find.descendant(of: find.byType(NavigationToolbar), matching: find.byType(Padding).last),
);
expect(actionsPaddingWidget.padding, actionsPadding);
final Size appBarSize = tester.getSize(find.byType(AppBar));
expect(appBarSize.height, toolbarHeight);
});
testWidgets('Local AppBarTheme can override global AppBarTheme', (WidgetTester tester) async {
const Color backgroundColor = Colors.blueAccent;
const Color foregroundColor = Colors.white;
const double elevation = 1.0;
const double scrolledUnderElevation = 2.0;
const Color shadowColor = Colors.black87;
const Color surfaceTintColor = Colors.transparent;
const ShapeBorder shape = RoundedRectangleBorder();
const IconThemeData iconTheme = IconThemeData(color: Colors.red);
const IconThemeData actionsIconTheme = IconThemeData(color: Color(0xFF6750A4));
const bool centerTitle = true;
const double titleSpacing = 20.0;
const double leadingWidth = 80.0;
const double toolbarHeight = 100.0;
const TextStyle toolbarTextStyle = TextStyle(color: Colors.yellow);
const TextStyle titleTextStyle = TextStyle(color: Colors.orange);
const SystemUiOverlayStyle systemOverlayStyle = SystemUiOverlayStyle.dark;
const EdgeInsetsGeometry actionsPadding = EdgeInsets.all(8);
const AppBarThemeData appbarThemeData = AppBarThemeData(
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
elevation: elevation,
scrolledUnderElevation: scrolledUnderElevation,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
shape: shape,
iconTheme: iconTheme,
actionsIconTheme: actionsIconTheme,
centerTitle: centerTitle,
titleSpacing: titleSpacing,
leadingWidth: leadingWidth,
toolbarHeight: toolbarHeight,
toolbarTextStyle: toolbarTextStyle,
titleTextStyle: titleTextStyle,
systemOverlayStyle: systemOverlayStyle,
actionsPadding: actionsPadding,
);
const AppBarThemeData globalAppbarThemeData = AppBarThemeData(
backgroundColor: Colors.red,
foregroundColor: Colors.green,
elevation: 0.0,
scrolledUnderElevation: 0.0,
shadowColor: Colors.blue,
surfaceTintColor: Colors.yellow,
shape: RoundedRectangleBorder(),
iconTheme: IconThemeData(color: Colors.black),
actionsIconTheme: IconThemeData(color: Colors.purple),
centerTitle: false,
titleSpacing: 10.0,
leadingWidth: 50.0,
toolbarHeight: 50.0,
toolbarTextStyle: TextStyle(color: Colors.white),
titleTextStyle: TextStyle(color: Colors.black),
systemOverlayStyle: SystemUiOverlayStyle.light,
actionsPadding: EdgeInsets.zero,
);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(appBarTheme: globalAppbarThemeData),
home: AppBarTheme(
data: appbarThemeData,
child: Scaffold(
appBar: AppBar(
title: const Text('Title'),
leading: IconButton(icon: const Icon(Icons.menu), onPressed: () {}),
actions: <Widget>[IconButton(icon: const Icon(Icons.add), onPressed: () {})],
),
),
),
),
);
final Material material = _getAppBarMaterial(tester);
expect(material.color, backgroundColor);
expect(material.elevation, elevation);
expect(material.shadowColor, shadowColor);
expect(material.surfaceTintColor, surfaceTintColor);
expect(material.shape, shape);
final IconTheme leadingIconTheme = _getAppBarIconTheme(tester);
expect(leadingIconTheme.data, iconTheme);
final IconTheme actionsIconThemeWidget = _getAppBarActionsIconTheme(tester);
expect(actionsIconThemeWidget.data.color, appbarThemeData.actionsIconTheme!.color);
expect(actionsIconThemeWidget.data.size, appbarThemeData.actionsIconTheme!.size);
final NavigationToolbar navToolbar = tester.widget(find.byType(NavigationToolbar));
expect(navToolbar.centerMiddle, centerTitle);
expect(navToolbar.middleSpacing, titleSpacing);
final BoxConstraints leadingConstraints = (navToolbar.leading! as ConstrainedBox).constraints;
expect(leadingConstraints.maxWidth, leadingWidth);
expect(leadingConstraints.minWidth, leadingWidth);
expect(tester.getSize(find.byType(AppBar)).height, toolbarHeight);
final DefaultTextStyle text = _getAppBarText(tester);
expect(text.style, toolbarTextStyle);
final RichText titleText = tester.widget<RichText>(
find.descendant(of: find.text('Title'), matching: find.byType(RichText)),
);
expect(titleText.text.style, titleTextStyle);
expect(SystemChrome.latestStyle, systemOverlayStyle);
final Padding actionsPaddingWidget = tester.widget<Padding>(
find.descendant(of: find.byType(NavigationToolbar), matching: find.byType(Padding).last),
);
expect(actionsPaddingWidget.padding, actionsPadding);
final Size appBarSize = tester.getSize(find.byType(AppBar));
expect(appBarSize.height, toolbarHeight);
});
// This is a regression test for https://github.com/flutter/flutter/issues/130485.
testWidgets(
'Material3 - AppBarTheme.iconTheme correctly applies custom white color in dark mode',
'Material3 - AppBarThemeData.iconTheme correctly applies custom white color in dark mode',
(WidgetTester tester) async {
final ThemeData themeData = ThemeData(
brightness: Brightness.dark,
appBarTheme: const AppBarTheme(iconTheme: IconThemeData(color: Colors.white)),
appBarTheme: const AppBarThemeData(iconTheme: IconThemeData(color: Colors.white)),
);
await tester.pumpWidget(
MaterialApp(
@ -1228,7 +1466,7 @@ void main() {
);
}
AppBarTheme _appBarTheme() {
AppBarThemeData _appBarTheme() {
const SystemUiOverlayStyle systemOverlayStyle = SystemUiOverlayStyle.dark;
const Color backgroundColor = Colors.lightBlue;
const double elevation = 6.0;
@ -1236,7 +1474,7 @@ AppBarTheme _appBarTheme() {
const Color surfaceTintColor = Colors.green;
const IconThemeData iconThemeData = IconThemeData(color: Colors.black);
const IconThemeData actionsIconThemeData = IconThemeData(color: Colors.pink);
return const AppBarTheme(
return const AppBarThemeData(
actionsIconTheme: actionsIconThemeData,
systemOverlayStyle: systemOverlayStyle,
backgroundColor: backgroundColor,

View File

@ -1329,7 +1329,7 @@ void main() {
typography: Typography.material2018(),
// COMPONENT THEMES
actionIconTheme: const ActionIconThemeData(),
appBarTheme: const AppBarTheme(backgroundColor: Colors.black),
appBarTheme: const AppBarThemeData(backgroundColor: Colors.black),
badgeTheme: const BadgeThemeData(backgroundColor: Colors.black),
bannerTheme: const MaterialBannerThemeData(backgroundColor: Colors.black),
bottomAppBarTheme: const BottomAppBarThemeData(color: Colors.black),
@ -1458,7 +1458,7 @@ void main() {
typography: Typography.material2018(platform: TargetPlatform.iOS),
// COMPONENT THEMES
actionIconTheme: const ActionIconThemeData(),
appBarTheme: const AppBarTheme(backgroundColor: Colors.white),
appBarTheme: const AppBarThemeData(backgroundColor: Colors.white),
badgeTheme: const BadgeThemeData(backgroundColor: Colors.black),
bannerTheme: const MaterialBannerThemeData(backgroundColor: Colors.white),
bottomAppBarTheme: const BottomAppBarThemeData(color: Colors.white),