diff --git a/dev/tools/gen_defaults/lib/app_bar_template.dart b/dev/tools/gen_defaults/lib/app_bar_template.dart index 2c6e7f9517c..feab1a5af47 100644 --- a/dev/tools/gen_defaults/lib/app_bar_template.dart +++ b/dev/tools/gen_defaults/lib/app_bar_template.dart @@ -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')}, diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index 8c4e2d7bb81..da959562bbe 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -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 { 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? parentRoute = ModalRoute.of(context); @@ -1119,7 +1119,7 @@ class _AppBarState extends State { 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()!; @@ -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, diff --git a/packages/flutter/lib/src/material/app_bar_theme.dart b/packages/flutter/lib/src/material/app_bar_theme.dart index 07242f5a5fe..677f1ac92fd 100644 --- a/packages/flutter/lib/src/material/app_bar_theme.dart +++ b/packages/flutter/lib/src/material/app_bar_theme.dart @@ -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(); + 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('elevation', elevation, defaultValue: null)); + properties.add(DoubleProperty('elevation', elevation, defaultValue: null)); properties.add( - DiagnosticsProperty( - '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('actionsIconTheme', actionsIconTheme, defaultValue: null), ); properties.add(DiagnosticsProperty('centerTitle', centerTitle, defaultValue: null)); - properties.add(DiagnosticsProperty('titleSpacing', titleSpacing, defaultValue: null)); - properties.add(DiagnosticsProperty('leadingWidth', leadingWidth, defaultValue: null)); - properties.add(DiagnosticsProperty('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('toolbarTextStyle', toolbarTextStyle, defaultValue: null), ); @@ -323,7 +589,22 @@ class AppBarTheme with Diagnosticable { DiagnosticsProperty('titleTextStyle', titleTextStyle, defaultValue: null), ); properties.add( - DiagnosticsProperty('actionsPadding', actionsPadding, defaultValue: null), + DiagnosticsProperty( + 'systemOverlayStyle', + systemOverlayStyle, + defaultValue: null, + description: + systemOverlayStyle == null + ? null + : 'SystemUiOverlayStyle(${[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( + 'actionsPadding', + actionsPadding, + defaultValue: null, + ), ); } } diff --git a/packages/flutter/lib/src/material/search.dart b/packages/flutter/lib/src/material/search.dart index 67588c6d881..f7b0bac77b2 100644 --- a/packages/flutter/lib/src/material/search.dart +++ b/packages/flutter/lib/src/material/search.dart @@ -262,7 +262,7 @@ abstract class SearchDelegate { 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 diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index bfba0d2200c..f945bdc681f 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -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( + DiagnosticsProperty( 'appBarTheme', appBarTheme, defaultValue: defaultData.appBarTheme, diff --git a/packages/flutter/test/material/app_bar_test.dart b/packages/flutter/test/material/app_bar_test.dart index 4d89faf51a3..dbb95088805 100644 --- a/packages/flutter/test/material/app_bar_test.dart +++ b/packages/flutter/test/material/app_bar_test.dart @@ -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'))), ), ); diff --git a/packages/flutter/test/material/app_bar_theme_test.dart b/packages/flutter/test/material/app_bar_theme_test.dart index 4dd630b7ec1..23e982f88fe 100644 --- a/packages/flutter/test/material/app_bar_theme_test.dart +++ b/packages/flutter/test/material/app_bar_theme_test.dart @@ -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: [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: [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: [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: [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 description = builder.properties @@ -1146,9 +1165,9 @@ void main() { expect(description, []); }); - 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 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: [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( + find.descendant(of: find.text('Title'), matching: find.byType(RichText)), + ); + expect(titleText.text.style, titleTextStyle); + + expect(SystemChrome.latestStyle, systemOverlayStyle); + + final Padding actionsPaddingWidget = tester.widget( + 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: [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( + find.descendant(of: find.text('Title'), matching: find.byType(RichText)), + ); + expect(titleText.text.style, titleTextStyle); + + expect(SystemChrome.latestStyle, systemOverlayStyle); + + final Padding actionsPaddingWidget = tester.widget( + 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, diff --git a/packages/flutter/test/material/theme_data_test.dart b/packages/flutter/test/material/theme_data_test.dart index 5f8a286c263..92506c010f1 100644 --- a/packages/flutter/test/material/theme_data_test.dart +++ b/packages/flutter/test/material/theme_data_test.dart @@ -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),