diff --git a/packages/flutter/lib/cupertino.dart b/packages/flutter/lib/cupertino.dart index 07a1fba88a..de84781f1e 100644 --- a/packages/flutter/lib/cupertino.dart +++ b/packages/flutter/lib/cupertino.dart @@ -17,6 +17,7 @@ export 'src/cupertino/button.dart'; export 'src/cupertino/colors.dart'; export 'src/cupertino/date_picker.dart'; export 'src/cupertino/dialog.dart'; +export 'src/cupertino/icon_theme_data.dart'; export 'src/cupertino/icons.dart'; export 'src/cupertino/interface_level.dart'; export 'src/cupertino/localizations.dart'; diff --git a/packages/flutter/lib/src/cupertino/colors.dart b/packages/flutter/lib/src/cupertino/colors.dart index d80de64c67..34b1bcaae8 100644 --- a/packages/flutter/lib/src/cupertino/colors.dart +++ b/packages/flutter/lib/src/cupertino/colors.dart @@ -13,8 +13,7 @@ import 'theme.dart'; // Examples can assume: // Widget child; -// Color lightModeColor; -// Color darkModeColor; +// BuildContext context; /// A palette of [Color] constants that describe colors commonly used when /// matching the iOS platform aesthetics. @@ -539,45 +538,105 @@ class CupertinoColors { /// A [Color] subclass that represents a family of colors, and the currect effective /// color in the color family. /// -/// When used as a regular color, `CupertinoDynamicColor` is equivalent to the +/// When used as a regular color, [CupertinoDynamicColor] is equivalent to the /// effective color (i.e. [CupertinoDynamicColor.value] will come from the effective /// color), which is determined by the [BuildContext] it is last resolved against. /// If it has never been resolved, the light, normal contrast, base elevation variant -/// [CupertinoDynamicColor.color] will be the effective color. -// TODO(LongCatIsLooong): publicize once all Cupertino components have adopted this. -// {@tool sample} -// -// The following snippet will create a [CupertinoButton] whose background color -// is _lightModeColor_ in light mode but _darkModeColor_ in dark mode. -// -// -// ```dart -// CupertinoButton( -// child: child, -// color: CupertinoDynamicColor.withVibrancy( -// color: lightModeColor, -// darkColor: darkModeColor, -// ), -// onTap: () => null, -// ) -// ``` -// {@end-tool} -// -// When a Cupertino component is provided with a `CupertinoDynamicColor`, either -// directly in its constructor, or from an [InheritedWidget] it depends on (for example, -// [DefaultTextStyle]), the component will automatically resolve the color by calling -// [CupertinoDynamicColor.resolve], using their own [BuildContext]. -// -// When used outside of a Cupertino component, such color resolution will not happen -// automatically. It's essential to call [CupertinoDynamicColor.resolve] with the -// correct [BuildContext] before using the color to paint, in order to get the -// desired effect. +/// [CupertinoDynamicColor.color] will be the default effective color. +/// +/// Sometimes manually resolving a [CupertinoDynamicColor] is not necessary, because +/// the Cupertino Library provides built-in support for it. +/// +/// ### Using a [CupertinoDynamicColor] in a Cupertino widget +/// +/// When a Cupertino widget is provided with a [CupertinoDynamicColor], either +/// directly in its constructor, or from an [InheritedWidget] it depends on (for example, +/// [DefaultTextStyle]), the widget will automatically resolve the color using +/// [CupertinoDynamicColor.resolve] against its own [BuildContext], on a best-effort +/// basis. +/// +/// {@tool sample} +/// By default a [CupertinoButton] has no background color. The following sample +/// code shows how to build a [CupertinoButton] that appears white in light mode, +/// and changes automatically to black in dark mode. +/// +/// ```dart +/// CupertinoButton( +/// child: child, +/// // CupertinoDynamicColor works out of box in a CupertinoButton. +/// color: CupertinoDynamicColor.withBrightness( +/// color: CupertinoColors.white, +/// darkColor: CupertinoColors.black, +/// ), +/// onPressed: () { }, +/// ) +/// ``` +/// {@end-tool} +/// +/// ### Using a [CupertinoDynamicColor] from a [CupertinoTheme] +/// +/// When referring to a [CupertinoTheme] color, generally the color will already +/// have adapted to the ambient [BuildContext], because [CupertinoTheme.of] +/// implicitly resolves all the colors used in the retrieved [CupertinoThemeData], +/// before returning it. +/// +/// {@tool sample} +/// The following code sample creates a [Container] with the `primaryColor` of the +/// current theme. If `primaryColor` is a [CupertinoDynamicColor], the container +/// will be adaptive, thanks to [CupertinoTheme.of]: it will switch to `primaryColor`'s +/// dark variant once dark mode is turned on, and turns to primaryColor`'s high +/// contrast variant when [MediaQueryData.highContrast] is requested in the ambient +/// [MediaQuery], etc. +/// +/// ```dart +/// Container( +/// // Container is not a Cupertino widget, but CupertinoTheme.of implicitly +/// // resolves colors used in the retrieved CupertinoThemeData. +/// color: CupertinoTheme.of(context).primaryColor, +/// ) +/// ``` +/// {@end-tool} +/// +/// ### Manually Resolving a [CupertinoDynamicColor] +/// +/// When used to configure a non-Cupertino widget, or wrapped in an object opaque +/// to the receiving Cupertino component, a [CupertinoDynamicColor] may need to be +/// manually resolved using [CupertinoDynamicColor.resolve], before it can used +/// to paint. For example, to use a custom [Border] in a [CupertinoNavigationBar], +/// the colors used in the [Border] have to be resolved manually before being passed +/// to [CupertinoNavigationBar]'s constructor. +/// +/// {@tool sample} +/// +/// The following code samples demostrate two cases where you have to manually +/// resolve a [CupertinoDynamicColor]. +/// +/// ```dart +/// CupertinoNavigationBar( +/// // CupertinoNavigationBar does not know how to resolve colors used in +/// // a Border class. +/// border: Border( +/// bottom: BorderSide( +/// color: CupertinoDynamicColor.resolve(CupertinoColors.systemBlue, context), +/// ), +/// ), +/// ) +/// ``` +/// +/// ```dart +/// Container( +/// // Container is not a Cupertino widget. +/// color: CupertinoDynamicColor.resolve(CupertinoColors.systemBlue, context), +/// ) +/// ``` +/// {@end-tool} /// /// See also: /// /// * [CupertinoUserInterfaceLevel], an [InheritedWidget] that may affect color -/// resolution of a `CupertinoDynamicColor`. -/// * https://developer.apple.com/documentation/uikit/uicolor/3238042-resolvedcolor. +/// resolution of a [CupertinoDynamicColor]. +/// * [CupertinoTheme.of], a static method that retrieves the ambient [CupertinoThemeData], +/// and then resolves [CupertinoDynamicColor]s used in the retrieved data. @immutable class CupertinoDynamicColor extends Color { /// Creates an adaptive [Color] that changes its effective color based on the @@ -678,7 +737,7 @@ class CupertinoDynamicColor extends Color { /// The color to use when the [BuildContext] implies a combination of light mode, /// normal contrast, and base interface elevation. /// - /// In other words, this color will be the effective color of the `CupertinoDynamicColor` + /// In other words, this color will be the effective color of the [CupertinoDynamicColor] /// after it is resolved against a [BuildContext] that: /// - has a [CupertinoTheme] whose [brightness] is [PlatformBrightness.light], /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [PlatformBrightness.light]. @@ -689,7 +748,7 @@ class CupertinoDynamicColor extends Color { /// The color to use when the [BuildContext] implies a combination of dark mode, /// normal contrast, and base interface elevation. /// - /// In other words, this color will be the effective color of the `CupertinoDynamicColor` + /// In other words, this color will be the effective color of the [CupertinoDynamicColor] /// after it is resolved against a [BuildContext] that: /// - has a [CupertinoTheme] whose [brightness] is [PlatformBrightness.dark], /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [PlatformBrightness.dark]. @@ -700,7 +759,7 @@ class CupertinoDynamicColor extends Color { /// The color to use when the [BuildContext] implies a combination of light mode, /// high contrast, and base interface elevation. /// - /// In other words, this color will be the effective color of the `CupertinoDynamicColor` + /// In other words, this color will be the effective color of the [CupertinoDynamicColor] /// after it is resolved against a [BuildContext] that: /// - has a [CupertinoTheme] whose [brightness] is [PlatformBrightness.light], /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [PlatformBrightness.light]. @@ -711,7 +770,7 @@ class CupertinoDynamicColor extends Color { /// The color to use when the [BuildContext] implies a combination of dark mode, /// high contrast, and base interface elevation. /// - /// In other words, this color will be the effective color of the `CupertinoDynamicColor` + /// In other words, this color will be the effective color of the [CupertinoDynamicColor] /// after it is resolved against a [BuildContext] that: /// - has a [CupertinoTheme] whose [brightness] is [PlatformBrightness.dark], /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [PlatformBrightness.dark]. @@ -722,7 +781,7 @@ class CupertinoDynamicColor extends Color { /// The color to use when the [BuildContext] implies a combination of light mode, /// normal contrast, and elevated interface elevation. /// - /// In other words, this color will be the effective color of the `CupertinoDynamicColor` + /// In other words, this color will be the effective color of the [CupertinoDynamicColor] /// after it is resolved against a [BuildContext] that: /// - has a [CupertinoTheme] whose [brightness] is [PlatformBrightness.light], /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [PlatformBrightness.light]. @@ -733,7 +792,7 @@ class CupertinoDynamicColor extends Color { /// The color to use when the [BuildContext] implies a combination of dark mode, /// normal contrast, and elevated interface elevation. /// - /// In other words, this color will be the effective color of the `CupertinoDynamicColor` + /// In other words, this color will be the effective color of the [CupertinoDynamicColor] /// after it is resolved against a [BuildContext] that: /// - has a [CupertinoTheme] whose [brightness] is [PlatformBrightness.dark], /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [PlatformBrightness.dark]. @@ -744,7 +803,7 @@ class CupertinoDynamicColor extends Color { /// The color to use when the [BuildContext] implies a combination of light mode, /// high contrast, and elevated interface elevation. /// - /// In other words, this color will be the effective color of the `CupertinoDynamicColor` + /// In other words, this color will be the effective color of the [CupertinoDynamicColor] /// after it is resolved against a [BuildContext] that: /// - has a [CupertinoTheme] whose [brightness] is [PlatformBrightness.light], /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [PlatformBrightness.light]. @@ -755,7 +814,7 @@ class CupertinoDynamicColor extends Color { /// The color to use when the [BuildContext] implies a combination of dark mode, /// high contrast, and elevated interface elevation. /// - /// In other words, this color will be the effective color of the `CupertinoDynamicColor` + /// In other words, this color will be the effective color of the [CupertinoDynamicColor] /// after it is resolved against a [BuildContext] that: /// - has a [CupertinoTheme] whose [brightness] is [PlatformBrightness.dark], /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [PlatformBrightness.dark]. @@ -802,10 +861,10 @@ class CupertinoDynamicColor extends Color { || darkHighContrastColor != darkHighContrastElevatedColor; } - /// Resolves this `CupertinoDynamicColor` using the provided [BuildContext]. + /// Resolves this [CupertinoDynamicColor] using the provided [BuildContext]. /// - /// Calling this method will create a new `CupertinoDynamicColor` that is almost - /// identical to this `CupertinoDynamicColor`, except the effective color is + /// Calling this method will create a new [CupertinoDynamicColor] that is almost + /// identical to this [CupertinoDynamicColor], except the effective color is /// changed to adapt to the given [BuildContext]. /// /// For example, if the given [BuildContext] indicates the widgets in the subtree @@ -814,9 +873,9 @@ class CupertinoDynamicColor extends Color { /// with a high accessibility contrast (the surrounding [MediaQuery]'s [MediaQueryData.highContrast] /// is `true`), and an elevated interface elevation (the surrounding [CupertinoUserInterfaceLevel]'s /// `data` is [CupertinoUserInterfaceLevelData.elevated]), the resolved - /// `CupertinoDynamicColor` will be the same as this [CupertinoDynamicColor], + /// [CupertinoDynamicColor] will be the same as this [CupertinoDynamicColor], /// except its effective color will be the `darkHighContrastElevatedColor` variant - /// from the orignal `CupertinoDynamicColor`. + /// from the orignal [CupertinoDynamicColor]. /// /// Calling this function may create dependencies on the closest instance of some /// [InheritedWidget]s that enclose the given [BuildContext]. E.g., if [darkColor] diff --git a/packages/flutter/lib/src/cupertino/icon_theme_data.dart b/packages/flutter/lib/src/cupertino/icon_theme_data.dart new file mode 100644 index 0000000000..4e437b4b5f --- /dev/null +++ b/packages/flutter/lib/src/cupertino/icon_theme_data.dart @@ -0,0 +1,27 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; +import 'colors.dart'; + +/// An [IconThemeData] subclass that automatically resolves its [color] when retrieved +/// using [IconTheme.of]. +class CupertinoIconThemeData extends IconThemeData { + /// Creates a [CupertinoIconThemeData]. + /// + /// The opacity applies to both explicit and default icon colors. The value + /// is clamped between 0.0 and 1.0. + const CupertinoIconThemeData({ + Color color, + double opacity, + double size + }) : super(color: color, opacity: opacity, size: size); + + /// Called by [IconThemeData.of] to resolve [color] against the given [BuildContext]. + @override + IconThemeData resolve(BuildContext context) { + final Color resolvedColor = CupertinoDynamicColor.resolve(color, context); + return resolvedColor == color ? this : copyWith(color: resolvedColor); + } +} diff --git a/packages/flutter/lib/src/cupertino/text_theme.dart b/packages/flutter/lib/src/cupertino/text_theme.dart index 8e87277bcf..ca47790bb3 100644 --- a/packages/flutter/lib/src/cupertino/text_theme.dart +++ b/packages/flutter/lib/src/cupertino/text_theme.dart @@ -8,26 +8,24 @@ import 'package:flutter/widgets.dart'; import 'colors.dart'; +// Please update _DefaultCupertinoTextThemeData and _DefaultCupertinoTextThemeData +// accordingly after changing the default color here, as their implementation +// depends on the default value of the color field. +// // Values derived from https://developer.apple.com/design/resources/. -const TextStyle _kDefaultLightTextStyle = TextStyle( +const TextStyle _kDefaultTextStyle = TextStyle( inherit: false, fontFamily: '.SF Pro Text', fontSize: 17.0, letterSpacing: -0.41, - color: CupertinoColors.black, - decoration: TextDecoration.none, -); - -// Values derived from https://developer.apple.com/design/resources/. -const TextStyle _kDefaultDarkTextStyle = TextStyle( - inherit: false, - fontFamily: '.SF Pro Text', - fontSize: 17.0, - letterSpacing: -0.41, - color: CupertinoColors.white, + color: CupertinoColors.label, decoration: TextDecoration.none, ); +// Please update _DefaultCupertinoTextThemeData and _DefaultCupertinoTextThemeData +// accordingly after changing the default color here, as their implementation +// depends on the default value of the color field. +// // Values derived from https://developer.apple.com/design/resources/. const TextStyle _kDefaultActionTextStyle = TextStyle( inherit: false, @@ -38,6 +36,10 @@ const TextStyle _kDefaultActionTextStyle = TextStyle( decoration: TextDecoration.none, ); +// Please update _DefaultCupertinoTextThemeData and _DefaultCupertinoTextThemeData +// accordingly after changing the default color here, as their implementation +// depends on the default value of the color field. +// // Values derived from https://developer.apple.com/design/resources/. const TextStyle _kDefaultTabLabelTextStyle = TextStyle( inherit: false, @@ -47,97 +49,83 @@ const TextStyle _kDefaultTabLabelTextStyle = TextStyle( color: CupertinoColors.inactiveGray, ); -const TextStyle _kDefaultMiddleTitleLightTextStyle = TextStyle( +// Please update _DefaultCupertinoTextThemeData and _DefaultCupertinoTextThemeData +// accordingly after changing the default color here, as their implementation +// depends on the default value of the color field. +const TextStyle _kDefaultMiddleTitleTextStyle = TextStyle( inherit: false, fontFamily: '.SF Pro Text', fontSize: 17.0, fontWeight: FontWeight.w600, letterSpacing: -0.41, - color: CupertinoColors.black, + color: CupertinoColors.label, ); -const TextStyle _kDefaultMiddleTitleDarkTextStyle = TextStyle( - inherit: false, - fontFamily: '.SF Pro Text', - fontSize: 17.0, - fontWeight: FontWeight.w600, - letterSpacing: -0.41, - color: CupertinoColors.white, -); - -const TextStyle _kDefaultLargeTitleLightTextStyle = TextStyle( +// Please update _DefaultCupertinoTextThemeData and _DefaultCupertinoTextThemeData +// accordingly after changing the default color here, as their implementation +// depends on the default value of the color field. +const TextStyle _kDefaultLargeTitleTextStyle = TextStyle( inherit: false, fontFamily: '.SF Pro Display', fontSize: 34.0, fontWeight: FontWeight.w700, letterSpacing: 0.41, - color: CupertinoColors.black, + color: CupertinoColors.label, ); -const TextStyle _kDefaultLargeTitleDarkTextStyle = TextStyle( - inherit: false, - fontFamily: '.SF Pro Display', - fontSize: 34.0, - fontWeight: FontWeight.w700, - letterSpacing: 0.41, - color: CupertinoColors.white, -); - -// Eyeballed value since it's not documented in https://developer.apple.com/design/resources/. -const TextStyle _kDefaultPickerLightTextStyle = TextStyle( - inherit: false, - fontFamily: '.SF Pro Display', - fontSize: 25.0, - fontWeight: FontWeight.w400, - letterSpacing: -0.41, - color: CupertinoColors.black, -); - -// Eyeballed value since it's not documented in https://developer.apple.com/design/resources/. -const TextStyle _kDefaultPickerDarkTextStyle = TextStyle( - inherit: false, - fontFamily: '.SF Pro Display', - fontSize: 25.0, - fontWeight: FontWeight.w400, - letterSpacing: -0.41, - color: CupertinoColors.white, -); - -// Eyeballed value since it's not documented in https://developer.apple.com/design/resources/. +// Please update _DefaultCupertinoTextThemeData and _DefaultCupertinoTextThemeData +// accordingly after changing the default color here, as their implementation +// depends on the default value of the color field. +// // Inspected on iOS 13 simulator with "Debug View Hierarchy". -const TextStyle _kDefaultDateTimePickerLightTextStyle = TextStyle( +// Value extracted from off-center labels. Centered labels have a font size of 25pt. +const TextStyle _kDefaultPickerTextStyle = TextStyle( + inherit: false, + fontFamily: '.SF Pro Display', + fontSize: 21.0, + fontWeight: FontWeight.w400, + letterSpacing: -0.41, + color: CupertinoColors.label, +); + +// Please update _DefaultCupertinoTextThemeData and _DefaultCupertinoTextThemeData +// accordingly after changing the default color here, as their implementation +// depends on the default value of the color field. +// +// Inspected on iOS 13 simulator with "Debug View Hierarchy". +// Value extracted from off-center labels. Centered labels have a font size of 25pt. +const TextStyle _kDefaultDateTimePickerTextStyle = TextStyle( inherit: false, fontFamily: '.SF Pro Display', fontSize: 21, fontWeight: FontWeight.normal, - color: CupertinoColors.black, + color: CupertinoColors.label, ); -// Eyeballed value since it's not documented in https://developer.apple.com/design/resources/. -// Inspected on iOS 13 simulator with "Debug View Hierarchy". -const TextStyle _kDefaultDateTimePickerDarkTextStyle = TextStyle( - inherit: false, - fontFamily: '.SF Pro Display', - fontSize: 21, - fontWeight: FontWeight.normal, - color: CupertinoColors.white, -); +TextStyle _resolveTextStyle(TextStyle style, BuildContext context, bool nullOk) { + // This does not resolve the shadow color, foreground, background, etc. + return style?.copyWith( + color: CupertinoDynamicColor.resolve(style?.color, context, nullOk: nullOk), + backgroundColor: CupertinoDynamicColor.resolve(style?.backgroundColor, context, nullOk: nullOk), + decorationColor: CupertinoDynamicColor.resolve(style?.decorationColor, context, nullOk: nullOk), + ); +} /// Cupertino typography theme in a [CupertinoThemeData]. @immutable class CupertinoTextThemeData extends Diagnosticable { /// Create a [CupertinoTextThemeData]. /// - /// The [primaryColor] and [isLight] parameters are used to derive TextStyle - /// defaults of other attributes such as [textStyle] and [actionTextStyle] - /// etc. The default value of [primaryColor] is [CupertinoColors.activeBlue] - /// and the default value of [isLight] is true. + /// The [primaryColor] is used to derive TextStyle defaults of other attributes + /// such as [navActionTextStyle] and [actionTextStyle], it must not be null when + /// either [navActionTextStyle] or [actionTextStyle] is null. Defaults to + /// [CupertinoColors.systemBlue]. /// /// Other [TextStyle] parameters default to default iOS text styles when /// unspecified. const CupertinoTextThemeData({ - Color primaryColor, - Brightness brightness, + Color primaryColor = CupertinoColors.systemBlue, + @deprecated Brightness brightness, //ignore: avoid_unused_constructor_parameters , the parameter is deprecated. TextStyle textStyle, TextStyle actionTextStyle, TextStyle tabLabelTextStyle, @@ -146,96 +134,90 @@ class CupertinoTextThemeData extends Diagnosticable { TextStyle navActionTextStyle, TextStyle pickerTextStyle, TextStyle dateTimePickerTextStyle, - }) : _primaryColor = primaryColor ?? CupertinoColors.activeBlue, - _brightness = brightness, - _textStyle = textStyle, - _actionTextStyle = actionTextStyle, - _tabLabelTextStyle = tabLabelTextStyle, - _navTitleTextStyle = navTitleTextStyle, - _navLargeTitleTextStyle = navLargeTitleTextStyle, - _navActionTextStyle = navActionTextStyle, - _pickerTextStyle = pickerTextStyle, - _dateTimePickerTextStyle = dateTimePickerTextStyle; + }) : this._raw( + const _DefaultCupertinoTextThemeData(), + primaryColor, + textStyle, + actionTextStyle, + tabLabelTextStyle, + navTitleTextStyle, + navLargeTitleTextStyle, + navActionTextStyle, + pickerTextStyle, + dateTimePickerTextStyle, + ); + const CupertinoTextThemeData._raw( + this._defaults, + this._primaryColor, + this._textStyle, + this._actionTextStyle, + this._tabLabelTextStyle, + this._navTitleTextStyle, + this._navLargeTitleTextStyle, + this._navActionTextStyle, + this._pickerTextStyle, + this._dateTimePickerTextStyle, + ) : assert((_navActionTextStyle != null && _actionTextStyle != null) || _primaryColor != null); + + final _DefaultCupertinoTextThemeData _defaults; final Color _primaryColor; - final Brightness _brightness; - bool get _isLight => _brightness != Brightness.dark; final TextStyle _textStyle; - /// Typography of general text content for Cupertino widgets. - TextStyle get textStyle => _textStyle ?? (_isLight ? _kDefaultLightTextStyle : _kDefaultDarkTextStyle); + /// The [TextStyle] of general text content for Cupertino widgets. + TextStyle get textStyle => _textStyle ?? _defaults.textStyle; final TextStyle _actionTextStyle; - /// Typography of interactive text content such as text in a button without background. + /// The [TextStyle] of interactive text content such as text in a button without background. TextStyle get actionTextStyle { - return _actionTextStyle ?? _kDefaultActionTextStyle.copyWith( - color: _primaryColor, - ); + return _actionTextStyle ?? _defaults.actionTextStyle(primaryColor: _primaryColor); } final TextStyle _tabLabelTextStyle; - /// Typography of unselected tabs. - TextStyle get tabLabelTextStyle => _tabLabelTextStyle ?? _kDefaultTabLabelTextStyle; + /// The [TextStyle] of unselected tabs. + TextStyle get tabLabelTextStyle => _tabLabelTextStyle ?? _defaults.tabLabelTextStyle; final TextStyle _navTitleTextStyle; - /// Typography of titles in standard navigation bars. - TextStyle get navTitleTextStyle { - return _navTitleTextStyle ?? - (_isLight ? _kDefaultMiddleTitleLightTextStyle : _kDefaultMiddleTitleDarkTextStyle); - } + /// The [TextStyle] of titles in standard navigation bars. + TextStyle get navTitleTextStyle => _navTitleTextStyle ?? _defaults.navTitleTextStyle; final TextStyle _navLargeTitleTextStyle; - /// Typography of large titles in sliver navigation bars. - TextStyle get navLargeTitleTextStyle { - return _navLargeTitleTextStyle ?? - (_isLight ? _kDefaultLargeTitleLightTextStyle : _kDefaultLargeTitleDarkTextStyle); - } + /// The [TextStyle] of large titles in sliver navigation bars. + TextStyle get navLargeTitleTextStyle => _navLargeTitleTextStyle ?? _defaults.navLargeTitleTextStyle; final TextStyle _navActionTextStyle; - /// Typography of interactive text content in navigation bars. + /// The [TextStyle] of interactive text content in navigation bars. TextStyle get navActionTextStyle { - return _navActionTextStyle ?? _kDefaultActionTextStyle.copyWith( - color: _primaryColor, - ); + return _navActionTextStyle ?? _defaults.navActionTextStyle(primaryColor: _primaryColor); } final TextStyle _pickerTextStyle; - /// Typography of pickers. - TextStyle get pickerTextStyle { - return _pickerTextStyle ?? - (_isLight ? _kDefaultPickerLightTextStyle : _kDefaultPickerDarkTextStyle); - } + /// The [TextStyle] of pickers. + TextStyle get pickerTextStyle => _pickerTextStyle ?? _defaults.pickerTextStyle; final TextStyle _dateTimePickerTextStyle; - /// Typography of date time pickers. - TextStyle get dateTimePickerTextStyle { - return _dateTimePickerTextStyle ?? - (_isLight ? _kDefaultDateTimePickerLightTextStyle : _kDefaultDateTimePickerDarkTextStyle); - } + /// The [TextStyle] of date time pickers. + TextStyle get dateTimePickerTextStyle => _dateTimePickerTextStyle ?? _defaults.dateTimePickerTextStyle; /// Returns a copy of the current [CupertinoTextThemeData] with all the colors /// resolved against the given [BuildContext]. + /// + /// Throws an exception if any of the [InheritedWidget]s required to resolve + /// this [CupertinoTextThemeData] is not found in [context], unless [nullOk] is + /// set to true, in which case [CupertinoDynamicColor]s that fail to resolve will + /// be used as-is. CupertinoTextThemeData resolveFrom(BuildContext context, { bool nullOk = false }) { - Color convertColor(Color color) => CupertinoDynamicColor.resolve(color, context, nullOk: nullOk); - - TextStyle resolveTextStyle(TextStyle textStyle) { - return textStyle?.copyWith( - color: convertColor(textStyle.color), - backgroundColor: convertColor(textStyle.backgroundColor), - decorationColor: convertColor(textStyle.decorationColor), - ); - } - - return copyWith( - primaryColor: convertColor(_primaryColor), - textStyle: resolveTextStyle(_textStyle), - actionTextStyle: resolveTextStyle(_actionTextStyle), - tabLabelTextStyle: resolveTextStyle(_tabLabelTextStyle), - navTitleTextStyle : resolveTextStyle(_navTitleTextStyle), - navLargeTitleTextStyle: resolveTextStyle(_navLargeTitleTextStyle), - navActionTextStyle: resolveTextStyle(_navActionTextStyle), - pickerTextStyle: resolveTextStyle(_pickerTextStyle), - dateTimePickerTextStyle: resolveTextStyle(_dateTimePickerTextStyle), + return CupertinoTextThemeData._raw( + _defaults?.resolveFrom(context, nullOk), + CupertinoDynamicColor.resolve(_primaryColor, context, nullOk: nullOk), + _resolveTextStyle(_textStyle, context, nullOk), + _resolveTextStyle(_actionTextStyle, context, nullOk), + _resolveTextStyle(_tabLabelTextStyle, context, nullOk), + _resolveTextStyle(_navTitleTextStyle, context, nullOk), + _resolveTextStyle(_navLargeTitleTextStyle, context, nullOk), + _resolveTextStyle(_navActionTextStyle, context, nullOk), + _resolveTextStyle(_pickerTextStyle, context, nullOk), + _resolveTextStyle(_dateTimePickerTextStyle, context, nullOk), ); } @@ -243,7 +225,7 @@ class CupertinoTextThemeData extends Diagnosticable { /// specified overrides. CupertinoTextThemeData copyWith({ Color primaryColor, - Brightness brightness, + @deprecated Brightness brightness, TextStyle textStyle, TextStyle actionTextStyle, TextStyle tabLabelTextStyle, @@ -253,17 +235,53 @@ class CupertinoTextThemeData extends Diagnosticable { TextStyle pickerTextStyle, TextStyle dateTimePickerTextStyle, }) { - return CupertinoTextThemeData( - primaryColor: primaryColor ?? _primaryColor, - brightness: brightness ?? _brightness, - textStyle: textStyle ?? _textStyle, - actionTextStyle: actionTextStyle ?? _actionTextStyle, - tabLabelTextStyle: tabLabelTextStyle ?? _tabLabelTextStyle, - navTitleTextStyle: navTitleTextStyle ?? _navTitleTextStyle, - navLargeTitleTextStyle: navLargeTitleTextStyle ?? _navLargeTitleTextStyle, - navActionTextStyle: navActionTextStyle ?? _navActionTextStyle, - pickerTextStyle: pickerTextStyle ?? _pickerTextStyle, - dateTimePickerTextStyle: dateTimePickerTextStyle ?? _dateTimePickerTextStyle, + return CupertinoTextThemeData._raw( + _defaults, + primaryColor ?? _primaryColor, + textStyle ?? _textStyle, + actionTextStyle ?? _actionTextStyle, + tabLabelTextStyle ?? _tabLabelTextStyle, + navTitleTextStyle ?? _navTitleTextStyle, + navLargeTitleTextStyle ?? _navLargeTitleTextStyle, + navActionTextStyle ?? _navActionTextStyle, + pickerTextStyle ?? _pickerTextStyle, + dateTimePickerTextStyle ?? _dateTimePickerTextStyle, ); } } + +@immutable +class _DefaultCupertinoTextThemeData extends Diagnosticable { + const _DefaultCupertinoTextThemeData({ + this.labelColor = CupertinoColors.label, + this.inactiveGrayColor = CupertinoColors.inactiveGray, + }) : assert(labelColor != null), + assert(inactiveGrayColor != null); + + final Color labelColor; + final Color inactiveGrayColor; + + static TextStyle _applyLabelColor(TextStyle original, Color color) { + return original?.color == color + ? original + : original?.copyWith(color: color); + } + + TextStyle get textStyle => _applyLabelColor(_kDefaultTextStyle, labelColor); + TextStyle get tabLabelTextStyle => _applyLabelColor(_kDefaultTabLabelTextStyle, inactiveGrayColor); + TextStyle get navTitleTextStyle => _applyLabelColor(_kDefaultMiddleTitleTextStyle, labelColor); + TextStyle get navLargeTitleTextStyle => _applyLabelColor(_kDefaultLargeTitleTextStyle, labelColor); + TextStyle get pickerTextStyle => _applyLabelColor(_kDefaultPickerTextStyle, labelColor); + TextStyle get dateTimePickerTextStyle => _applyLabelColor(_kDefaultDateTimePickerTextStyle, labelColor); + + TextStyle actionTextStyle({ Color primaryColor }) => _kDefaultActionTextStyle.copyWith(color: primaryColor); + TextStyle navActionTextStyle({ Color primaryColor }) => actionTextStyle(primaryColor: primaryColor); + + _DefaultCupertinoTextThemeData resolveFrom(BuildContext context, bool nullOk) { + final Color resolvedLabelColor = CupertinoDynamicColor.resolve(labelColor, context, nullOk: nullOk); + final Color resolvedInactiveGray = CupertinoDynamicColor.resolve(inactiveGrayColor, context, nullOk: nullOk); + return resolvedLabelColor == labelColor && resolvedInactiveGray == CupertinoColors.inactiveGray + ? this + : _DefaultCupertinoTextThemeData(labelColor: resolvedLabelColor, inactiveGrayColor: resolvedInactiveGray); + } +} diff --git a/packages/flutter/lib/src/cupertino/theme.dart b/packages/flutter/lib/src/cupertino/theme.dart index 695aa35760..b6f9442ca1 100644 --- a/packages/flutter/lib/src/cupertino/theme.dart +++ b/packages/flutter/lib/src/cupertino/theme.dart @@ -7,13 +7,24 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'colors.dart'; +import 'icon_theme_data.dart'; import 'text_theme.dart'; export 'package:flutter/services.dart' show Brightness; // Values derived from https://developer.apple.com/design/resources/. -const Color _kDefaultBarLightBackgroundColor = Color(0xCCF8F8F8); -const Color _kDefaultBarDarkBackgroundColor = Color(0xB7212121); +const _CupertinoThemeDefaults _kDefaultTheme = _CupertinoThemeDefaults( + null, + CupertinoColors.systemBlue, + CupertinoColors.systemBackground, + CupertinoDynamicColor.withBrightness( + color: Color(0xF0F9F9F9), + darkColor: Color(0xF01D1D1D), + // Values extracted from navigation bar. For toolbar or tabbar the dark color is 0xF0161616. + ), + CupertinoColors.systemBackground, + _CupertinoTextThemeDefaults(CupertinoColors.label, CupertinoColors.inactiveGray), +); /// Applies a visual styling theme to descendant Cupertino widgets. /// @@ -30,7 +41,8 @@ const Color _kDefaultBarDarkBackgroundColor = Color(0xB7212121); /// See also: /// /// * [CupertinoThemeData], specifies the theme's visual styling. -/// * [CupertinoApp], which will automatically add a [CupertinoTheme]. +/// * [CupertinoApp], which will automatically add a [CupertinoTheme] based on the +/// value of [CupertinoApp.theme]. /// * [Theme], a Material theme which will automatically add a [CupertinoTheme] /// with a [CupertinoThemeData] derived from the Material [ThemeData]. class CupertinoTheme extends StatelessWidget { @@ -48,20 +60,22 @@ class CupertinoTheme extends StatelessWidget { /// The [CupertinoThemeData] styling for this theme. final CupertinoThemeData data; - /// Retrieve the [CupertinoThemeData] from an ancestor [CupertinoTheme] widget. + /// Retrieves the [CupertinoThemeData] from the closest ancestor [CupertinoTheme] + /// widget, or a default [CupertinoThemeData] if no [CupertinoTheme] ancestor + /// exists. /// - /// Returns a default [CupertinoThemeData] if no [CupertinoTheme] widgets - /// exist in the ancestry tree. + /// Resolves all the colors defined in that [CupertinoThemeData] against the + /// given [BuildContext] on a best-effort basis. static CupertinoThemeData of(BuildContext context) { final _InheritedCupertinoTheme inheritedTheme = context.inheritFromWidgetOfExactType(_InheritedCupertinoTheme); return (inheritedTheme?.theme?.data ?? const CupertinoThemeData()).resolveFrom(context, nullOk: true); } - /// Retrieve the [Brightness] value from the closest ancestor [CupertinoTheme] + /// Retrieves the [Brightness] value from the closest ancestor [CupertinoTheme] /// widget. /// - /// If no ancestral [CupertinoTheme] widget with explicit brightness value could - /// be found, the method will resort to the closest ancestor [MediaQuery] widget. + /// If no [CupertinoTheme] ancestor with an explicit brightness value could be + /// found, this method will resort to the closest ancestor [MediaQuery] widget. /// /// Throws an exception if no such [CupertinoTheme] or [MediaQuery] widgets exist /// in the ancestry tree, unless [nullOk] is set to true. @@ -80,7 +94,7 @@ class CupertinoTheme extends StatelessWidget { return _InheritedCupertinoTheme( theme: this, child: IconTheme( - data: IconThemeData(color: data.primaryColor), + data: CupertinoIconThemeData(color: data.primaryColor), child: child, ), ); @@ -119,7 +133,7 @@ class _InheritedCupertinoTheme extends InheritedWidget { /// styling via a [CupertinoThemeData] subclass [MaterialBasedCupertinoThemeData]. @immutable class CupertinoThemeData extends Diagnosticable { - /// Create a [CupertinoTheme] styling specification. + /// Creates a [CupertinoTheme] styling specification. /// /// Unspecified parameters default to a reasonable iOS default style. const CupertinoThemeData({ @@ -144,89 +158,104 @@ class CupertinoThemeData extends Diagnosticable { /// Used by subclasses to get the superclass's defaulting behaviors. @protected const CupertinoThemeData.raw( + Brightness brightness, + Color primaryColor, + Color primaryContrastingColor, + CupertinoTextThemeData textTheme, + Color barBackgroundColor, + Color scaffoldBackgroundColor, + ) : this._rawWithDefaults( + brightness, + primaryColor, + primaryContrastingColor, + textTheme, + barBackgroundColor, + scaffoldBackgroundColor, + _kDefaultTheme, + ); + + const CupertinoThemeData._rawWithDefaults( this._brightness, this._primaryColor, this._primaryContrastingColor, this._textTheme, this._barBackgroundColor, this._scaffoldBackgroundColor, + this._defaults, ); - bool get _isLight => brightness == Brightness.light; + final _CupertinoThemeDefaults _defaults; /// The general brightness theme of the [CupertinoThemeData]. /// - /// Affects all other theme properties when unspecified. Defaults to - /// [Brightness.light]. + /// Overrides the ambient [MediaQueryData.platformBrightness] when specified. + /// Defaults to [Brightness.light]. /// /// If coming from a Material [Theme] and unspecified, [brightness] will be /// derived from the Material [ThemeData]'s `brightness`. + /// + /// See also: + /// + /// * [MaterialBasedCupertinoThemeData], a [CupertinoThemeData] that defers + /// [brightness] to its Material [Theme] parent if it's unspecified. Brightness get brightness => _brightness ?? Brightness.light; final Brightness _brightness; /// A color used on interactive elements of the theme. /// /// This color is generally used on text and icons in buttons and tappable - /// elements. Defaults to [CupertinoColors.activeBlue] or - /// [CupertinoColors.activeOrange] when [brightness] is light or dark. + /// elements. Defaults to [CupertinoColors.activeBlue]. /// /// If coming from a Material [Theme] and unspecified, [primaryColor] will be /// derived from the Material [ThemeData]'s `colorScheme.primary`. However, in /// iOS styling, the [primaryColor] is more sparsely used than in Material /// Design where the [primaryColor] can appear on non-interactive surfaces like /// the [AppBar] background, [TextField] borders etc. - Color get primaryColor { - return _primaryColor ?? - (_isLight ? CupertinoColors.activeBlue : CupertinoColors.activeOrange); - } + /// + /// See also: + /// + /// * [MaterialBasedCupertinoThemeData], a [CupertinoThemeData] that defers + /// [primaryColor] to its Material [Theme] parent if it's unspecified. + Color get primaryColor => _primaryColor ?? _defaults.primaryColor; final Color _primaryColor; - /// A color used for content that must contrast against a [primaryColor] background. + /// A color that must be easy to see when rendered on a [primaryColor] background. /// /// For example, this color is used for a [CupertinoButton]'s text and icons /// when the button's background is [primaryColor]. /// /// If coming from a Material [Theme] and unspecified, [primaryContrastingColor] /// will be derived from the Material [ThemeData]'s `colorScheme.onPrimary`. - Color get primaryContrastingColor { - return _primaryContrastingColor ?? - (_isLight ? CupertinoColors.white : CupertinoColors.black); - } + /// + /// See also: + /// + /// * [MaterialBasedCupertinoThemeData], a [CupertinoThemeData] that defers + /// [primaryContrastingColor] to its Material [Theme] parent if it's unspecified. + Color get primaryContrastingColor => _primaryContrastingColor ?? _defaults.primaryContrastingColor; final Color _primaryContrastingColor; /// Text styles used by Cupertino widgets. /// - /// Derived from [brightness] and [primaryColor] if unspecified, including - /// [brightness] and [primaryColor] of a Material [ThemeData] if coming - /// from a Material [Theme]. + /// Derived from [primaryColor] if unspecified. CupertinoTextThemeData get textTheme { - return _textTheme ?? CupertinoTextThemeData( - brightness: brightness, - primaryColor: primaryColor, - ); + return _textTheme ?? _defaults.textThemeDefaults.createDefaults(primaryColor: primaryColor); } final CupertinoTextThemeData _textTheme; /// Background color of the top nav bar and bottom tab bar. /// - /// Defaults to a light gray or a dark gray translucent color depending - /// on the [brightness]. - Color get barBackgroundColor { - return _barBackgroundColor ?? - (_isLight ? _kDefaultBarLightBackgroundColor : _kDefaultBarDarkBackgroundColor); - } + /// Defaults to a light gray in light mode, or a dark translucent gray color in + /// dark mode. + Color get barBackgroundColor => _barBackgroundColor ?? _defaults.barBackgroundColor; final Color _barBackgroundColor; /// Background color of the scaffold. /// - /// Defaults to white or black depending on the [brightness]. - Color get scaffoldBackgroundColor { - return _scaffoldBackgroundColor ?? - (_isLight ? CupertinoColors.white : CupertinoColors.black); - } + /// Defaults to [CupertinoColors.systemBackground]. + Color get scaffoldBackgroundColor => _scaffoldBackgroundColor ?? _defaults.scaffoldBackgroundColor; final Color _scaffoldBackgroundColor; - /// Return an instance of the [CupertinoThemeData] whose property getters + /// Returns an instance of the [CupertinoThemeData] whose property getters /// only return the construction time specifications with no derived values. /// /// Used in Material themes to let unspecified properties fallback to Material @@ -242,30 +271,33 @@ class CupertinoThemeData extends Diagnosticable { ); } - /// Return a new `CupertinoThemeData` whose colors are from this `CupertinoThemeData`, - /// but resolved aginst the given [BuildContext]. + /// Returns a new `CupertinoThemeData` with all its colors resolved aginst the + /// given [BuildContext]. /// - /// It will be called in [CupertinoTheme.of]. + /// Called by [CupertinoTheme.of] to resolve colors defined in the retrieved + /// [CupertinoThemeData]. @protected CupertinoThemeData resolveFrom(BuildContext context, { bool nullOk = false }) { Color convertColor(Color color) => CupertinoDynamicColor.resolve(color, context, nullOk: nullOk); - return copyWith( - primaryColor: convertColor(primaryColor), - primaryContrastingColor: convertColor(primaryContrastingColor), - textTheme: textTheme?.resolveFrom(context, nullOk: nullOk), - barBackgroundColor: convertColor(barBackgroundColor), - scaffoldBackgroundColor: convertColor(scaffoldBackgroundColor), + return CupertinoThemeData._rawWithDefaults( + _brightness, + convertColor(_primaryColor), + convertColor(_primaryContrastingColor), + textTheme?.resolveFrom(context, nullOk: nullOk), + convertColor(_barBackgroundColor), + convertColor(_scaffoldBackgroundColor), + _defaults.resolveFrom(context, nullOk: nullOk), ); } - /// Create a copy of [CupertinoThemeData] with specified attributes overridden. + /// Creates a copy of [CupertinoThemeData] with specified attributes overridden. /// /// Only the current instance's specified attributes are copied instead of - /// derived values. For instance, if the current [primaryColor] is implied - /// to be [CupertinoColors.activeOrange] due to the current [brightness], - /// copying with a different [brightness] will also change the copy's - /// implied [primaryColor]. + /// derived values. For instance, if the current [CupertinoThemeData.textTheme] + /// is implied from the current [primaryColor] because it was not specified, + /// copying with a different [primaryColor] will also change the copy's implied + /// [textTheme]. CupertinoThemeData copyWith({ Brightness brightness, Color primaryColor, @@ -274,13 +306,14 @@ class CupertinoThemeData extends Diagnosticable { Color barBackgroundColor, Color scaffoldBackgroundColor, }) { - return CupertinoThemeData( - brightness: brightness ?? _brightness, - primaryColor: primaryColor ?? _primaryColor, - primaryContrastingColor: primaryContrastingColor ?? _primaryContrastingColor, - textTheme: textTheme ?? _textTheme, - barBackgroundColor: barBackgroundColor ?? _barBackgroundColor, - scaffoldBackgroundColor: scaffoldBackgroundColor ?? _scaffoldBackgroundColor, + return CupertinoThemeData._rawWithDefaults( + brightness ?? _brightness, + primaryColor ?? _primaryColor, + primaryContrastingColor ?? _primaryContrastingColor, + textTheme ?? _textTheme, + barBackgroundColor ?? _barBackgroundColor, + scaffoldBackgroundColor ?? _scaffoldBackgroundColor, + _defaults, ); } @@ -305,13 +338,14 @@ class _NoDefaultCupertinoThemeData extends CupertinoThemeData { this.textTheme, this.barBackgroundColor, this.scaffoldBackgroundColor, - ) : super.raw( + ) : super._rawWithDefaults( brightness, primaryColor, primaryContrastingColor, textTheme, barBackgroundColor, scaffoldBackgroundColor, + null, ); @override @@ -360,3 +394,98 @@ class _NoDefaultCupertinoThemeData extends CupertinoThemeData { ); } } + +@immutable +class _CupertinoThemeDefaults { + const _CupertinoThemeDefaults( + this.brightness, + this.primaryColor, + this.primaryContrastingColor, + this.barBackgroundColor, + this.scaffoldBackgroundColor, + this.textThemeDefaults, + ); + + final Brightness brightness; + final Color primaryColor; + final Color primaryContrastingColor; + final Color barBackgroundColor; + final Color scaffoldBackgroundColor; + final _CupertinoTextThemeDefaults textThemeDefaults; + + _CupertinoThemeDefaults resolveFrom(BuildContext context, { @required bool nullOk }) { + assert(nullOk != null); + Color convertColor(Color color) => CupertinoDynamicColor.resolve(color, context, nullOk: nullOk); + + return _CupertinoThemeDefaults( + brightness, + convertColor(primaryColor), + convertColor(primaryContrastingColor), + convertColor(barBackgroundColor), + convertColor(scaffoldBackgroundColor), + textThemeDefaults?.resolveFrom(context, nullOk: nullOk), + ); + } +} + +@immutable +class _CupertinoTextThemeDefaults { + const _CupertinoTextThemeDefaults( + this.labelColor, + this.inactiveGray, + ); + + final Color labelColor; + final Color inactiveGray; + + _CupertinoTextThemeDefaults resolveFrom(BuildContext context, { @required bool nullOk }) { + return _CupertinoTextThemeDefaults( + CupertinoDynamicColor.resolve(labelColor, context, nullOk: nullOk), + CupertinoDynamicColor.resolve(inactiveGray, context, nullOk: nullOk), + ); + } + + CupertinoTextThemeData createDefaults({ @required Color primaryColor }) { + assert(primaryColor != null); + return _DefaultCupertinoTextThemeData( + primaryColor: primaryColor, + labelColor: labelColor, + inactiveGray: inactiveGray, + ); + } +} + +// CupertinoTextThemeData with no text styles explicitly specified. +// The implementation of this class may need to be updated when any of the default +// text styles changes. +class _DefaultCupertinoTextThemeData extends CupertinoTextThemeData { + const _DefaultCupertinoTextThemeData({ + @required this.labelColor, + @required this.inactiveGray, + @required Color primaryColor, + }) : assert(labelColor != null), + assert(inactiveGray != null), + assert(primaryColor != null), + super(primaryColor: primaryColor); + + final Color labelColor; + final Color inactiveGray; + + @override + TextStyle get textStyle => super.textStyle.copyWith(color: labelColor); + + @override + TextStyle get tabLabelTextStyle => super.tabLabelTextStyle.copyWith(color: inactiveGray); + + @override + TextStyle get navTitleTextStyle => super.navTitleTextStyle.copyWith(color: labelColor); + + @override + TextStyle get navLargeTitleTextStyle => super.navLargeTitleTextStyle.copyWith(color: labelColor); + + @override + TextStyle get pickerTextStyle => super.pickerTextStyle.copyWith(color: labelColor); + + @override + TextStyle get dateTimePickerTextStyle => super.dateTimePickerTextStyle.copyWith(color: labelColor); +} diff --git a/packages/flutter/lib/src/widgets/icon_theme.dart b/packages/flutter/lib/src/widgets/icon_theme.dart index 8a922bde09..67201e9f03 100644 --- a/packages/flutter/lib/src/widgets/icon_theme.dart +++ b/packages/flutter/lib/src/widgets/icon_theme.dart @@ -59,7 +59,7 @@ class IconTheme extends InheritedTheme { /// IconThemeData theme = IconTheme.of(context); /// ``` static IconThemeData of(BuildContext context) { - final IconThemeData iconThemeData = _getInheritedIconThemeData(context); + final IconThemeData iconThemeData = _getInheritedIconThemeData(context).resolve(context); return iconThemeData.isConcrete ? iconThemeData : const IconThemeData.fallback().merge(iconThemeData); } diff --git a/packages/flutter/lib/src/widgets/icon_theme_data.dart b/packages/flutter/lib/src/widgets/icon_theme_data.dart index 02983ffb40..bc59671f03 100644 --- a/packages/flutter/lib/src/widgets/icon_theme_data.dart +++ b/packages/flutter/lib/src/widgets/icon_theme_data.dart @@ -8,6 +8,8 @@ import 'dart:ui' as ui show lerpDouble; import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; +import 'framework.dart' show BuildContext; + /// Defines the color, opacity, and size of icons. /// /// Used by [IconTheme] to control the color, opacity, and size of icons in a @@ -54,6 +56,24 @@ class IconThemeData extends Diagnosticable { ); } + /// Called by [IconTheme.of] to convert this instance to an [IconThemeData] + /// that fits the given [BuildContext]. + /// + /// This method gives the ambient [IconThemeData] a chance to update itself, + /// after it's been retrieved by [IconTheme.of], and before being returned as + /// the final result. For instance, [CupertinoIconThemeData] overrides this method + /// to resolve [color], in case [color] is a [CupertinoDynamicColor] and needs + /// to be resolved against the given [BuildContext] before it can be used as a + /// regular [Color]. + /// + /// The default implementation returns this [IconThemeData] as-is. + /// + /// See also: + /// + /// * [CupertinoIconThemeData.resolve] an implementation that resolves + /// [CupertinoIconThemeData.color] before returning. + IconThemeData resolve(BuildContext context) => this; + /// Whether all the properties of this object are non-null. bool get isConcrete => color != null && opacity != null && size != null; diff --git a/packages/flutter/test/cupertino/bottom_tab_bar_test.dart b/packages/flutter/test/cupertino/bottom_tab_bar_test.dart index 491847217b..92fcfaaddc 100644 --- a/packages/flutter/test/cupertino/bottom_tab_bar_test.dart +++ b/packages/flutter/test/cupertino/bottom_tab_bar_test.dart @@ -218,7 +218,7 @@ void main() { matching: find.byType(RichText), )); - expect(actualActive.text.style.color.value, CupertinoColors.activeOrange.darkColor.value); + expect(actualActive.text.style.color, isSameColorAs(CupertinoColors.activeBlue.darkColor)); }); testWidgets('Use active icon', (WidgetTester tester) async { diff --git a/packages/flutter/test/cupertino/button_test.dart b/packages/flutter/test/cupertino/button_test.dart index 40bb654ee6..69426070eb 100644 --- a/packages/flutter/test/cupertino/button_test.dart +++ b/packages/flutter/test/cupertino/button_test.dart @@ -306,7 +306,7 @@ void main() { ), ); - expect(textStyle.color, CupertinoColors.white); + expect(textStyle.color, isSameColorAs(CupertinoColors.white)); BoxDecoration decoration = tester.widget( find.descendant( of: find.byType(CupertinoButton), @@ -327,7 +327,7 @@ void main() { ), ), ); - expect(textStyle.color.value, CupertinoColors.activeOrange.darkColor.value); + expect(textStyle.color, isSameColorAs(CupertinoColors.systemBlue.darkColor)); await tester.pumpWidget( CupertinoApp( @@ -341,14 +341,14 @@ void main() { ), ), ); - expect(textStyle.color, CupertinoColors.black); + expect(textStyle.color, isSameColorAs(CupertinoColors.black)); decoration = tester.widget( find.descendant( of: find.byType(CupertinoButton), matching: find.byType(DecoratedBox), ), ).decoration; - expect(decoration.color.value, CupertinoColors.activeOrange.darkColor.value); + expect(decoration.color, isSameColorAs(CupertinoColors.systemBlue.darkColor)); }); } diff --git a/packages/flutter/test/cupertino/icon_theme_data_test.dart b/packages/flutter/test/cupertino/icon_theme_data_test.dart new file mode 100644 index 0000000000..a5a0a97c5d --- /dev/null +++ b/packages/flutter/test/cupertino/icon_theme_data_test.dart @@ -0,0 +1,38 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('IconTheme.of works', (WidgetTester tester) async { + const IconThemeData data = IconThemeData(color: Color(0xAAAAAAAA), opacity: 0.5, size: 16.0); + + IconThemeData retrieved; + await tester.pumpWidget( + IconTheme(data: data, child: Builder(builder: (BuildContext context) { + retrieved = IconTheme.of(context); + return const SizedBox(); + })) + ); + + expect(retrieved, data); + + await tester.pumpWidget( + IconTheme( + data: const CupertinoIconThemeData(color: CupertinoColors.systemBlue), + child: MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.dark), + child: Builder(builder: (BuildContext context) { + retrieved = IconTheme.of(context); + return const SizedBox(); + }, + ) + ), + ), + ); + + expect(retrieved.color, isSameColorAs(CupertinoColors.systemBlue.darkColor)); + }); +} diff --git a/packages/flutter/test/cupertino/material/tab_scaffold_test.dart b/packages/flutter/test/cupertino/material/tab_scaffold_test.dart index ed749dad7a..515aa15215 100644 --- a/packages/flutter/test/cupertino/material/tab_scaffold_test.dart +++ b/packages/flutter/test/cupertino/material/tab_scaffold_test.dart @@ -133,7 +133,7 @@ void main() { matching: find.byType(DecoratedBox), )).decoration; - expect(tabDecoration.color, const Color(0xCCF8F8F8)); + expect(tabDecoration.color, isSameColorAs(const Color(0xF0F9F9F9))); // Inherited from theme. await tester.tap(find.text('Tab 2')); await tester.pump(); @@ -159,7 +159,7 @@ void main() { matching: find.byType(DecoratedBox), )).decoration; - expect(tabDecoration.color, const Color(0xB7212121)); + expect(tabDecoration.color, isSameColorAs(const Color(0xF01D1D1D))); final RichText tab1 = tester.widget(find.descendant( of: find.text('Tab 1'), diff --git a/packages/flutter/test/cupertino/nav_bar_test.dart b/packages/flutter/test/cupertino/nav_bar_test.dart index c2b81481d0..9c22ff2295 100644 --- a/packages/flutter/test/cupertino/nav_bar_test.dart +++ b/packages/flutter/test/cupertino/nav_bar_test.dart @@ -188,19 +188,18 @@ void main() { testWidgets('Nav bar respects themes', (WidgetTester tester) async { count = 0x000000; - const CupertinoDynamicColor orange = CupertinoColors.activeOrange; await tester.pumpWidget( CupertinoApp( theme: const CupertinoThemeData(brightness: Brightness.dark), home: CupertinoNavigationBar( leading: CupertinoButton( onPressed: () { }, - child: _ExpectStyles(color: orange.darkColor, index: 0x000001), + child: _ExpectStyles(color: CupertinoColors.systemBlue.darkColor, index: 0x000001), ), middle: const _ExpectStyles(color: CupertinoColors.white, index: 0x000100), trailing: CupertinoButton( onPressed: () { }, - child: _ExpectStyles(color: orange.darkColor, index: 0x010000), + child: _ExpectStyles(color: CupertinoColors.systemBlue.darkColor, index: 0x010000), ), ), ), @@ -1129,7 +1128,7 @@ class _ExpectStyles extends StatelessWidget { @override Widget build(BuildContext context) { final TextStyle style = DefaultTextStyle.of(context).style; - expect(style.color.value, color.value); + expect(style.color, isSameColorAs(color)); expect(style.fontFamily, '.SF Pro Text'); expect(style.fontSize, 17.0); expect(style.letterSpacing, -0.41); diff --git a/packages/flutter/test/cupertino/nav_bar_transition_test.dart b/packages/flutter/test/cupertino/nav_bar_transition_test.dart index c18edfe422..ccc8fcea08 100644 --- a/packages/flutter/test/cupertino/nav_bar_transition_test.dart +++ b/packages/flutter/test/cupertino/nav_bar_transition_test.dart @@ -226,7 +226,7 @@ void main() { // The transition's stack is ordered. The bottom middle is inserted first. final RenderParagraph bottomMiddle = tester.renderObject(flying(tester, find.text('Page 1')).first); - expect(bottomMiddle.text.style.color, const Color(0xfffffaf4)); + expect(bottomMiddle.text.style.color, const Color(0xFFF4F9FF)); expect(bottomMiddle.text.style.fontWeight, FontWeight.w600); expect(bottomMiddle.text.style.fontFamily, '.SF Pro Text'); expect(bottomMiddle.text.style.letterSpacing, -0.41); @@ -237,7 +237,7 @@ void main() { // are flipped. final RenderParagraph topBackLabel = tester.renderObject(flying(tester, find.text('Page 1')).last); - expect(topBackLabel.text.style.color, const Color(0xfffffaf4)); + expect(topBackLabel.text.style.color, const Color(0xFFF4F9FF)); expect(topBackLabel.text.style.fontWeight, FontWeight.w600); expect(topBackLabel.text.style.fontFamily, '.SF Pro Text'); expect(topBackLabel.text.style.letterSpacing, -0.41); @@ -246,14 +246,14 @@ void main() { // Move animation further a bit. await tester.pump(const Duration(milliseconds: 200)); - expect(bottomMiddle.text.style.color, const Color(0xffffa923)); + expect(bottomMiddle.text.style.color, const Color(0xFF2390FF)); expect(bottomMiddle.text.style.fontWeight, FontWeight.w400); expect(bottomMiddle.text.style.fontFamily, '.SF Pro Text'); expect(bottomMiddle.text.style.letterSpacing, -0.41); checkOpacity(tester, flying(tester, find.text('Page 1')).first, 0.0); - expect(topBackLabel.text.style.color, const Color(0xffffa923)); + expect(topBackLabel.text.style.color, const Color(0xFF2390FF)); expect(topBackLabel.text.style.fontWeight, FontWeight.w400); expect(topBackLabel.text.style.fontFamily, '.SF Pro Text'); expect(topBackLabel.text.style.letterSpacing, -0.41); diff --git a/packages/flutter/test/cupertino/picker_test.dart b/packages/flutter/test/cupertino/picker_test.dart index cba11acd21..860cd3f3ea 100644 --- a/packages/flutter/test/cupertino/picker_test.dart +++ b/packages/flutter/test/cupertino/picker_test.dart @@ -34,10 +34,11 @@ void main() { final RenderParagraph paragraph = tester.renderObject(find.text('1')); - expect(paragraph.text.style, const TextStyle( + expect(paragraph.text.style.color, isSameColorAs(CupertinoColors.black)); + expect(paragraph.text.style.copyWith(color: CupertinoColors.black), const TextStyle( inherit: false, fontFamily: '.SF Pro Display', - fontSize: 25.0, + fontSize: 21.0, fontWeight: FontWeight.w400, letterSpacing: -0.41, color: CupertinoColors.black, diff --git a/packages/flutter/test/cupertino/scaffold_test.dart b/packages/flutter/test/cupertino/scaffold_test.dart index d4c7a9a1a6..07535b0863 100644 --- a/packages/flutter/test/cupertino/scaffold_test.dart +++ b/packages/flutter/test/cupertino/scaffold_test.dart @@ -411,7 +411,7 @@ void main() { expect(decoratedBox.decoration.runtimeType, BoxDecoration); final BoxDecoration decoration = decoratedBox.decoration; - expect(decoration.color, CupertinoColors.white); + expect(decoration.color, isSameColorAs(CupertinoColors.white)); }); testWidgets('Overrides background color', (WidgetTester tester) async { diff --git a/packages/flutter/test/cupertino/segmented_control_test.dart b/packages/flutter/test/cupertino/segmented_control_test.dart index cef95e364d..47f547fc94 100644 --- a/packages/flutter/test/cupertino/segmented_control_test.dart +++ b/packages/flutter/test/cupertino/segmented_control_test.dart @@ -290,7 +290,7 @@ void main() { DefaultTextStyle textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1')); IconTheme iconTheme = tester.widget(find.widgetWithIcon(IconTheme, const IconData(1))); - expect(textStyle.style.color, CupertinoColors.white); + expect(textStyle.style.color, isSameColorAs(CupertinoColors.white)); expect(iconTheme.data.color, CupertinoColors.activeBlue); await tester.tap(find.widgetWithIcon(IconTheme, const IconData(1))); @@ -300,7 +300,7 @@ void main() { iconTheme = tester.widget(find.widgetWithIcon(IconTheme, const IconData(1))); expect(textStyle.style.color, CupertinoColors.activeBlue); - expect(iconTheme.data.color, CupertinoColors.white); + expect(iconTheme.data.color, isSameColorAs(CupertinoColors.white)); }); testWidgets( @@ -334,8 +334,8 @@ void main() { DefaultTextStyle textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1').first); IconThemeData iconTheme = IconTheme.of(tester.element(find.byIcon(const IconData(1)))); - expect(textStyle.style.color, CupertinoColors.black); - expect(iconTheme.color.value, CupertinoColors.activeOrange.darkColor.value); + expect(textStyle.style.color, isSameColorAs(CupertinoColors.black)); + expect(iconTheme.color, isSameColorAs(CupertinoColors.systemBlue.darkColor)); await tester.tap(find.byIcon(const IconData(1))); await tester.pump(); @@ -344,8 +344,8 @@ void main() { textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1').first); iconTheme = IconTheme.of(tester.element(find.byIcon(const IconData(1)))); - expect(textStyle.style.color.value, CupertinoColors.activeOrange.darkColor.value); - expect(iconTheme.color, CupertinoColors.black); + expect(textStyle.style.color, isSameColorAs(CupertinoColors.systemBlue.darkColor)); + expect(iconTheme.color, isSameColorAs(CupertinoColors.black)); }, ); @@ -484,13 +484,13 @@ void main() { ); expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); await tester.tap(find.text('Child 2')); await tester.pump(); expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); }); testWidgets( @@ -499,7 +499,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget(setupSimpleSegmentedControl()); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); await tester.tap(find.text('Child 2')); await tester.pumpAndSettle(const Duration(milliseconds: 200)); @@ -552,7 +552,7 @@ void main() { expect(getRenderSegmentedControl(tester).selectedIndex, 0); expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); }); testWidgets('Null input for value results in no child initially selected', (WidgetTester tester) async { @@ -582,15 +582,15 @@ void main() { expect(getRenderSegmentedControl(tester).selectedIndex, null); - expect(getBackgroundColor(tester, 0), CupertinoColors.white); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white)); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); }); testWidgets('Long press changes background color of not-selected child', (WidgetTester tester) async { await tester.pumpWidget(setupSimpleSegmentedControl()); expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); final Offset center = tester.getCenter(find.text('Child 2')); await tester.startGesture(center); @@ -604,14 +604,14 @@ void main() { await tester.pumpWidget(setupSimpleSegmentedControl()); expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); final Offset center = tester.getCenter(find.text('Child 1')); await tester.startGesture(center); await tester.pumpAndSettle(); expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); }); testWidgets('Height of segmented control is determined by tallest widget', (WidgetTester tester) async { @@ -768,12 +768,12 @@ void main() { ); expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); await tester.tap(find.text('Child 2')); await tester.pumpAndSettle(); - expect(getBackgroundColor(tester, 0), CupertinoColors.white); + expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white)); expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue); await tester.tap(find.text('Child 2')); @@ -952,7 +952,7 @@ void main() { expect(getBackgroundColor(tester, 1), const Color(0xf8007aff)); await tester.pump(const Duration(milliseconds: 40)); - expect(getBackgroundColor(tester, 0), CupertinoColors.white); + expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white)); expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue); }); @@ -1104,7 +1104,7 @@ void main() { ), const Duration(milliseconds: 40), ); - expect(getBackgroundColor(tester, 0), CupertinoColors.white); + expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white)); expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue); }); @@ -1134,20 +1134,20 @@ void main() { ), ); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); await tester.startGesture(tester.getCenter(find.text('B'))); await tester.pumpAndSettle(const Duration(milliseconds: 200)); expect(getBackgroundColor(tester, 1), const Color(0x33007aff)); - expect(getBackgroundColor(tester, 2), CupertinoColors.white); + expect(getBackgroundColor(tester, 2), isSameColorAs(CupertinoColors.white)); await tester.startGesture(tester.getCenter(find.text('C'))); await tester.pumpAndSettle(const Duration(milliseconds: 200)); // Press on C has no effect while B is held down. expect(getBackgroundColor(tester, 1), const Color(0x33007aff)); - expect(getBackgroundColor(tester, 2), CupertinoColors.white); + expect(getBackgroundColor(tester, 2), isSameColorAs(CupertinoColors.white)); }); testWidgets('Transition is triggered while a transition is already occurring', (WidgetTester tester) async { @@ -1199,12 +1199,12 @@ void main() { await tester.pump(const Duration(milliseconds: 40)); // B background color has reached unselected state. expect(getBackgroundColor(tester, 0), const Color(0xff7bbaff)); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); expect(getBackgroundColor(tester, 2), const Color(0x64007aff)); await tester.pump(const Duration(milliseconds: 100)); // A background color has reached unselected state. - expect(getBackgroundColor(tester, 0), CupertinoColors.white); + expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white)); expect(getBackgroundColor(tester, 2), const Color(0xe0007aff)); await tester.pump(const Duration(milliseconds: 40)); @@ -1237,7 +1237,7 @@ void main() { await tester.pump(const Duration(milliseconds: 40)); // A and B finish transitioning. expect(getBackgroundColor(tester, 0), CupertinoColors.activeBlue); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); }); testWidgets('Add segment while animation is running', (WidgetTester tester) async { @@ -1273,19 +1273,19 @@ void main() { await tester.tap(find.text('B')); await tester.pump(); - expect(getBackgroundColor(tester, 0), CupertinoColors.white); + expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white)); expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue); - expect(getBackgroundColor(tester, 3), CupertinoColors.white); + expect(getBackgroundColor(tester, 3), isSameColorAs(CupertinoColors.white)); await tester.pump(const Duration(milliseconds: 40)); - expect(getBackgroundColor(tester, 0), CupertinoColors.white); + expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white)); expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue); - expect(getBackgroundColor(tester, 3), CupertinoColors.white); + expect(getBackgroundColor(tester, 3), isSameColorAs(CupertinoColors.white)); await tester.pump(const Duration(milliseconds: 150)); - expect(getBackgroundColor(tester, 0), CupertinoColors.white); + expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white)); expect(getBackgroundColor(tester, 1), CupertinoColors.activeBlue); - expect(getBackgroundColor(tester, 3), CupertinoColors.white); + expect(getBackgroundColor(tester, 3), isSameColorAs(CupertinoColors.white)); }); testWidgets('Remove segment while animation is running', (WidgetTester tester) async { @@ -1373,15 +1373,15 @@ void main() { await tester.pump(const Duration(milliseconds: 40)); expect(getBackgroundColor(tester, 0), const Color(0xff3d9aff)); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); await tester.pump(const Duration(milliseconds: 40)); expect(getBackgroundColor(tester, 0), const Color(0xff7bbaff)); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); await tester.pump(const Duration(milliseconds: 100)); - expect(getBackgroundColor(tester, 0), CupertinoColors.white); - expect(getBackgroundColor(tester, 1), CupertinoColors.white); + expect(getBackgroundColor(tester, 0), isSameColorAs(CupertinoColors.white)); + expect(getBackgroundColor(tester, 1), isSameColorAs(CupertinoColors.white)); }); testWidgets('Golden Test Placeholder Widget', (WidgetTester tester) async { diff --git a/packages/flutter/test/cupertino/slider_test.dart b/packages/flutter/test/cupertino/slider_test.dart index ec62ca3568..81a2df2b0e 100644 --- a/packages/flutter/test/cupertino/slider_test.dart +++ b/packages/flutter/test/cupertino/slider_test.dart @@ -427,10 +427,9 @@ void main() { ), ); - const CupertinoDynamicColor orange = CupertinoColors.activeOrange; expect( find.byType(CupertinoSlider), - paints..rrect(color: orange.darkColor), + paints..rrect(color: CupertinoColors.systemBlue.darkColor), ); }); diff --git a/packages/flutter/test/cupertino/tab_scaffold_test.dart b/packages/flutter/test/cupertino/tab_scaffold_test.dart index eb0da3ea00..4ffb80627c 100644 --- a/packages/flutter/test/cupertino/tab_scaffold_test.dart +++ b/packages/flutter/test/cupertino/tab_scaffold_test.dart @@ -340,7 +340,7 @@ void main() { matching: find.byType(DecoratedBox), )).decoration; - expect(tabDecoration.color, const Color(0xCCF8F8F8)); + expect(tabDecoration.color, isSameColorAs(const Color(0xF0F9F9F9))); // Inherited from theme. await tester.tap(find.text('Tab 2')); await tester.pump(); @@ -366,7 +366,7 @@ void main() { matching: find.byType(DecoratedBox), )).decoration; - expect(tabDecoration.color, const Color(0xB7212121)); + expect(tabDecoration.color, isSameColorAs(const Color(0xF01D1D1D))); final RichText tab1 = tester.widget(find.descendant( of: find.text('Tab 1'), @@ -378,7 +378,7 @@ void main() { of: find.text('Tab 2'), matching: find.byType(RichText), )); - expect(tab2.text.style.color.value, CupertinoColors.systemRed.darkColor.value); + expect(tab2.text.style.color, isSameColorAs(CupertinoColors.systemRed.darkColor)); }); testWidgets('Tab contents are padded when there are view insets', (WidgetTester tester) async { diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index c932c69bfc..51c3d26a85 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -2749,7 +2749,7 @@ void main() { tester.renderObject( find.byElementPredicate((Element element) => element.renderObject is RenderEditable) ).text.style.color, - CupertinoColors.white, + isSameColorAs(CupertinoColors.white), ); }, ); @@ -2899,7 +2899,7 @@ void main() { ); await tester.pump(); - expect(renderEditable.cursorColor, CupertinoColors.activeOrange.darkColor); + expect(renderEditable.cursorColor, CupertinoColors.activeBlue.darkColor); await tester.pumpWidget( const CupertinoApp( diff --git a/packages/flutter/test/cupertino/theme_test.dart b/packages/flutter/test/cupertino/theme_test.dart index abd7231db2..8817c692be 100644 --- a/packages/flutter/test/cupertino/theme_test.dart +++ b/packages/flutter/test/cupertino/theme_test.dart @@ -59,7 +59,7 @@ void main() { primaryColor: CupertinoColors.destructiveRed, )); - expect(theme.textTheme.actionTextStyle.color, CupertinoColors.destructiveRed); + expect(theme.textTheme.actionTextStyle.color, isSameColorAs(CupertinoColors.destructiveRed)); }); testWidgets('Dependent attribute can be overridden from cascaded value', (WidgetTester tester) async { @@ -71,9 +71,9 @@ void main() { )); // The brightness still cascaded down to the background color. - expect(theme.scaffoldBackgroundColor, CupertinoColors.black); + expect(theme.scaffoldBackgroundColor, isSameColorAs(CupertinoColors.black)); // But not to the font color which we overrode. - expect(theme.textTheme.textStyle.color, CupertinoColors.black); + expect(theme.textTheme.textStyle.color, isSameColorAs(CupertinoColors.black)); }); testWidgets( @@ -125,24 +125,32 @@ void main() { ); final CupertinoThemeData theme = await testTheme(tester, originalTheme.copyWith( - primaryColor: CupertinoColors.activeGreen, + primaryColor: CupertinoColors.systemGreen, )); expect(theme.brightness, Brightness.dark); - expect(theme.primaryColor.value, CupertinoColors.systemGreen.darkColor.value); + expect(theme.primaryColor, isSameColorAs(CupertinoColors.systemGreen.darkColor)); // Now check calculated derivatives. - expect(theme.textTheme.actionTextStyle.color.value, CupertinoColors.systemGreen.darkColor.value); - expect(theme.scaffoldBackgroundColor.value, CupertinoColors.black.value); + expect(theme.textTheme.actionTextStyle.color, isSameColorAs(CupertinoColors.systemGreen.darkColor)); + expect(theme.scaffoldBackgroundColor, isSameColorAs(CupertinoColors.black)); }, ); testWidgets("Theme has default IconThemeData, which is derived from the theme's primary color", (WidgetTester tester) async { - const Color primaryColor = CupertinoColors.destructiveRed; + const CupertinoDynamicColor primaryColor = CupertinoColors.destructiveRed; const CupertinoThemeData themeData = CupertinoThemeData(primaryColor: primaryColor); final IconThemeData resultingIconTheme = await testIconTheme(tester, themeData); expect(resultingIconTheme.color, themeData.primaryColor); + + // Works in dark mode if primaryColor is a CupertinoDynamicColor. + final Color darkColor = (await testIconTheme( + tester, + themeData.copyWith(brightness: Brightness.dark), + )).color; + + expect(darkColor, isSameColorAs(primaryColor.darkColor)); }); testWidgets('IconTheme.of creates a dependency on iconTheme', (WidgetTester tester) async { @@ -155,4 +163,56 @@ void main() { expect(buildCount, 2); expect(iconTheme.color, CupertinoColors.activeOrange); }); + + Brightness currentBrightness; + void colorMatches(Color componentColor, CupertinoDynamicColor expectedDynamicColor) { + switch (currentBrightness) { + case Brightness.light: + expect(componentColor, isSameColorAs(expectedDynamicColor.color)); + break; + case Brightness.dark: + expect(componentColor, isSameColorAs(expectedDynamicColor.darkColor)); + break; + } + } + + final Function dynamicColorsTestGroup = () { + testWidgets('CupertinoTheme.of resolves colors', (WidgetTester tester) async { + final CupertinoThemeData data = CupertinoThemeData(brightness: currentBrightness, primaryColor: CupertinoColors.systemRed); + final CupertinoThemeData theme = await testTheme(tester, data); + + expect(data.primaryColor, isSameColorAs(CupertinoColors.systemRed.color)); + colorMatches(theme.primaryColor, CupertinoColors.systemRed); + }); + + testWidgets('CupertinoTheme.of resolves default values', (WidgetTester tester) async { + const CupertinoDynamicColor primaryColor = CupertinoColors.systemRed; + final CupertinoThemeData data = CupertinoThemeData(brightness: currentBrightness, primaryColor: primaryColor); + + const CupertinoDynamicColor barBackgroundColor = CupertinoDynamicColor.withBrightness( + color: Color(0xF0F9F9F9), + darkColor: Color(0xF01D1D1D), + ); + + final CupertinoThemeData theme = await testTheme(tester, data); + + colorMatches(theme.primaryContrastingColor, CupertinoColors.systemBackground); + colorMatches(theme.barBackgroundColor, barBackgroundColor); + colorMatches(theme.scaffoldBackgroundColor, CupertinoColors.systemBackground); + colorMatches(theme.textTheme.textStyle.color, CupertinoColors.label); + colorMatches(theme.textTheme.actionTextStyle.color, primaryColor); + colorMatches(theme.textTheme.tabLabelTextStyle.color, CupertinoColors.inactiveGray); + colorMatches(theme.textTheme.navTitleTextStyle.color, CupertinoColors.label); + colorMatches(theme.textTheme.navLargeTitleTextStyle.color, CupertinoColors.label); + colorMatches(theme.textTheme.navActionTextStyle.color, primaryColor); + colorMatches(theme.textTheme.pickerTextStyle.color, CupertinoColors.label); + colorMatches(theme.textTheme.dateTimePickerTextStyle.color, CupertinoColors.label); + }); + }; + + currentBrightness = Brightness.light; + group('light colors', dynamicColorsTestGroup); + + currentBrightness = Brightness.dark; + group('dark colors', dynamicColorsTestGroup); } diff --git a/packages/flutter_test/lib/src/matchers.dart b/packages/flutter_test/lib/src/matchers.dart index 11d9f9fa60..0525404177 100644 --- a/packages/flutter_test/lib/src/matchers.dart +++ b/packages/flutter_test/lib/src/matchers.dart @@ -127,6 +127,12 @@ const Matcher isInCard = _IsInCard(); /// * [isInCard], the opposite. const Matcher isNotInCard = _IsNotInCard(); +/// Asserts that the object represents the same color as [color] when used to paint. +/// +/// Specifically this matcher checks the object is of type [Color] and its [Color.value] +/// equals to that of the given [color]. +Matcher isSameColorAs(Color color) => _ColorMatcher(targetColor: color); + /// Asserts that an object's toString() is a plausible one-line description. /// /// Specifically, this matcher checks that the string does not contains newline @@ -1609,6 +1615,24 @@ class _CoversSameAreaAs extends Matcher { description.add('covers expected area and only expected area'); } +class _ColorMatcher extends Matcher { + const _ColorMatcher({ + @required this.targetColor, + }) : assert(targetColor != null); + + final Color targetColor; + + @override + bool matches(dynamic item, Map matchState) { + if (item is Color) + return item.value == targetColor.value; + return false; + } + + @override + Description describe(Description description) => description.add('matches color $targetColor'); +} + Future _captureImage(Element element) { RenderObject renderObject = element.renderObject; while (!renderObject.isRepaintBoundary) { diff --git a/packages/flutter_test/test/matchers_test.dart b/packages/flutter_test/test/matchers_test.dart index 314ac5280a..b5167feaa4 100644 --- a/packages/flutter_test/test/matchers_test.dart +++ b/packages/flutter_test/test/matchers_test.dart @@ -237,6 +237,33 @@ void main() { ); }); + test('isSameColorAs', () { + expect( + const Color(0x87654321), + isSameColorAs(_CustomColor(0x87654321)), + ); + + expect( + _CustomColor(0x87654321), + isSameColorAs(const Color(0x87654321)), + ); + + expect( + const Color(0x12345678), + isNot(isSameColorAs(_CustomColor(0x87654321))), + ); + + expect( + _CustomColor(0x87654321), + isNot(isSameColorAs(const Color(0x12345678))), + ); + + expect( + _CustomColor(0xFF123456), + isSameColorAs(_CustomColor(0xFF123456)..isEqual = false), + ); + }); + group('coversSameAreaAs', () { test('empty Paths', () { expect( @@ -676,3 +703,14 @@ class _FakeSemanticsNode extends SemanticsNode { @override SemanticsData getSemanticsData() => data; } + +class _CustomColor extends Color { + _CustomColor(int value) : super(value); + bool isEqual; + + @override + bool operator ==(dynamic other) => isEqual ?? super == other; + + @override + int get hashCode => hashValues(super.hashCode, isEqual); +}