diff --git a/packages/flutter/lib/cupertino.dart b/packages/flutter/lib/cupertino.dart index b16282f7af..07a1fba88a 100644 --- a/packages/flutter/lib/cupertino.dart +++ b/packages/flutter/lib/cupertino.dart @@ -18,6 +18,7 @@ export 'src/cupertino/colors.dart'; export 'src/cupertino/date_picker.dart'; export 'src/cupertino/dialog.dart'; export 'src/cupertino/icons.dart'; +export 'src/cupertino/interface_level.dart'; export 'src/cupertino/localizations.dart'; export 'src/cupertino/nav_bar.dart'; export 'src/cupertino/page_scaffold.dart'; diff --git a/packages/flutter/lib/src/cupertino/app.dart b/packages/flutter/lib/src/cupertino/app.dart index fa4968350a..1c3cb9bcd8 100644 --- a/packages/flutter/lib/src/cupertino/app.dart +++ b/packages/flutter/lib/src/cupertino/app.dart @@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart'; import 'button.dart'; import 'colors.dart'; import 'icons.dart'; +import 'interface_level.dart'; import 'localizations.dart'; import 'route.dart'; import 'theme.dart'; @@ -268,45 +269,51 @@ class _CupertinoAppState extends State { return ScrollConfiguration( behavior: _AlwaysCupertinoScrollBehavior(), - child: CupertinoTheme( - data: effectiveThemeData, - child: WidgetsApp( - key: GlobalObjectKey(this), - navigatorKey: widget.navigatorKey, - navigatorObservers: _navigatorObservers, - pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) => - CupertinoPageRoute(settings: settings, builder: builder), - home: widget.home, - routes: widget.routes, - initialRoute: widget.initialRoute, - onGenerateRoute: widget.onGenerateRoute, - onUnknownRoute: widget.onUnknownRoute, - builder: widget.builder, - title: widget.title, - onGenerateTitle: widget.onGenerateTitle, - textStyle: effectiveThemeData.textTheme.textStyle, - color: widget.color ?? CupertinoColors.activeBlue, - locale: widget.locale, - localizationsDelegates: _localizationsDelegates, - localeResolutionCallback: widget.localeResolutionCallback, - localeListResolutionCallback: widget.localeListResolutionCallback, - supportedLocales: widget.supportedLocales, - showPerformanceOverlay: widget.showPerformanceOverlay, - checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, - checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, - showSemanticsDebugger: widget.showSemanticsDebugger, - debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, - inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) { - return CupertinoButton.filled( - child: const Icon( - CupertinoIcons.search, - size: 28.0, - color: CupertinoColors.white, - ), - padding: EdgeInsets.zero, - onPressed: onPressed, - ); - }, + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.base, + child: CupertinoTheme( + data: effectiveThemeData, + child: CupertinoSystemColors( + data: CupertinoSystemColors.of(context, useFallbackValues: true), + child: WidgetsApp( + key: GlobalObjectKey(this), + navigatorKey: widget.navigatorKey, + navigatorObservers: _navigatorObservers, + pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) => + CupertinoPageRoute(settings: settings, builder: builder), + home: widget.home, + routes: widget.routes, + initialRoute: widget.initialRoute, + onGenerateRoute: widget.onGenerateRoute, + onUnknownRoute: widget.onUnknownRoute, + builder: widget.builder, + title: widget.title, + onGenerateTitle: widget.onGenerateTitle, + textStyle: effectiveThemeData.textTheme.textStyle, + color: widget.color ?? CupertinoColors.activeBlue, + locale: widget.locale, + localizationsDelegates: _localizationsDelegates, + localeResolutionCallback: widget.localeResolutionCallback, + localeListResolutionCallback: widget.localeListResolutionCallback, + supportedLocales: widget.supportedLocales, + showPerformanceOverlay: widget.showPerformanceOverlay, + checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, + checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, + showSemanticsDebugger: widget.showSemanticsDebugger, + debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, + inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) { + return CupertinoButton.filled( + child: const Icon( + CupertinoIcons.search, + size: 28.0, + color: CupertinoColors.white, + ), + padding: EdgeInsets.zero, + onPressed: onPressed, + ); + }, + ), + ), ), ), ); diff --git a/packages/flutter/lib/src/cupertino/colors.dart b/packages/flutter/lib/src/cupertino/colors.dart index e2868ebe62..bf69c754a3 100644 --- a/packages/flutter/lib/src/cupertino/colors.dart +++ b/packages/flutter/lib/src/cupertino/colors.dart @@ -3,6 +3,19 @@ // found in the LICENSE file. import 'dart:ui' show Color; +import 'package:collection/collection.dart' show DeepCollectionEquality; + +import '../../foundation.dart'; +import '../widgets/basic.dart'; +import '../widgets/framework.dart'; +import '../widgets/media_query.dart'; +import 'interface_level.dart'; +import 'theme.dart'; + +// Examples can assume: +// Widget child; +// Color lightModeColor; +// Color darkModeColor; /// A palette of [Color] constants that describe colors commonly used when /// matching the iOS platform aesthetics. @@ -80,3 +93,1116 @@ class CupertinoColors { /// This is SystemRed in the iOS palette. static const Color destructiveRed = Color(0xFFFF3B30); } + +/// 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 +/// 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. +/// +/// See also: +/// +/// * [CupertinoUserInterfaceLevel], an [InheritedWidget] that may affect color +/// resolution of a `CupertinoDynamicColor`. +/// * [CupertinoSystemColors], an [InheritedWidget] that exposes system colors +/// of iOS 13+. +/// * https://developer.apple.com/documentation/uikit/uicolor/3238042-resolvedcolor. +@immutable +class CupertinoDynamicColor extends Color { + /// Creates an adaptive [Color] that changes its effective color based on the + /// [BuildContext] given. The default effective color is [color]. + /// + /// All the colors must not be null. + CupertinoDynamicColor({ + @required Color color, + @required Color darkColor, + @required Color highContrastColor, + @required Color darkHighContrastColor, + @required Color elevatedColor, + @required Color darkElevatedColor, + @required Color highContrastElevatedColor, + @required Color darkHighContrastElevatedColor, + }) : this._( + color, + >>[ + >[ + [darkColor, darkElevatedColor], + [darkHighContrastColor, darkHighContrastElevatedColor], + ], + >[ + [color, elevatedColor], + [highContrastColor, highContrastElevatedColor], + ], + ], + ); + + /// Creates an adaptive [Color] that changes its effective color based on the + /// given [BuildContext]'s brightness (from [MediaQueryData.platformBrightness] + /// or [CupertinoThemeData.brightness]) and accessibility contrast setting + /// ([MediaQueryData.highContrast]). The default effective color is [color]. + /// + /// All the colors must not be null. + CupertinoDynamicColor.withBrightnessAndContrast({ + @required Color color, + @required Color darkColor, + @required Color highContrastColor, + @required Color darkHighContrastColor, + }) : this( + color: color, + darkColor: darkColor, + highContrastColor: highContrastColor, + darkHighContrastColor: darkHighContrastColor, + elevatedColor: color, + darkElevatedColor: darkColor, + highContrastElevatedColor: highContrastColor, + darkHighContrastElevatedColor: darkHighContrastColor, + ); + /// Creates an adaptive [Color] that changes its effective color based on the given + /// [BuildContext]'s brightness (from [MediaQueryData.platformBrightness] or + /// [CupertinoThemeData.brightness]). The default effective color is [color]. + /// + /// All the colors must not be null. + CupertinoDynamicColor.withBrightness({ + @required Color color, + @required Color darkColor, + }) : this( + color: color, + darkColor: darkColor, + highContrastColor: color, + darkHighContrastColor: darkColor, + elevatedColor: color, + darkElevatedColor: darkColor, + highContrastElevatedColor: color, + darkHighContrastElevatedColor: darkColor, + ); + + CupertinoDynamicColor._( + Color value, + this._colorMap, + ) : assert(() { + Iterable expand(Object a) { + return (a is Iterable) ? a.expand(expand) : [a]; + } + + final Iterable expanded = expand(_colorMap); + assert(expanded.contains(value), '$value is not one of $_colorMap'); + return !expanded.contains(null) && expanded.length == 8; + }(), 'The colorMap provided is invalid: $_colorMap'), + super(value.value); + + /// 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` + /// 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]. + /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `false`. + /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.base]. + Color get color => _colorMap[Brightness.light.index] + [0] // 0 for normal contrast. + [CupertinoUserInterfaceLevelData.base.index]; + + /// 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` + /// 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]. + /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `false`. + /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.base]. + Color get darkColor => _colorMap[Brightness.dark.index] + [0] // 0 for normal contrast. + [CupertinoUserInterfaceLevelData.base.index]; + + /// 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` + /// 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]. + /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `true`. + /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.base]. + Color get highContrastColor => _colorMap[Brightness.light.index] + [1] // 1 for high contrast. + [CupertinoUserInterfaceLevelData.base.index]; + + /// 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` + /// 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]. + /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `true`. + /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.base]. + Color get darkHighContrastColor => _colorMap[Brightness.dark.index] + [1] // 1 for high contrast. + [CupertinoUserInterfaceLevelData.base.index]; + + /// 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` + /// 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]. + /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `false`. + /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.elevated]. + Color get elevatedColor => _colorMap [Brightness.light.index] + [0] // 0 for normal contrast. + [CupertinoUserInterfaceLevelData.elevated.index]; + + /// 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` + /// 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]. + /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `false`. + /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.elevated]. + Color get darkElevatedColor => _colorMap[Brightness.dark.index] + [0] // 0 for normal contrast. + [CupertinoUserInterfaceLevelData.elevated.index]; + + /// 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` + /// 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]. + /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `true`. + /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.elevated]. + Color get highContrastElevatedColor => _colorMap[Brightness.light.index] + [1] // 1 for high contrast. + [CupertinoUserInterfaceLevelData.elevated.index]; + + /// 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` + /// 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]. + /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `true`. + /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.elevated]. + Color get darkHighContrastElevatedColor => _colorMap[Brightness.dark.index] + [1] // 1 for high contrast. + [CupertinoUserInterfaceLevelData.elevated.index]; + + final List>> _colorMap; + + /// Resolves the given [Color] by calling [resolveFrom]. + /// + /// If the given color is already a concrete [Color], it will be returned as is. + /// If the given color is a [CupertinoDynamicColor], but the given [BuildContext] + /// lacks the dependencies essential to the color resolution, an exception will + /// be thrown, unless [nullOk] is set to true. + static Color resolve(Color resolvable, BuildContext context, { bool nullOk = false }) { + assert(resolvable != null); + assert(context != null); + return (resolvable is CupertinoDynamicColor) + ? resolvable.resolveFrom(context, nullOk: nullOk) + : resolvable; + } + + bool get _isPlatformBrightnessDependent { + return color != darkColor + || elevatedColor != darkElevatedColor + || highContrastColor != darkHighContrastColor + || highContrastElevatedColor != darkHighContrastElevatedColor; + } + + bool get _isHighContrastDependent { + return color != highContrastColor + || darkColor != darkHighContrastColor + || elevatedColor != highContrastElevatedColor + || darkElevatedColor != darkHighContrastElevatedColor; + } + + bool get _isInterfaceElevationDependent { + return color != elevatedColor + || darkColor != darkElevatedColor + || highContrastColor != highContrastElevatedColor + || darkHighContrastColor != darkHighContrastElevatedColor; + } + + /// 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 + /// changed to adapt to the given [BuildContext]. + /// + /// For example, if the given [BuildContext] indicates the widgets in the subtree + /// should be displayed in dark mode (the surrounding [CupertinoTheme]'s [CupertinoThemeData.brightness] + /// or [MediaQuery]'s [MediaQueryData.platformBrightness] is [PlatformBrightness.dark]), + /// 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], + /// except its effective color will be the `darkHighContrastElevatedColor` variant + /// 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] + /// is different from [color], this method will call [CupertinoTheme.of], and + /// then [MediaQuery.of] if brightness wasn't specified in the theme data retrived + /// from the previous [CupertinoTheme.of] call, in an effort to determine the + /// brightness value. + /// + /// If any of the required dependecies are missing from the given context, an exception + /// will be thrown unless [nullOk] is set to `true`. + CupertinoDynamicColor resolveFrom(BuildContext context, { bool nullOk = false }) { + int brightnessNumber = 0; + int highContrastNumber = 0; + int interfaceElevationNumber = 0; + + // If this CupertinoDynamicColor cares about brightness. + if (_isPlatformBrightnessDependent) { + final Brightness brightness = CupertinoTheme.brightnessOf(context, nullOk: nullOk) ?? Brightness.light; + brightnessNumber = brightness.index; + } + + // If this CupertinoDynamicColor cares about accessibility contrast. + if (_isHighContrastDependent) { + final bool isHighContrastEnabled = MediaQuery.of(context, nullOk: nullOk)?.highContrast + ?? false; + + highContrastNumber = isHighContrastEnabled ? 1 : 0; + } + + // If this CupertinoDynamicColor cares about user interface elevation. + if (_isInterfaceElevationDependent) { + final CupertinoUserInterfaceLevelData level = CupertinoUserInterfaceLevel.of(context, nullOk: nullOk) + ?? CupertinoUserInterfaceLevelData.base; + + interfaceElevationNumber = level.index; + } + + final Color resolved = _colorMap[brightnessNumber][highContrastNumber][interfaceElevationNumber]; + return resolved.value == value ? this : CupertinoDynamicColor._(resolved, _colorMap); + } + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) + return true; + + return other.runtimeType == runtimeType + && value == other.value + && (identical(_colorMap, other._colorMap) || const DeepCollectionEquality().equals(_colorMap, other._colorMap)); + } + + @override + int get hashCode { + return hashValues( + value, + color, + darkColor, + highContrastColor, + elevatedColor, + darkElevatedColor, + darkHighContrastColor, + darkHighContrastElevatedColor, + highContrastElevatedColor, + ); + } + + @override + String toString() { + String toString(String name, Color color) { + final String marker = color.value == value ? '*' : ''; + return '$marker$name = $color$marker'; + } + + final List xs = [toString('color', color), + if (_isPlatformBrightnessDependent) toString('darkColor', darkColor), + if (_isHighContrastDependent) toString('highContrastColor', highContrastColor), + if (_isPlatformBrightnessDependent && _isHighContrastDependent) toString('darkHighContrastColor', darkHighContrastColor), + if (_isInterfaceElevationDependent) toString('elevatedColor', elevatedColor), + if (_isPlatformBrightnessDependent && _isInterfaceElevationDependent) toString('darkElevatedColor', darkElevatedColor), + if (_isHighContrastDependent && _isInterfaceElevationDependent) toString('highContrastElevatedColor', highContrastElevatedColor), + if (_isPlatformBrightnessDependent && _isHighContrastDependent && _isInterfaceElevationDependent) toString('darkHighContrastElevatedColor', darkHighContrastElevatedColor), + ]; + + return '$runtimeType(${xs.join(', ')})'; + } +} + +/// A color palette that typically matches iOS 13+ system colors. +/// +/// Generally you should not create a [CupertinoSystemColorsData] yourself. +/// Use [CupertinoSystemColors.of] to get the [CupertinoSystemColorsData] from the +/// current [BuildContext] if possible, or [CupertinoSystemColors.fromSystem] +/// when the current [BuildContext] is not available (e.g., in [CupertinoApp]'s +/// constructor). +@immutable +class CupertinoSystemColorsData extends Diagnosticable { + /// Creates a color palette. + /// + /// Generally you should not create your own `CupertinoSystemColorsData`. + /// Use [CupertinoSystemColors.of] to get the [CupertinoSystemColorsData] from the + /// current [BuildContext] if possible, or [CupertinoSystemColors.fromSystem] + /// when the current [BuildContext] is not available (e.g., in [CupertinoApp]'s + /// constructor). + const CupertinoSystemColorsData({ + @required this.label, + @required this.secondaryLabel, + @required this.tertiaryLabel, + @required this.quaternaryLabel, + @required this.systemFill, + @required this.secondarySystemFill, + @required this.tertiarySystemFill, + @required this.quaternarySystemFill, + @required this.placeholderText, + @required this.systemBackground, + @required this.secondarySystemBackground, + @required this.tertiarySystemBackground, + @required this.systemGroupedBackground, + @required this.secondarySystemGroupedBackground, + @required this.tertiarySystemGroupedBackground, + @required this.separator, + @required this.opaqueSeparator, + @required this.link, + @required this.systemBlue, + @required this.systemGreen, + @required this.systemIndigo, + @required this.systemOrange, + @required this.systemPink, + @required this.systemPurple, + @required this.systemRed, + @required this.systemTeal, + @required this.systemYellow, + @required this.systemGray, + @required this.systemGray2, + @required this.systemGray3, + @required this.systemGray4, + @required this.systemGray5, + @required this.systemGray6, + }) : assert(label != null), + assert(secondaryLabel != null), + assert(tertiaryLabel != null), + assert(quaternaryLabel != null), + assert(systemFill != null), + assert(secondarySystemFill != null), + assert(tertiarySystemFill != null), + assert(quaternarySystemFill != null), + assert(placeholderText != null), + assert(systemBackground != null), + assert(secondarySystemBackground != null), + assert(tertiarySystemBackground != null), + assert(systemGroupedBackground != null), + assert(secondarySystemGroupedBackground != null), + assert(tertiarySystemGroupedBackground != null), + assert(separator != null), + assert(opaqueSeparator != null), + assert(link != null), + assert(systemBlue != null), + assert(systemGreen != null), + assert(systemIndigo != null), + assert(systemOrange != null), + assert(systemPink != null), + assert(systemPurple != null), + assert(systemRed != null), + assert(systemTeal != null), + assert(systemYellow != null), + assert(systemGray != null), + assert(systemGray2 != null), + assert(systemGray3 != null), + assert(systemGray4 != null), + assert(systemGray5 != null), + assert(systemGray6 != null), + super(); + + /// The color for text labels containing primary content. + final CupertinoDynamicColor label; + + /// The color for text labels containing secondary content. + final CupertinoDynamicColor secondaryLabel; + + /// The color for text labels containing tertiary content. + final CupertinoDynamicColor tertiaryLabel; + + /// The color for text labels containing quaternary content. + final CupertinoDynamicColor quaternaryLabel; + + /// An overlay fill color for thin and small shapes. + final CupertinoDynamicColor systemFill; + + /// An overlay fill color for medium-size shapes. + final CupertinoDynamicColor secondarySystemFill; + + /// An overlay fill color for large shapes. + final CupertinoDynamicColor tertiarySystemFill; + + /// An overlay fill color for large areas containing complex content. + final CupertinoDynamicColor quaternarySystemFill; + + /// The color for placeholder text in controls or text views. + final CupertinoDynamicColor placeholderText; + + /// The color for the main background of your interface. + /// + /// Typically used for designs that have a white primary background in a light environment. + final CupertinoDynamicColor systemBackground; + + /// The color for content layered on top of the main background. + /// + /// Typically used for designs that have a white primary background in a light environment. + final CupertinoDynamicColor secondarySystemBackground; + + /// The color for content layered on top of secondary backgrounds. + /// + /// Typically used for designs that have a white primary background in a light environment. + final CupertinoDynamicColor tertiarySystemBackground; + + /// The color for the main background of your grouped interface. + /// + /// Typically used for grouped content, including table views and platter-based designs. + final CupertinoDynamicColor systemGroupedBackground; + + /// The color for content layered on top of the main background of your grouped interface. + /// + /// Typically used for grouped content, including table views and platter-based designs. + final CupertinoDynamicColor secondarySystemGroupedBackground; + + /// The color for content layered on top of secondary backgrounds of your grouped interface. + /// + /// Typically used for grouped content, including table views and platter-based designs. + final CupertinoDynamicColor tertiarySystemGroupedBackground; + + /// The color for thin borders or divider lines that allows some underlying content to be visible. + final CupertinoDynamicColor separator; + + /// The color for borders or divider lines that hide any underlying content. + final CupertinoDynamicColor opaqueSeparator; + + /// The color for links. + final CupertinoDynamicColor link; + + /// A blue color that can adapt to the given [BuildContext]. + final CupertinoDynamicColor systemBlue; + + /// A green color that can adapt to the given [BuildContext]. + final CupertinoDynamicColor systemGreen; + + /// An indigo color that can adapt to the given [BuildContext]. + final CupertinoDynamicColor systemIndigo; + + /// An orange color that can adapt to the given [BuildContext]. + final CupertinoDynamicColor systemOrange; + + /// A pink color that can adapt to the given [BuildContext]. + final CupertinoDynamicColor systemPink; + + /// A purple color that can adapt to the given [BuildContext]. + final CupertinoDynamicColor systemPurple; + + /// A red color that can adapt to the given [BuildContext]. + final CupertinoDynamicColor systemRed; + + /// A teal color that can adapt to the given [BuildContext]. + final CupertinoDynamicColor systemTeal; + + /// A yellow color that can adapt to the given [BuildContext]. + final CupertinoDynamicColor systemYellow; + + /// The base gray color. + final CupertinoDynamicColor systemGray; + + /// A second-level shade of grey. + final CupertinoDynamicColor systemGray2; + + /// A third-level shade of grey. + final CupertinoDynamicColor systemGray3; + + /// A fourth-level shade of grey. + final CupertinoDynamicColor systemGray4; + + /// A fifth-level shade of grey. + final CupertinoDynamicColor systemGray5; + + /// A sixth-level shade of grey. + final CupertinoDynamicColor systemGray6; + + /// Resolve every color in the palette using the given [BuildContext], by calling + /// [CupertinoDynamicColor.resolve], and return a new [CupertinoSystemColorsData] + /// with all the resolved colors. + CupertinoSystemColorsData resolveColors(BuildContext context) { + return CupertinoSystemColorsData( + label: CupertinoDynamicColor.resolve(label, context), + secondaryLabel: CupertinoDynamicColor.resolve(secondaryLabel, context), + tertiaryLabel: CupertinoDynamicColor.resolve(tertiaryLabel, context), + quaternaryLabel: CupertinoDynamicColor.resolve(quaternaryLabel, context), + systemFill: CupertinoDynamicColor.resolve(systemFill, context), + secondarySystemFill: CupertinoDynamicColor.resolve(secondarySystemFill, context), + tertiarySystemFill: CupertinoDynamicColor.resolve(tertiarySystemFill, context), + quaternarySystemFill: CupertinoDynamicColor.resolve(quaternarySystemFill, context), + placeholderText: CupertinoDynamicColor.resolve(placeholderText, context), + systemBackground: CupertinoDynamicColor.resolve(systemBackground, context), + secondarySystemBackground: CupertinoDynamicColor.resolve(secondarySystemBackground, context), + tertiarySystemBackground: CupertinoDynamicColor.resolve(tertiarySystemBackground, context), + systemGroupedBackground: CupertinoDynamicColor.resolve(systemGroupedBackground, context), + secondarySystemGroupedBackground: CupertinoDynamicColor.resolve(secondarySystemGroupedBackground, context), + tertiarySystemGroupedBackground: CupertinoDynamicColor.resolve(tertiarySystemGroupedBackground, context), + separator: CupertinoDynamicColor.resolve(separator, context), + opaqueSeparator: CupertinoDynamicColor.resolve(opaqueSeparator, context), + link: CupertinoDynamicColor.resolve(link, context), + systemBlue: CupertinoDynamicColor.resolve(systemBlue, context), + systemGreen: CupertinoDynamicColor.resolve(systemGreen, context), + systemIndigo: CupertinoDynamicColor.resolve(systemIndigo, context), + systemOrange: CupertinoDynamicColor.resolve(systemOrange, context), + systemPink: CupertinoDynamicColor.resolve(systemPink, context), + systemPurple: CupertinoDynamicColor.resolve(systemPurple, context), + systemRed: CupertinoDynamicColor.resolve(systemRed, context), + systemTeal: CupertinoDynamicColor.resolve(systemTeal, context), + systemYellow: CupertinoDynamicColor.resolve(systemYellow, context), + systemGray: CupertinoDynamicColor.resolve(systemGray, context), + systemGray2: CupertinoDynamicColor.resolve(systemGray2, context), + systemGray3: CupertinoDynamicColor.resolve(systemGray3, context), + systemGray4: CupertinoDynamicColor.resolve(systemGray4, context), + systemGray5: CupertinoDynamicColor.resolve(systemGray5, context), + systemGray6: CupertinoDynamicColor.resolve(systemGray6, context), + ); + } + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) + return true; + return other.runtimeType == runtimeType + && other.label == label + && other.secondaryLabel == secondaryLabel + && other.tertiaryLabel == tertiaryLabel + && other.quaternaryLabel == quaternaryLabel + && other.systemFill == systemFill + && other.secondarySystemFill == secondarySystemFill + && other.tertiarySystemFill == tertiarySystemFill + && other.quaternarySystemFill == quaternarySystemFill + && other.placeholderText == placeholderText + && other.systemBackground == systemBackground + && other.secondarySystemBackground == secondarySystemBackground + && other.tertiarySystemBackground == tertiarySystemBackground + && other.systemGroupedBackground == systemGroupedBackground + && other.secondarySystemGroupedBackground == secondarySystemGroupedBackground + && other.tertiarySystemGroupedBackground == tertiarySystemGroupedBackground + && other.separator == separator + && other.opaqueSeparator== opaqueSeparator + && other.link == link + && other.systemBlue == systemBlue + && other.systemGreen == systemGreen + && other.systemIndigo == systemIndigo + && other.systemOrange == systemOrange + && other.systemPink == systemPink + && other.systemPurple == systemPurple + && other.systemRed == systemRed + && other.systemTeal == systemTeal + && other.systemYellow == systemYellow + && other.systemGray == systemGray + && other.systemGray2 == systemGray2 + && other.systemGray3 == systemGray3 + && other.systemGray4 == systemGray4 + && other.systemGray5 == systemGray5 + && other.systemGray6 == systemGray6; + } + + @override + int get hashCode { + return hashList( + [ + label, + secondaryLabel, + tertiaryLabel, + quaternaryLabel, + systemFill, + secondarySystemFill, + tertiarySystemFill, + quaternarySystemFill, + placeholderText, + systemBackground, + secondarySystemBackground, + tertiarySystemBackground, + systemGroupedBackground, + secondarySystemGroupedBackground, + tertiarySystemGroupedBackground, + separator, + opaqueSeparator, + link, + systemBlue, + systemGreen, + systemIndigo, + systemOrange, + systemPink, + systemPurple, + systemRed, + systemTeal, + systemYellow, + systemGray, + systemGray2, + systemGray3, + systemGray4, + systemGray5, + systemGray6, + ]); + } + + /// Creates a copy of this CupertinoSystemColorsData but with the given fields + /// replace with the new values. + CupertinoSystemColorsData copyWith({ + CupertinoDynamicColor label, + CupertinoDynamicColor secondaryLabel, + CupertinoDynamicColor tertiaryLabel, + CupertinoDynamicColor quaternaryLabel, + CupertinoDynamicColor systemFill, + CupertinoDynamicColor secondarySystemFill, + CupertinoDynamicColor tertiarySystemFill, + CupertinoDynamicColor quaternarySystemFill, + CupertinoDynamicColor placeholderText, + CupertinoDynamicColor systemBackground, + CupertinoDynamicColor secondarySystemBackground, + CupertinoDynamicColor tertiarySystemBackground, + CupertinoDynamicColor systemGroupedBackground, + CupertinoDynamicColor secondarySystemGroupedBackground, + CupertinoDynamicColor tertiarySystemGroupedBackground, + CupertinoDynamicColor separator, + CupertinoDynamicColor opaqueSeparator, + CupertinoDynamicColor link, + CupertinoDynamicColor systemBlue, + CupertinoDynamicColor systemGreen, + CupertinoDynamicColor systemIndigo, + CupertinoDynamicColor systemOrange, + CupertinoDynamicColor systemPink, + CupertinoDynamicColor systemPurple, + CupertinoDynamicColor systemRed, + CupertinoDynamicColor systemTeal, + CupertinoDynamicColor systemYellow, + CupertinoDynamicColor systemGray, + CupertinoDynamicColor systemGray2, + CupertinoDynamicColor systemGray3, + CupertinoDynamicColor systemGray4, + CupertinoDynamicColor systemGray5, + CupertinoDynamicColor systemGray6, + }) { + return CupertinoSystemColorsData( + label: label ?? this.label, + secondaryLabel: secondaryLabel ?? this.secondaryLabel, + tertiaryLabel: tertiaryLabel ?? this.tertiaryLabel, + quaternaryLabel: quaternaryLabel ?? this.quaternaryLabel, + systemFill: systemFill ?? this.systemFill, + secondarySystemFill: secondarySystemFill ?? this.secondarySystemFill, + tertiarySystemFill: tertiarySystemFill ?? this.tertiarySystemFill, + quaternarySystemFill: quaternarySystemFill ?? this.quaternarySystemFill, + placeholderText: placeholderText ?? this.placeholderText, + systemBackground: systemBackground ?? this.systemBackground, + secondarySystemBackground: secondarySystemBackground ?? this.secondarySystemBackground, + tertiarySystemBackground: tertiarySystemBackground ?? this.tertiarySystemBackground, + systemGroupedBackground: systemGroupedBackground ?? this.systemGroupedBackground, + secondarySystemGroupedBackground: secondarySystemGroupedBackground ?? this.secondarySystemGroupedBackground, + tertiarySystemGroupedBackground: tertiarySystemGroupedBackground ?? this.tertiarySystemGroupedBackground, + separator: separator ?? this.separator, + opaqueSeparator: opaqueSeparator ?? this.opaqueSeparator, + link: link ?? this.link, + systemBlue: systemBlue ?? this.systemBlue, + systemGreen: systemGreen ?? this.systemGreen, + systemIndigo: systemIndigo ?? this.systemIndigo, + systemOrange: systemOrange ?? this.systemOrange, + systemPink: systemPink ?? this.systemPink, + systemPurple: systemPurple ?? this.systemPurple, + systemRed: systemRed ?? this.systemRed, + systemTeal: systemTeal ?? this.systemTeal, + systemYellow: systemYellow ?? this.systemYellow, + systemGray: systemGray ?? this.systemGray, + systemGray2: systemGray2 ?? this.systemGray2, + systemGray3: systemGray3 ?? this.systemGray3, + systemGray4: systemGray4 ?? this.systemGray4, + systemGray5: systemGray5 ?? this.systemGray5, + systemGray6: systemGray6 ?? this.systemGray6, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(ColorProperty('label', label)); + properties.add(ColorProperty('secondaryLabel', secondaryLabel)); + properties.add(ColorProperty('tertiaryLabel', tertiaryLabel)); + properties.add(ColorProperty('quaternaryLabel', quaternaryLabel)); + properties.add(ColorProperty('systemFill', systemFill)); + properties.add(ColorProperty('secondarySystemFill', secondarySystemFill)); + properties.add(ColorProperty('tertiarySystemFill', tertiarySystemFill)); + properties.add(ColorProperty('quaternarySystemFill', quaternarySystemFill)); + properties.add(ColorProperty('placeholderText', placeholderText)); + properties.add(ColorProperty('systemBackground', systemBackground)); + properties.add(ColorProperty('secondarySystemBackground', secondarySystemBackground)); + properties.add(ColorProperty('tertiarySystemBackground', tertiarySystemBackground)); + properties.add(ColorProperty('systemGroupedBackground', systemGroupedBackground)); + properties.add(ColorProperty('secondarySystemGroupedBackground', secondarySystemGroupedBackground)); + properties.add(ColorProperty('tertiarySystemGroupedBackground', tertiarySystemGroupedBackground)); + properties.add(ColorProperty('separator', separator)); + properties.add(ColorProperty('opaqueSeparator', opaqueSeparator)); + properties.add(ColorProperty('link', link)); + properties.add(ColorProperty('systemBlue', systemBlue)); + properties.add(ColorProperty('systemGreen', systemGreen)); + properties.add(ColorProperty('systemIndigo', systemIndigo)); + properties.add(ColorProperty('systemOrange', systemOrange)); + properties.add(ColorProperty('systemPink', systemPink)); + properties.add(ColorProperty('systemPurple', systemPurple)); + properties.add(ColorProperty('systemRed', systemRed)); + properties.add(ColorProperty('systemTeal', systemTeal)); + properties.add(ColorProperty('systemYellow', systemYellow)); + properties.add(ColorProperty('systemGray', systemGray)); + properties.add(ColorProperty('systemGray2', systemGray2)); + properties.add(ColorProperty('systemGray3', systemGray3)); + properties.add(ColorProperty('systemGray4', systemGray4)); + properties.add(ColorProperty('systemGray5', systemGray5)); + properties.add(ColorProperty('systemGray6', systemGray6)); + } +} + +/// Establishes a subtree where iOS system colors resolve to the given data. +/// +/// Typically the given [CupertinoSystemColorsData] is resolved against its own +/// [BuildContext] using [CupertinoSystemColorsData.resolveColors]. +class CupertinoSystemColors extends InheritedWidget { + /// Creates a widget that provides a given [CupertinoSystemColorsData] to its + /// descendants. + const CupertinoSystemColors({ + Key key, + @required CupertinoSystemColorsData data, + Widget child, + }) : _data = data, + assert(data != null), + super(key: key, child: child); + + final CupertinoSystemColorsData _data; + + @override + bool updateShouldNotify(CupertinoSystemColors oldWidget) => oldWidget._data != _data; + + /// Retrieves the iOS system colors from the given [BuildContext]. + /// + /// Falls back to [fromSystem] if a [CupertinoSystemColors] widget couldn't be + /// found in the ancestry tree. When [fromSystem] returns null, setting [useFallbackValues] + /// to true will make the method return a set of default system colors extracted + /// from iOS 13 beta. + static CupertinoSystemColorsData of(BuildContext context, { bool useFallbackValues = true }) { + assert(context != null); + assert(useFallbackValues != null); + final CupertinoSystemColors widget = context.inheritFromWidgetOfExactType(CupertinoSystemColors); + return widget?._data ?? (useFallbackValues ? fallbackValues : null); + } + + /// Fallback System Colors, extracted from: + /// https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/color/#dynamic-system-colors + /// and iOS 13 beta. + @visibleForTesting + static CupertinoSystemColorsData get fallbackValues { + return CupertinoSystemColorsData( + label: CupertinoDynamicColor( + color: const Color.fromARGB(255, 0, 0, 0), + darkColor: const Color.fromARGB(255, 255, 255, 255), + highContrastColor: const Color.fromARGB(255, 0, 0, 0), + darkHighContrastColor: const Color.fromARGB(255, 255, 255, 255), + elevatedColor: const Color.fromARGB(255, 0, 0, 0), + darkElevatedColor: const Color.fromARGB(255, 255, 255, 255), + highContrastElevatedColor: const Color.fromARGB(255, 0, 0, 0), + darkHighContrastElevatedColor: const Color.fromARGB(255, 255, 255, 255), + ), + secondaryLabel: CupertinoDynamicColor( + color: const Color.fromARGB(255, 0, 0, 0), + darkColor: const Color.fromARGB(255, 255, 255, 255), + highContrastColor: const Color.fromARGB(255, 0, 0, 0), + darkHighContrastColor: const Color.fromARGB(255, 255, 255, 255), + elevatedColor: const Color.fromARGB(255, 0, 0, 0), + darkElevatedColor: const Color.fromARGB(255, 255, 255, 255), + highContrastElevatedColor: const Color.fromARGB(255, 0, 0, 0), + darkHighContrastElevatedColor: const Color.fromARGB(255, 255, 255, 255), + ), + tertiaryLabel: CupertinoDynamicColor( + color: const Color.fromARGB(76, 60, 60, 67), + darkColor: const Color.fromARGB(76, 235, 235, 245), + highContrastColor: const Color.fromARGB(96, 60, 60, 67), + darkHighContrastColor: const Color.fromARGB(96, 235, 235, 245), + elevatedColor: const Color.fromARGB(76, 60, 60, 67), + darkElevatedColor: const Color.fromARGB(76, 235, 235, 245), + highContrastElevatedColor: const Color.fromARGB(96, 60, 60, 67), + darkHighContrastElevatedColor: const Color.fromARGB(96, 235, 235, 245), + ), + quaternaryLabel: CupertinoDynamicColor( + color: const Color.fromARGB(45, 60, 60, 67), + darkColor: const Color.fromARGB(40, 235, 235, 245), + highContrastColor: const Color.fromARGB(66, 60, 60, 67), + darkHighContrastColor: const Color.fromARGB(61, 235, 235, 245), + elevatedColor: const Color.fromARGB(45, 60, 60, 67), + darkElevatedColor: const Color.fromARGB(40, 235, 235, 245), + highContrastElevatedColor: const Color.fromARGB(66, 60, 60, 67), + darkHighContrastElevatedColor: const Color.fromARGB(61, 235, 235, 245), + ), + systemFill: CupertinoDynamicColor( + color: const Color.fromARGB(51, 120, 120, 128), + darkColor: const Color.fromARGB(91, 120, 120, 128), + highContrastColor: const Color.fromARGB(71, 120, 120, 128), + darkHighContrastColor: const Color.fromARGB(112, 120, 120, 128), + elevatedColor: const Color.fromARGB(51, 120, 120, 128), + darkElevatedColor: const Color.fromARGB(91, 120, 120, 128), + highContrastElevatedColor: const Color.fromARGB(71, 120, 120, 128), + darkHighContrastElevatedColor: const Color.fromARGB(112, 120, 120, 128), + ), + secondarySystemFill: CupertinoDynamicColor( + color: const Color.fromARGB(153, 60, 60, 67), + darkColor: const Color.fromARGB(153, 235, 235, 245), + highContrastColor: const Color.fromARGB(173, 60, 60, 67), + darkHighContrastColor: const Color.fromARGB(173, 235, 235, 245), + elevatedColor: const Color.fromARGB(153, 60, 60, 67), + darkElevatedColor: const Color.fromARGB(153, 235, 235, 245), + highContrastElevatedColor: const Color.fromARGB(173, 60, 60, 67), + darkHighContrastElevatedColor: const Color.fromARGB(173, 235, 235, 245), + ), + tertiarySystemFill: CupertinoDynamicColor( + color: const Color.fromARGB(30, 118, 118, 128), + darkColor: const Color.fromARGB(61, 118, 118, 128), + highContrastColor: const Color.fromARGB(51, 118, 118, 128), + darkHighContrastColor: const Color.fromARGB(81, 118, 118, 128), + elevatedColor: const Color.fromARGB(30, 118, 118, 128), + darkElevatedColor: const Color.fromARGB(61, 118, 118, 128), + highContrastElevatedColor: const Color.fromARGB(51, 118, 118, 128), + darkHighContrastElevatedColor: const Color.fromARGB(81, 118, 118, 128), + ), + quaternarySystemFill: CupertinoDynamicColor( + color: const Color.fromARGB(20, 116, 116, 128), + darkColor: const Color.fromARGB(45, 118, 118, 128), + highContrastColor: const Color.fromARGB(40, 116, 116, 128), + darkHighContrastColor: const Color.fromARGB(66, 118, 118, 128), + elevatedColor: const Color.fromARGB(20, 116, 116, 128), + darkElevatedColor: const Color.fromARGB(45, 118, 118, 128), + highContrastElevatedColor: const Color.fromARGB(40, 116, 116, 128), + darkHighContrastElevatedColor: const Color.fromARGB(66, 118, 118, 128), + ), + placeholderText: CupertinoDynamicColor( + color: const Color.fromARGB(76, 60, 60, 67), + darkColor: const Color.fromARGB(76, 235, 235, 245), + highContrastColor: const Color.fromARGB(96, 60, 60, 67), + darkHighContrastColor: const Color.fromARGB(96, 235, 235, 245), + elevatedColor: const Color.fromARGB(76, 60, 60, 67), + darkElevatedColor: const Color.fromARGB(76, 235, 235, 245), + highContrastElevatedColor: const Color.fromARGB(96, 60, 60, 67), + darkHighContrastElevatedColor: const Color.fromARGB(96, 235, 235, 245), + ), + systemBackground: CupertinoDynamicColor( + color: const Color.fromARGB(255, 255, 255, 255), + darkColor: const Color.fromARGB(255, 0, 0, 0), + highContrastColor: const Color.fromARGB(255, 255, 255, 255), + darkHighContrastColor: const Color.fromARGB(255, 0, 0, 0), + elevatedColor: const Color.fromARGB(255, 255, 255, 255), + darkElevatedColor: const Color.fromARGB(255, 28, 28, 30), + highContrastElevatedColor: const Color.fromARGB(255, 255, 255, 255), + darkHighContrastElevatedColor: const Color.fromARGB(255, 36, 36, 38), + ), + secondarySystemBackground: CupertinoDynamicColor( + color: const Color.fromARGB(255, 242, 242, 247), + darkColor: const Color.fromARGB(255, 28, 28, 30), + highContrastColor: const Color.fromARGB(255, 235, 235, 240), + darkHighContrastColor: const Color.fromARGB(255, 36, 36, 38), + elevatedColor: const Color.fromARGB(255, 242, 242, 247), + darkElevatedColor: const Color.fromARGB(255, 44, 44, 46), + highContrastElevatedColor: const Color.fromARGB(255, 235, 235, 240), + darkHighContrastElevatedColor: const Color.fromARGB(255, 54, 54, 56), + ), + tertiarySystemBackground: CupertinoDynamicColor( + color: const Color.fromARGB(255, 255, 255, 255), + darkColor: const Color.fromARGB(255, 44, 44, 46), + highContrastColor: const Color.fromARGB(255, 255, 255, 255), + darkHighContrastColor: const Color.fromARGB(255, 54, 54, 56), + elevatedColor: const Color.fromARGB(255, 255, 255, 255), + darkElevatedColor: const Color.fromARGB(255, 58, 58, 60), + highContrastElevatedColor: const Color.fromARGB(255, 255, 255, 255), + darkHighContrastElevatedColor: const Color.fromARGB(255, 68, 68, 70), + ), + systemGroupedBackground: CupertinoDynamicColor( + color: const Color.fromARGB(255, 242, 242, 247), + darkColor: const Color.fromARGB(255, 0, 0, 0), + highContrastColor: const Color.fromARGB(255, 235, 235, 240), + darkHighContrastColor: const Color.fromARGB(255, 0, 0, 0), + elevatedColor: const Color.fromARGB(255, 242, 242, 247), + darkElevatedColor: const Color.fromARGB(255, 28, 28, 30), + highContrastElevatedColor: const Color.fromARGB(255, 235, 235, 240), + darkHighContrastElevatedColor: const Color.fromARGB(255, 36, 36, 38), + ), + secondarySystemGroupedBackground: CupertinoDynamicColor( + color: const Color.fromARGB(255, 242, 242, 247), + darkColor: const Color.fromARGB(255, 0, 0, 0), + highContrastColor: const Color.fromARGB(255, 235, 235, 240), + darkHighContrastColor: const Color.fromARGB(255, 0, 0, 0), + elevatedColor: const Color.fromARGB(255, 242, 242, 247), + darkElevatedColor: const Color.fromARGB(255, 28, 28, 30), + highContrastElevatedColor: const Color.fromARGB(255, 235, 235, 240), + darkHighContrastElevatedColor: const Color.fromARGB(255, 36, 36, 38), + ), + tertiarySystemGroupedBackground: CupertinoDynamicColor( + color: const Color.fromARGB(255, 242, 242, 247), + darkColor: const Color.fromARGB(255, 44, 44, 46), + highContrastColor: const Color.fromARGB(255, 235, 235, 240), + darkHighContrastColor: const Color.fromARGB(255, 54, 54, 56), + elevatedColor: const Color.fromARGB(255, 242, 242, 247), + darkElevatedColor: const Color.fromARGB(255, 58, 58, 60), + highContrastElevatedColor: const Color.fromARGB(255, 235, 235, 240), + darkHighContrastElevatedColor: const Color.fromARGB(255, 68, 68, 70), + ), + separator: CupertinoDynamicColor( + color: const Color.fromARGB(73, 60, 60, 67), + darkColor: const Color.fromARGB(153, 84, 84, 88), + highContrastColor: const Color.fromARGB(94, 60, 60, 67), + darkHighContrastColor: const Color.fromARGB(173, 84, 84, 88), + elevatedColor: const Color.fromARGB(73, 60, 60, 67), + darkElevatedColor: const Color.fromARGB(153, 84, 84, 88), + highContrastElevatedColor: const Color.fromARGB(94, 60, 60, 67), + darkHighContrastElevatedColor: const Color.fromARGB(173, 84, 84, 88), + ), + opaqueSeparator: CupertinoDynamicColor( + color: const Color.fromARGB(255, 198, 198, 200), + darkColor: const Color.fromARGB(255, 56, 56, 58), + highContrastColor: const Color.fromARGB(255, 198, 198, 200), + darkHighContrastColor: const Color.fromARGB(255, 56, 56, 58), + elevatedColor: const Color.fromARGB(255, 198, 198, 200), + darkElevatedColor: const Color.fromARGB(255, 56, 56, 58), + highContrastElevatedColor: const Color.fromARGB(255, 198, 198, 200), + darkHighContrastElevatedColor: const Color.fromARGB(255, 56, 56, 58), + ), + link: CupertinoDynamicColor( + color: const Color.fromARGB(255, 0, 122, 255), + darkColor: const Color.fromARGB(255, 9, 132, 255), + highContrastColor: const Color.fromARGB(255, 0, 122, 255), + darkHighContrastColor: const Color.fromARGB(255, 9, 132, 255), + elevatedColor: const Color.fromARGB(255, 0, 122, 255), + darkElevatedColor: const Color.fromARGB(255, 9, 132, 255), + highContrastElevatedColor: const Color.fromARGB(255, 0, 122, 255), + darkHighContrastElevatedColor: const Color.fromARGB(255, 9, 132, 255), + ), + systemBlue: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 0, 122, 255), + darkColor: const Color.fromARGB(255, 10, 132, 255), + highContrastColor: const Color.fromARGB(255, 0, 64, 221), + darkHighContrastColor: const Color.fromARGB(255, 64, 156, 255), + ), + systemGreen: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 52, 199, 89), + darkColor: const Color.fromARGB(255, 48, 209, 88), + highContrastColor: const Color.fromARGB(255, 36, 138, 61), + darkHighContrastColor: const Color.fromARGB(255, 48, 219, 91), + ), + systemIndigo: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 88, 86, 214), + darkColor: const Color.fromARGB(255, 94, 92, 230), + highContrastColor: const Color.fromARGB(255, 54, 52, 163), + darkHighContrastColor: const Color.fromARGB(255, 125, 122, 255), + ), + systemOrange: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 255, 149, 0), + darkColor: const Color.fromARGB(255, 255, 159, 10), + highContrastColor: const Color.fromARGB(255, 201, 52, 0), + darkHighContrastColor: const Color.fromARGB(255, 255, 179, 64), + ), + systemPink: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 255, 45, 85), + darkColor: const Color.fromARGB(255, 255, 55, 95), + highContrastColor: const Color.fromARGB(255, 211, 15, 69), + darkHighContrastColor: const Color.fromARGB(255, 255, 100, 130), + ), + systemPurple: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 175, 82, 222), + darkColor: const Color.fromARGB(255, 191, 90, 242), + highContrastColor: const Color.fromARGB(255, 137, 68, 171), + darkHighContrastColor: const Color.fromARGB(255, 218, 143, 255), + ), + systemRed: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 255, 59, 48), + darkColor: const Color.fromARGB(255, 255, 69, 58), + highContrastColor: const Color.fromARGB(255, 215, 0, 21), + darkHighContrastColor: const Color.fromARGB(255, 255, 105, 97), + ), + systemTeal: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 90, 200, 250), + darkColor: const Color.fromARGB(255, 100, 210, 255), + highContrastColor: const Color.fromARGB(255, 0, 113, 164), + darkHighContrastColor: const Color.fromARGB(255, 112, 215, 255), + ), + systemYellow: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 255, 204, 0), + darkColor: const Color.fromARGB(255, 255, 214, 10), + highContrastColor: const Color.fromARGB(255, 160, 90, 0), + darkHighContrastColor: const Color.fromARGB(255, 255, 212, 38), + ), + systemGray: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 142, 142, 147), + darkColor: const Color.fromARGB(255, 142, 142, 147), + highContrastColor: const Color.fromARGB(255, 108, 108, 112), + darkHighContrastColor: const Color.fromARGB(255, 174, 174, 178), + ), + systemGray2: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 174, 174, 178), + darkColor: const Color.fromARGB(255, 99, 99, 102), + highContrastColor: const Color.fromARGB(255, 142, 142, 147), + darkHighContrastColor: const Color.fromARGB(255, 124, 124, 128), + ), + systemGray3: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 199, 199, 204), + darkColor: const Color.fromARGB(255, 72, 72, 74), + highContrastColor: const Color.fromARGB(255, 174, 174, 178), + darkHighContrastColor: const Color.fromARGB(255, 84, 84, 86), + ), + systemGray4: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 209, 209, 214), + darkColor: const Color.fromARGB(255, 58, 58, 60), + highContrastColor: const Color.fromARGB(255, 188, 188, 192), + darkHighContrastColor: const Color.fromARGB(255, 68, 68, 70), + ), + systemGray5: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 229, 229, 234), + darkColor: const Color.fromARGB(255, 44, 44, 46), + highContrastColor: const Color.fromARGB(255, 216, 216, 220), + darkHighContrastColor: const Color.fromARGB(255, 54, 54, 56), + ), + systemGray6: CupertinoDynamicColor.withBrightnessAndContrast( + color: const Color.fromARGB(255, 242, 242, 247), + darkColor: const Color.fromARGB(255, 28, 28, 30), + highContrastColor: const Color.fromARGB(255, 235, 235, 240), + darkHighContrastColor: const Color.fromARGB(255, 36, 36, 38), + ), + ); + } +} diff --git a/packages/flutter/lib/src/cupertino/interface_level.dart b/packages/flutter/lib/src/cupertino/interface_level.dart new file mode 100644 index 0000000000..74ca5e5270 --- /dev/null +++ b/packages/flutter/lib/src/cupertino/interface_level.dart @@ -0,0 +1,76 @@ +// 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 '../widgets/framework.dart'; + +/// Indicates the visual level for a piece of content. Equivalent to `UIUserInterfaceLevel` +/// from `UIKit`. +/// +/// See also: +/// +/// * `UIUserInterfaceLevel`, the UIKit equivalent: https://developer.apple.com/documentation/uikit/uiuserinterfacelevel. +enum CupertinoUserInterfaceLevelData { + /// The level for your window's main content. + base, + + /// The level for content visually above [base]. + elevated, +} + +/// Establishes a subtree in which [CupertinoUserInterfaceLevel.of] resolves to +/// the given data. +/// +/// Querying the current elevation status using [CupertinoUserInterfaceLevel.of] +/// will cause your widget to rebuild automatically whenever the [CupertinoUserInterfaceLevelData] +/// changes. +/// +/// If no [CupertinoUserInterfaceLevel] is in scope then the [CupertinoUserInterfaceLevel.of] +/// method will throw an exception, unless the `nullOk` argument is set to true, +/// in which case it returns null. +/// +/// See also: +/// +/// * [CupertinoUserInterfaceLevelData], specifies the visual level for the content +/// in the subtree [CupertinoUserInterfaceLevel] established. +class CupertinoUserInterfaceLevel extends InheritedWidget { + /// Creates a [CupertinoUserInterfaceLevel] to change descendant Cupertino widget's + /// visual level. + const CupertinoUserInterfaceLevel({ + Key key, + @required CupertinoUserInterfaceLevelData data, + Widget child, + }) : assert(data != null), + _data = data, + super(key: key, child: child); + + final CupertinoUserInterfaceLevelData _data; + + @override + bool updateShouldNotify(CupertinoUserInterfaceLevel oldWidget) => oldWidget._data != _data; + + /// The data from the closest instance of this class that encloses the given + /// context. + /// + /// You can use this function to query the user interface elevation level within + /// the given [BuildContext]. When that information changes, your widget will + /// be scheduled to be rebuilt, keeping your widget up-to-date. + static CupertinoUserInterfaceLevelData of(BuildContext context, { bool nullOk = false }) { + assert(context != null); + assert(nullOk != null); + final CupertinoUserInterfaceLevel query = context.inheritFromWidgetOfExactType(CupertinoUserInterfaceLevel); + if (query != null) + return query._data; + if (nullOk) + return null; + throw FlutterError( + 'CupertinoUserInterfaceLevel.of() called with a context that does not contain a CupertinoUserInterfaceLevel.\n' + 'No CupertinoUserInterfaceLevel ancestor could be found starting from the context that was passed ' + 'to CupertinoUserInterfaceLevel.of(). This can happen because you do not have a WidgetsApp or ' + 'MaterialApp widget (those widgets introduce a CupertinoUserInterfaceLevel), or it can happen ' + 'if the context you use comes from a widget above those widgets.\n' + 'The context used was:\n' + ' $context' + ); + } +} diff --git a/packages/flutter/lib/src/cupertino/slider.dart b/packages/flutter/lib/src/cupertino/slider.dart index 219551d0a8..4c4363be06 100644 --- a/packages/flutter/lib/src/cupertino/slider.dart +++ b/packages/flutter/lib/src/cupertino/slider.dart @@ -9,6 +9,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; +import 'colors.dart'; import 'theme.dart'; import 'thumb_painter.dart'; @@ -228,7 +229,10 @@ class _CupertinoSliderState extends State with TickerProviderSt return _CupertinoSliderRenderObjectWidget( value: (widget.value - widget.min) / (widget.max - widget.min), divisions: widget.divisions, - activeColor: widget.activeColor ?? CupertinoTheme.of(context).primaryColor, + activeColor: CupertinoDynamicColor.resolve( + widget.activeColor ?? CupertinoTheme.of(context).primaryColor, + context + ), onChanged: widget.onChanged != null ? _handleChanged : null, onChangeStart: widget.onChangeStart != null ? _handleDragStart : null, onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null, @@ -257,12 +261,14 @@ class _CupertinoSliderRenderObjectWidget extends LeafRenderObjectWidget { final ValueChanged onChangeEnd; final TickerProvider vsync; + @override _RenderCupertinoSlider createRenderObject(BuildContext context) { return _RenderCupertinoSlider( value: value, divisions: divisions, activeColor: activeColor, + trackColor: CupertinoDynamicColor.resolve(CupertinoSystemColors.of(context).systemFill, context), onChanged: onChanged, onChangeStart: onChangeStart, onChangeEnd: onChangeEnd, @@ -277,6 +283,7 @@ class _CupertinoSliderRenderObjectWidget extends LeafRenderObjectWidget { ..value = value ..divisions = divisions ..activeColor = activeColor + ..trackColor = CupertinoDynamicColor.resolve(CupertinoSystemColors.of(context).systemFill, context) ..onChanged = onChanged ..onChangeStart = onChangeStart ..onChangeEnd = onChangeEnd @@ -287,7 +294,6 @@ class _CupertinoSliderRenderObjectWidget extends LeafRenderObjectWidget { } const double _kPadding = 8.0; -const Color _kTrackColor = Color(0xFFB5B5B5); const double _kSliderHeight = 2.0 * (CupertinoThumbPainter.radius + _kPadding); const double _kSliderWidth = 176.0; // Matches Material Design slider. const Duration _kDiscreteTransitionDuration = Duration(milliseconds: 500); @@ -299,6 +305,7 @@ class _RenderCupertinoSlider extends RenderConstrainedBox { @required double value, int divisions, Color activeColor, + Color trackColor, ValueChanged onChanged, this.onChangeStart, this.onChangeEnd, @@ -309,6 +316,7 @@ class _RenderCupertinoSlider extends RenderConstrainedBox { _value = value, _divisions = divisions, _activeColor = activeColor, + _trackColor = trackColor, _onChanged = onChanged, _textDirection = textDirection, super(additionalConstraints: const BoxConstraints.tightFor(width: _kSliderWidth, height: _kSliderHeight)) { @@ -355,6 +363,15 @@ class _RenderCupertinoSlider extends RenderConstrainedBox { markNeedsPaint(); } + Color get trackColor => _trackColor; + Color _trackColor; + set trackColor(Color value) { + if (value == _trackColor) + return; + _trackColor = value; + markNeedsPaint(); + } + ValueChanged get onChanged => _onChanged; ValueChanged _onChanged; set onChanged(ValueChanged value) { @@ -468,11 +485,11 @@ class _RenderCupertinoSlider extends RenderConstrainedBox { case TextDirection.rtl: visualPosition = 1.0 - _position.value; leftColor = _activeColor; - rightColor = _kTrackColor; + rightColor = trackColor; break; case TextDirection.ltr: visualPosition = _position.value; - leftColor = _kTrackColor; + leftColor = trackColor; rightColor = _activeColor; break; } diff --git a/packages/flutter/lib/src/cupertino/text_theme.dart b/packages/flutter/lib/src/cupertino/text_theme.dart index ba5a6b8f9b..7c24f3f739 100644 --- a/packages/flutter/lib/src/cupertino/text_theme.dart +++ b/packages/flutter/lib/src/cupertino/text_theme.dart @@ -213,6 +213,32 @@ class CupertinoTextThemeData extends Diagnosticable { (_isLight ? _kDefaultDateTimePickerLightTextStyle : _kDefaultDateTimePickerDarkTextStyle); } + /// Returns a copy of the current [CupertinoTextThemeData] with all the colors + /// resolved against the given [BuildContext]. + CupertinoTextThemeData resolveFrom(BuildContext context, { bool nullOk = false }) { + Color convertColor(Color color) => color == null ? null : 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), + ); + } + /// Returns a copy of the current [CupertinoTextThemeData] instance with /// specified overrides. CupertinoTextThemeData copyWith({ diff --git a/packages/flutter/lib/src/cupertino/theme.dart b/packages/flutter/lib/src/cupertino/theme.dart index 08c9740e64..50f55ecfea 100644 --- a/packages/flutter/lib/src/cupertino/theme.dart +++ b/packages/flutter/lib/src/cupertino/theme.dart @@ -54,7 +54,20 @@ class CupertinoTheme extends StatelessWidget { /// exist in the ancestry tree. static CupertinoThemeData of(BuildContext context) { final _InheritedCupertinoTheme inheritedTheme = context.inheritFromWidgetOfExactType(_InheritedCupertinoTheme); - return inheritedTheme?.theme?.data ?? const CupertinoThemeData(); + return (inheritedTheme?.theme?.data ?? const CupertinoThemeData()).resolveFrom(context, nullOk: true); + } + + /// Retrieve 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. + /// + /// Throws an exception if no such [CupertinoTheme] or [MediaQuery] widgets exist + /// in the ancestry tree, unless [nullOk] is set to true. + static Brightness brightnessOf(BuildContext context, { bool nullOk = false }) { + final _InheritedCupertinoTheme inheritedTheme = context.inheritFromWidgetOfExactType(_InheritedCupertinoTheme); + return inheritedTheme?.theme?.data?._brightness ?? MediaQuery.of(context, nullOk: nullOk)?.platformBrightness; } /// The widget below this widget in the tree. @@ -229,6 +242,23 @@ class CupertinoThemeData extends Diagnosticable { ); } + /// Return a new `CupertinoThemeData` whose colors are from this `CupertinoThemeData`, + /// but resolved aginst the given [BuildContext]. + /// + /// It will be called in [CupertinoTheme.of]. + @protected + CupertinoThemeData resolveFrom(BuildContext context, { bool nullOk = false }) { + Color convertColor(Color color) => color == null ? null : 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), + ); + } + /// Create a copy of [CupertinoThemeData] with specified attributes overridden. /// /// Only the current instance's specified attributes are copied instead of @@ -296,4 +326,39 @@ class _NoDefaultCupertinoThemeData extends CupertinoThemeData { final Color barBackgroundColor; @override final Color scaffoldBackgroundColor; + + @override + _NoDefaultCupertinoThemeData resolveFrom(BuildContext context, { bool nullOk = false }) { + Color convertColor(Color color) => color == null + ? null + : CupertinoDynamicColor.resolve(color, context, nullOk: nullOk); + + return _NoDefaultCupertinoThemeData( + brightness, + convertColor(primaryColor), + convertColor(primaryContrastingColor), + textTheme?.resolveFrom(context, nullOk: nullOk), + convertColor(barBackgroundColor), + convertColor(scaffoldBackgroundColor), + ); + } + + @override + CupertinoThemeData copyWith({ + Brightness brightness, + Color primaryColor, + Color primaryContrastingColor, + CupertinoTextThemeData textTheme, + Color barBackgroundColor , + Color scaffoldBackgroundColor + }) { + return _NoDefaultCupertinoThemeData( + brightness ?? this.brightness, + primaryColor ?? this.primaryColor, + primaryContrastingColor ?? this.primaryContrastingColor, + textTheme ?? this.textTheme, + barBackgroundColor ?? this.barBackgroundColor, + scaffoldBackgroundColor ?? this.scaffoldBackgroundColor, + ); + } } diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index 820b2d47b7..75f559c742 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -1416,34 +1416,43 @@ class MaterialBasedCupertinoThemeData extends CupertinoThemeData { /// /// The [materialTheme] parameter must not be null. MaterialBasedCupertinoThemeData({ - @required ThemeData materialTheme, - }) : assert(materialTheme != null), - _materialTheme = materialTheme, - // Pass all values to the superclass so Material-agnostic properties - // like barBackgroundColor can still behave like a normal - // CupertinoThemeData. - super.raw( - materialTheme.cupertinoOverrideTheme?.brightness, - materialTheme.cupertinoOverrideTheme?.primaryColor, - materialTheme.cupertinoOverrideTheme?.primaryContrastingColor, - materialTheme.cupertinoOverrideTheme?.textTheme, - materialTheme.cupertinoOverrideTheme?.barBackgroundColor, - materialTheme.cupertinoOverrideTheme?.scaffoldBackgroundColor, - ); + @required ThemeData materialTheme, + }) : this._( + materialTheme, + (materialTheme.cupertinoOverrideTheme ?? const CupertinoThemeData()).noDefault(), + ); + + MaterialBasedCupertinoThemeData._( + this._materialTheme, + this._cupertinoOverrideTheme, + ) : assert(_materialTheme != null), + assert(_cupertinoOverrideTheme != null), + // Pass all values to the superclass so Material-agnostic properties + // like barBackgroundColor can still behave like a normal + // CupertinoThemeData. + super.raw( + _cupertinoOverrideTheme.brightness, + _cupertinoOverrideTheme.primaryColor, + _cupertinoOverrideTheme.primaryContrastingColor, + _cupertinoOverrideTheme.textTheme, + _cupertinoOverrideTheme.barBackgroundColor, + _cupertinoOverrideTheme.scaffoldBackgroundColor, + ); final ThemeData _materialTheme; + final CupertinoThemeData _cupertinoOverrideTheme; @override - Brightness get brightness => _materialTheme.cupertinoOverrideTheme?.brightness ?? _materialTheme.brightness; + Brightness get brightness => _cupertinoOverrideTheme.brightness ?? _materialTheme.brightness; @override - Color get primaryColor => _materialTheme.cupertinoOverrideTheme?.primaryColor ?? _materialTheme.colorScheme.primary; + Color get primaryColor => _cupertinoOverrideTheme.primaryColor ?? _materialTheme.colorScheme.primary; @override - Color get primaryContrastingColor => _materialTheme.cupertinoOverrideTheme?.primaryContrastingColor ?? _materialTheme.colorScheme.onPrimary; + Color get primaryContrastingColor => _cupertinoOverrideTheme.primaryContrastingColor ?? _materialTheme.colorScheme.onPrimary; @override - Color get scaffoldBackgroundColor => _materialTheme.cupertinoOverrideTheme?.scaffoldBackgroundColor ?? _materialTheme.scaffoldBackgroundColor; + Color get scaffoldBackgroundColor => _cupertinoOverrideTheme.scaffoldBackgroundColor ?? _materialTheme.scaffoldBackgroundColor; /// Copies the [ThemeData]'s `cupertinoOverrideTheme`. /// @@ -1457,7 +1466,7 @@ class MaterialBasedCupertinoThemeData extends CupertinoThemeData { /// new Material [Theme] and use `copyWith` on the Material [ThemeData] /// instead. @override - CupertinoThemeData copyWith({ + MaterialBasedCupertinoThemeData copyWith({ Brightness brightness, Color primaryColor, Color primaryContrastingColor, @@ -1465,20 +1474,26 @@ class MaterialBasedCupertinoThemeData extends CupertinoThemeData { Color barBackgroundColor, Color scaffoldBackgroundColor, }) { - return _materialTheme.cupertinoOverrideTheme?.copyWith( - brightness: brightness, - primaryColor: primaryColor, - primaryContrastingColor: primaryContrastingColor, - textTheme: textTheme, - barBackgroundColor: barBackgroundColor, - scaffoldBackgroundColor: scaffoldBackgroundColor, - ) ?? CupertinoThemeData( - brightness: brightness, - primaryColor: primaryColor, - primaryContrastingColor: primaryContrastingColor, - textTheme: textTheme, - barBackgroundColor: barBackgroundColor, - scaffoldBackgroundColor: scaffoldBackgroundColor, + return MaterialBasedCupertinoThemeData._( + _materialTheme, + _cupertinoOverrideTheme.copyWith( + brightness: brightness, + primaryColor: primaryColor, + primaryContrastingColor: primaryContrastingColor, + textTheme: textTheme, + barBackgroundColor: barBackgroundColor, + scaffoldBackgroundColor: scaffoldBackgroundColor, + ), + ); + } + + @override + CupertinoThemeData resolveFrom(BuildContext context, { bool nullOk = false }) { + // Only the cupertino override theme part will be resolved. + // If the color comes from the material theme it's not resolved. + return MaterialBasedCupertinoThemeData._( + _materialTheme, + _cupertinoOverrideTheme.resolveFrom(context, nullOk: nullOk), ); } } diff --git a/packages/flutter/lib/src/widgets/media_query.dart b/packages/flutter/lib/src/widgets/media_query.dart index eaadef92d6..f0639f4411 100644 --- a/packages/flutter/lib/src/widgets/media_query.dart +++ b/packages/flutter/lib/src/widgets/media_query.dart @@ -98,6 +98,7 @@ class MediaQueryData { this.alwaysUse24HourFormat = false, this.accessibleNavigation = false, this.invertColors = false, + this.highContrast = false, this.disableAnimations = false, this.boldText = false, }); @@ -121,6 +122,7 @@ class MediaQueryData { invertColors = window.accessibilityFeatures.invertColors, disableAnimations = window.accessibilityFeatures.disableAnimations, boldText = window.accessibilityFeatures.boldText, + highContrast = false, alwaysUse24HourFormat = window.alwaysUse24HourFormat; /// The size of the media in logical pixels (e.g, the size of the screen). @@ -259,6 +261,13 @@ class MediaQueryData { /// * [Window.AccessibilityFeatures], where the setting originates. final bool invertColors; + /// Whether the user requested a high contrast between foreground and background + /// content on iOS, via Settings -> Accessibility -> Increase Contrast. + /// + /// This flag is currently only updated on iOS devices that are running iOS 13 + /// or above. + final bool highContrast; + /// Whether the platform is requesting that animations be disabled or reduced /// as much as possible. /// @@ -293,6 +302,7 @@ class MediaQueryData { EdgeInsets viewInsets, double physicalDepth, bool alwaysUse24HourFormat, + bool highContrast, bool disableAnimations, bool invertColors, bool accessibleNavigation, @@ -309,6 +319,7 @@ class MediaQueryData { physicalDepth: physicalDepth ?? this.physicalDepth, alwaysUse24HourFormat: alwaysUse24HourFormat ?? this.alwaysUse24HourFormat, invertColors: invertColors ?? this.invertColors, + highContrast: highContrast ?? this.highContrast, disableAnimations: disableAnimations ?? this.disableAnimations, accessibleNavigation: accessibleNavigation ?? this.accessibleNavigation, boldText: boldText ?? this.boldText, @@ -357,6 +368,7 @@ class MediaQueryData { ), viewInsets: viewInsets, alwaysUse24HourFormat: alwaysUse24HourFormat, + highContrast: highContrast, disableAnimations: disableAnimations, invertColors: invertColors, accessibleNavigation: accessibleNavigation, @@ -404,6 +416,7 @@ class MediaQueryData { bottom: removeBottom ? 0.0 : null, ), alwaysUse24HourFormat: alwaysUse24HourFormat, + highContrast: highContrast, disableAnimations: disableAnimations, invertColors: invertColors, accessibleNavigation: accessibleNavigation, @@ -451,6 +464,7 @@ class MediaQueryData { bottom: removeBottom ? 0.0 : null, ), alwaysUse24HourFormat: alwaysUse24HourFormat, + highContrast: highContrast, disableAnimations: disableAnimations, invertColors: invertColors, accessibleNavigation: accessibleNavigation, @@ -472,6 +486,7 @@ class MediaQueryData { && typedOther.viewInsets == viewInsets && typedOther.physicalDepth == physicalDepth && typedOther.alwaysUse24HourFormat == alwaysUse24HourFormat + && typedOther.highContrast == highContrast && typedOther.disableAnimations == disableAnimations && typedOther.invertColors == invertColors && typedOther.accessibleNavigation == accessibleNavigation @@ -490,6 +505,7 @@ class MediaQueryData { viewInsets, physicalDepth, alwaysUse24HourFormat, + highContrast, disableAnimations, invertColors, accessibleNavigation, @@ -510,6 +526,7 @@ class MediaQueryData { 'physicalDepth: $physicalDepth, ' 'alwaysUse24HourFormat: $alwaysUse24HourFormat, ' 'accessibleNavigation: $accessibleNavigation, ' + 'highContrast: $highContrast,' 'disableAnimations: $disableAnimations, ' 'invertColors: $invertColors, ' 'boldText: $boldText' diff --git a/packages/flutter/test/cupertino/colors_test.dart b/packages/flutter/test/cupertino/colors_test.dart new file mode 100644 index 0000000000..6eed485ed8 --- /dev/null +++ b/packages/flutter/test/cupertino/colors_test.dart @@ -0,0 +1,748 @@ +// 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/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/rendering.dart'; + +import '../rendering/mock_canvas.dart'; + +class DependentWidget extends StatelessWidget { + const DependentWidget({ + Key key, + this.color + }) : super(key: key); + + final Color color; + + @override + Widget build(BuildContext context) { + final Color resolved = CupertinoDynamicColor.resolve(color, context); + return DecoratedBox( + decoration: BoxDecoration(color: resolved), + child: const SizedBox.expand(), + ); + } +} + +const Color color0 = Color(0xFF000000); +const Color color1 = Color(0xFF000001); +const Color color2 = Color(0xFF000002); +const Color color3 = Color(0xFF000003); +const Color color4 = Color(0xFF000004); +const Color color5 = Color(0xFF000005); +const Color color6 = Color(0xFF000006); +const Color color7 = Color(0xFF000007); + +// A color that depends on color vibrancy, accessibility contrast, as well as user +// interface elevation. +final CupertinoDynamicColor dynamicColor = CupertinoDynamicColor( + color: color0, + darkColor: color1, + elevatedColor: color2, + highContrastColor: color3, + darkElevatedColor: color4, + darkHighContrastColor: color5, + highContrastElevatedColor: color6, + darkHighContrastElevatedColor: color7, +); + +// A color that uses [color0] in every circumstance. +final Color notSoDynamicColor1 = CupertinoDynamicColor( + color: color0, + darkColor: color0, + darkHighContrastColor: color0, + darkElevatedColor: color0, + darkHighContrastElevatedColor: color0, + highContrastColor: color0, + highContrastElevatedColor: color0, + elevatedColor: color0, +); + +// A color that uses [color1] for light mode, and [color0] for dark mode. +final Color vibrancyDependentColor1 = CupertinoDynamicColor( + color: color1, + elevatedColor: color1, + highContrastColor: color1, + highContrastElevatedColor: color1, + darkColor: color0, + darkHighContrastColor: color0, + darkElevatedColor: color0, + darkHighContrastElevatedColor: color0, +); + +// A color that uses [color1] for normal contrast mode, and [color0] for high +// contrast mode. +final Color contrastDependentColor1 = CupertinoDynamicColor( + color: color1, + darkColor: color1, + elevatedColor: color1, + darkElevatedColor: color1, + highContrastColor: color0, + darkHighContrastColor: color0, + highContrastElevatedColor: color0, + darkHighContrastElevatedColor: color0, +); + +// A color that uses [color1] for base interface elevation, and [color0] for elevated +// interface elevation. +final Color elevationDependentColor1 = CupertinoDynamicColor( + color: color1, + darkColor: color1, + highContrastColor: color1, + darkHighContrastColor: color1, + elevatedColor: color0, + darkElevatedColor: color0, + highContrastElevatedColor: color0, + darkHighContrastElevatedColor: color0, +); + +void main() { + test('== works as expected', () { + expect(dynamicColor, CupertinoDynamicColor( + color: color0, + darkColor: color1, + elevatedColor: color2, + highContrastColor: color3, + darkElevatedColor: color4, + darkHighContrastColor: color5, + highContrastElevatedColor: color6, + darkHighContrastElevatedColor: color7, + ) + ); + + expect(notSoDynamicColor1, isNot(vibrancyDependentColor1)); + + expect(notSoDynamicColor1, isNot(contrastDependentColor1)); + + expect(vibrancyDependentColor1, isNot(CupertinoDynamicColor( + color: color0, + elevatedColor: color0, + highContrastColor: color0, + highContrastElevatedColor: color0, + darkColor: color0, + darkHighContrastColor: color0, + darkElevatedColor: color0, + darkHighContrastElevatedColor: color0, + ))); + }); + + test('CupertinoDynamicColor.toString() works', () { + expect( + dynamicColor.toString(), + 'CupertinoDynamicColor(*color = Color(0xff000000)*, ' + 'darkColor = Color(0xff000001), ' + 'highContrastColor = Color(0xff000003), ' + 'darkHighContrastColor = Color(0xff000005), ' + 'elevatedColor = Color(0xff000002), ' + 'darkElevatedColor = Color(0xff000004), ' + 'highContrastElevatedColor = Color(0xff000006), ' + 'darkHighContrastElevatedColor = Color(0xff000007))' + ); + expect(notSoDynamicColor1.toString(), 'CupertinoDynamicColor(*color = Color(0xff000000)*)'); + expect(vibrancyDependentColor1.toString(), 'CupertinoDynamicColor(*color = Color(0xff000001)*, darkColor = Color(0xff000000))'); + expect(contrastDependentColor1.toString(), 'CupertinoDynamicColor(*color = Color(0xff000001)*, highContrastColor = Color(0xff000000))'); + expect(elevationDependentColor1.toString(), 'CupertinoDynamicColor(*color = Color(0xff000001)*, elevatedColor = Color(0xff000000))'); + + expect( + CupertinoDynamicColor.withBrightnessAndContrast( + color: color0, + darkColor: color1, + highContrastColor: color2, + darkHighContrastColor: color3, + ).toString(), + 'CupertinoDynamicColor(*color = Color(0xff000000)*, ' + 'darkColor = Color(0xff000001), ' + 'highContrastColor = Color(0xff000002), ' + 'darkHighContrastColor = Color(0xff000003))', + ); + }); + + test('withVibrancy constructor creates colors that may depend on vibrancy', () { + expect(vibrancyDependentColor1, CupertinoDynamicColor.withBrightness( + color: color1, + darkColor: color0, + )); + }); + + test('withVibrancyAndContrast constructor creates colors that may depend on contrast and vibrancy', () { + expect(contrastDependentColor1, CupertinoDynamicColor.withBrightnessAndContrast( + color: color1, + darkColor: color1, + highContrastColor: color0, + darkHighContrastColor: color0, + )); + + expect(CupertinoDynamicColor( + color: color0, + darkColor: color1, + highContrastColor: color2, + darkHighContrastColor: color3, + elevatedColor: color0, + darkElevatedColor: color1, + highContrastElevatedColor: color2, + darkHighContrastElevatedColor: color3, + ), + CupertinoDynamicColor.withBrightnessAndContrast( + color: color0, + darkColor: color1, + highContrastColor: color2, + darkHighContrastColor: color3, + )); + }); + + testWidgets('Dynamic colors that are not actually dynamic should not claim dependencies', + (WidgetTester tester) async { + await tester.pumpWidget(DependentWidget(color: notSoDynamicColor1)); + + expect(tester.takeException(), null); + expect(find.byType(DependentWidget), paints..rect(color: color0)); + }); + + testWidgets( + 'Dynamic colors that are only dependent on vibrancy should not claim unnecessary dependencies, ' + 'and its resolved color should change when its dependency changes', + (WidgetTester tester) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.light), + child: DependentWidget(color: vibrancyDependentColor1), + ), + ); + + expect(tester.takeException(), null); + expect(find.byType(DependentWidget), paints..rect(color: color1)); + expect(find.byType(DependentWidget), isNot(paints..rect(color: color0))); + + // Changing color vibrancy works. + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.dark), + child: DependentWidget(color: vibrancyDependentColor1), + ), + ); + + expect(tester.takeException(), null); + expect(find.byType(DependentWidget), paints..rect(color: color0)); + expect(find.byType(DependentWidget), isNot(paints..rect(color: color1))); + + // CupertinoTheme should take percedence over MediaQuery. + await tester.pumpWidget( + CupertinoTheme( + data: const CupertinoThemeData(brightness: Brightness.light), + child: MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.dark), + child: DependentWidget(color: vibrancyDependentColor1), + ), + ), + ); + + expect(tester.takeException(), null); + expect(find.byType(DependentWidget), paints..rect(color: color1)); + expect(find.byType(DependentWidget), isNot(paints..rect(color: color0))); + }); + + testWidgets( + 'Dynamic colors that are only dependent on accessibility contrast should not claim unnecessary dependencies, ' + 'and its resolved color should change when its dependency changes', + (WidgetTester tester) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(highContrast: false), + child: DependentWidget(color: contrastDependentColor1), + ), + ); + + expect(tester.takeException(), null); + expect(find.byType(DependentWidget), paints..rect(color: color1)); + expect(find.byType(DependentWidget), isNot(paints..rect(color: color0))); + + // Changing accessibility contrast works. + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(highContrast: true), + child: DependentWidget(color: contrastDependentColor1), + ), + ); + + expect(tester.takeException(), null); + expect(find.byType(DependentWidget), paints..rect(color: color0)); + expect(find.byType(DependentWidget), isNot(paints..rect(color: color1))); + + // Asserts when the required dependency is missing. + await tester.pumpWidget(DependentWidget(color: contrastDependentColor1)); + expect(tester.takeException()?.toString(), contains('does not contain a MediaQuery')); + }); + + testWidgets( + 'Dynamic colors that are only dependent on elevation level should not claim unnecessary dependencies, ' + 'and its resolved color should change when its dependency changes', + (WidgetTester tester) async { + await tester.pumpWidget( + CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.base, + child: DependentWidget(color: elevationDependentColor1), + ), + ); + + expect(tester.takeException(), null); + expect(find.byType(DependentWidget), paints..rect(color: color1)); + expect(find.byType(DependentWidget), isNot(paints..rect(color: color0))); + + // Changing UI elevation works. + await tester.pumpWidget( + CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.elevated, + child: DependentWidget(color: elevationDependentColor1), + ), + ); + + expect(tester.takeException(), null); + expect(find.byType(DependentWidget), paints..rect(color: color0)); + expect(find.byType(DependentWidget), isNot(paints..rect(color: color1))); + + // Asserts when the required dependency is missing. + await tester.pumpWidget(DependentWidget(color: elevationDependentColor1)); + expect(tester.takeException()?.toString(), contains('does not contain a CupertinoUserInterfaceLevel')); + }); + + testWidgets('Dynamic color with all 3 depedencies works', (WidgetTester tester) async { + final Color dynamicRainbowColor1 = CupertinoDynamicColor( + color: color0, + darkColor: color1, + highContrastColor: color2, + darkHighContrastColor: color3, + darkElevatedColor: color4, + highContrastElevatedColor: color5, + darkHighContrastElevatedColor: color6, + elevatedColor: color7, + ); + + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.light, highContrast: false), + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.base, + child: DependentWidget(color: dynamicRainbowColor1), + ), + ), + ); + expect(find.byType(DependentWidget), paints..rect(color: color0)); + + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.dark, highContrast: false), + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.base, + child: DependentWidget(color: dynamicRainbowColor1), + ), + ), + ); + expect(find.byType(DependentWidget), paints..rect(color: color1)); + + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.light, highContrast: true), + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.base, + child: DependentWidget(color: dynamicRainbowColor1), + ), + ), + ); + expect(find.byType(DependentWidget), paints..rect(color: color2)); + + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.dark, highContrast: true), + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.base, + child: DependentWidget(color: dynamicRainbowColor1), + ), + ), + ); + expect(find.byType(DependentWidget), paints..rect(color: color3)); + + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.dark, highContrast: false), + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.elevated, + child: DependentWidget(color: dynamicRainbowColor1), + ), + ), + ); + expect(find.byType(DependentWidget), paints..rect(color: color4)); + + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.light, highContrast: true), + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.elevated, + child: DependentWidget(color: dynamicRainbowColor1), + ), + ), + ); + expect(find.byType(DependentWidget), paints..rect(color: color5)); + + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.dark, highContrast: true), + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.elevated, + child: DependentWidget(color: dynamicRainbowColor1), + ), + ), + ); + expect(find.byType(DependentWidget), paints..rect(color: color6)); + + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.light, highContrast: false), + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.elevated, + child: DependentWidget(color: dynamicRainbowColor1), + ), + ), + ); + expect(find.byType(DependentWidget), paints..rect(color: color7)); + }); + + group('CupertinoSystemColors widget', () { + CupertinoSystemColorsData colors; + setUp(() { colors = null; }); + + Widget systemColorGetter(BuildContext context) { + colors = CupertinoSystemColors.of(context); + return const Placeholder(); + } + + testWidgets('exists in CupertinoApp', (WidgetTester tester) async { + await tester.pumpWidget(CupertinoApp(home: Builder(builder: systemColorGetter))); + expect(colors.systemBackground, CupertinoSystemColors.fallbackValues.systemBackground); + }); + + testWidgets('resolves against its own BuildContext', (WidgetTester tester) async { + await tester.pumpWidget( + CupertinoApp( + theme: const CupertinoThemeData(brightness: Brightness.dark), + home: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.elevated, + child: Builder( + builder: (BuildContext context) { + return CupertinoSystemColors( + child: Builder(builder: systemColorGetter), + data: CupertinoSystemColors.of(context).resolveColors(context), + ); + }, + ), + ), + ), + ); + + // In widget tests the OS colors should fallback to `fallbackValues`. + expect(colors.systemBackground, isNot(CupertinoSystemColors.fallbackValues.systemBackground)); + expect(colors.systemBackground.value, CupertinoSystemColors.fallbackValues.systemBackground.darkElevatedColor.value); + + colors = null; + // Changing dependencies works. + await tester.pumpWidget( + CupertinoApp( + theme: const CupertinoThemeData(brightness: Brightness.light), + home: Builder( + builder: (BuildContext context) { + return CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.elevated, + child: CupertinoSystemColors( + child: Builder(builder: systemColorGetter), + data: CupertinoSystemColors.of(context).resolveColors(context), + ), + ); + }, + ), + ), + ); + + expect(colors.systemBackground.value, CupertinoSystemColors.fallbackValues.systemBackground.elevatedColor.value); + }); + }); + + testWidgets('CupertinoDynamicColor used in a CupertinoTheme', (WidgetTester tester) async { + CupertinoDynamicColor color; + await tester.pumpWidget( + CupertinoApp( + theme: CupertinoThemeData( + brightness: Brightness.dark, + primaryColor: dynamicColor, + ), + home: Builder( + builder: (BuildContext context) { + color = CupertinoTheme.of(context).primaryColor; + return const Placeholder(); + } + ), + ), + ); + + expect(color.value, dynamicColor.darkColor.value); + + // Changing dependencies works. + await tester.pumpWidget( + CupertinoApp( + theme: CupertinoThemeData( + brightness: Brightness.light, + primaryColor: dynamicColor, + ), + home: Builder( + builder: (BuildContext context) { + color = CupertinoTheme.of(context).primaryColor; + return const Placeholder(); + } + ), + ), + ); + + expect(color.value, dynamicColor.color.value); + + // Having a dependency below the CupertinoTheme widget works. + await tester.pumpWidget( + CupertinoApp( + theme: CupertinoThemeData(primaryColor: dynamicColor), + home: MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.light, highContrast: false), + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.base, + child: Builder( + builder: (BuildContext context) { + color = CupertinoTheme.of(context).primaryColor; + return const Placeholder(); + } + ), + ), + ), + ), + ); + + expect(color.value, dynamicColor.color.value); + + // Changing dependencies works. + await tester.pumpWidget( + CupertinoApp( + // No brightness is explicitly specified here so it should defer to MediaQuery. + theme: CupertinoThemeData(primaryColor: dynamicColor), + home: MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.dark, highContrast: true), + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.elevated, + child: Builder( + builder: (BuildContext context) { + color = CupertinoTheme.of(context).primaryColor; + return const Placeholder(); + } + ), + ), + ), + ), + ); + + expect(color.value, dynamicColor.darkHighContrastElevatedColor.value); + }); + + group('MaterialApp:', () { + Color color; + setUp(() { color = null; }); + + testWidgets('dynamic color works in cupertino override theme', (WidgetTester tester) async { + final CupertinoDynamicColor Function() typedColor = () => color; + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + cupertinoOverrideTheme: CupertinoThemeData( + brightness: Brightness.dark, + primaryColor: dynamicColor, + ), + ), + home: MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.light, highContrast: false), + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.base, + child: Builder( + builder: (BuildContext context) { + color = CupertinoTheme.of(context).primaryColor; + return const Placeholder(); + } + ), + ), + ), + ), + ); + + // Explicit brightness is respected. + expect(typedColor().value, dynamicColor.darkColor.value); + color = null; + + // Changing dependencies works. + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + cupertinoOverrideTheme: CupertinoThemeData( + brightness: Brightness.dark, + primaryColor: dynamicColor, + ), + ), + home: MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.dark, highContrast: true), + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.elevated, + child: Builder( + builder: (BuildContext context) { + color = CupertinoTheme.of(context).primaryColor; + return const Placeholder(); + } + ), + ), + ), + ), + ); + + expect(typedColor().value, dynamicColor.darkHighContrastElevatedColor.value); + }); + + testWidgets('dynamic color does not work in a material theme', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + // This will create a MaterialBasedCupertinoThemeData with primaryColor set to `dynamicColor`. + theme: ThemeData(colorScheme: ColorScheme.dark(primary: dynamicColor)), + home: MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.dark, highContrast: true), + child: CupertinoUserInterfaceLevel( + data: CupertinoUserInterfaceLevelData.elevated, + child: Builder( + builder: (BuildContext context) { + color = CupertinoTheme.of(context).primaryColor; + return const Placeholder(); + } + ), + ), + ), + ), + ); + + // The color is not resolved. + expect(color, dynamicColor); + expect(color, isNot(dynamicColor.darkHighContrastElevatedColor)); + }); + }); + + group('CupertinoSystemColors', () { + final Color dynamicColor0 = CupertinoDynamicColor.withBrightness( + color: const Color(0x00000000), + darkColor: const Color(0x00000000) + ); + final Color dynamicColor1 = CupertinoDynamicColor.withBrightness( + color: const Color(0x00000001), + darkColor: const Color(0x00000000) + ); + + final CupertinoSystemColorsData system0 = CupertinoSystemColorsData( + label: dynamicColor0, + secondaryLabel: dynamicColor0, + tertiaryLabel: dynamicColor0, + quaternaryLabel: dynamicColor0, + systemFill: dynamicColor0, + secondarySystemFill: dynamicColor0, + tertiarySystemFill: dynamicColor0, + quaternarySystemFill: dynamicColor0, + placeholderText: dynamicColor0, + systemBackground: dynamicColor0, + secondarySystemBackground: dynamicColor0, + tertiarySystemBackground: dynamicColor0, + systemGroupedBackground: dynamicColor0, + secondarySystemGroupedBackground: dynamicColor0, + tertiarySystemGroupedBackground: dynamicColor0, + separator: dynamicColor0, + opaqueSeparator: dynamicColor0, + link: dynamicColor0, + systemBlue: dynamicColor0, + systemGreen: dynamicColor0, + systemIndigo: dynamicColor0, + systemOrange: dynamicColor0, + systemPink: dynamicColor0, + systemPurple: dynamicColor0, + systemRed: dynamicColor0, + systemTeal: dynamicColor0, + systemYellow: dynamicColor0, + systemGray: dynamicColor0, + systemGray2: dynamicColor0, + systemGray3: dynamicColor0, + systemGray4: dynamicColor0, + systemGray5: dynamicColor0, + systemGray6: dynamicColor0, + ); + + test('CupertinoSystemColorsData.== and CupertinoSystemColorsData.copyWith', () { + expect(system0, system0); + expect(system0, system0.copyWith()); + expect(system0, system0.copyWith(link: dynamicColor0)); + final CupertinoSystemColorsData withDifferentLink = system0.copyWith(link: dynamicColor1); + expect(withDifferentLink.link, dynamicColor1); + expect(system0, isNot(withDifferentLink)); + }); + + test('CupertinoSystemColorsData.hashCode', () { + expect(system0.hashCode, system0.hashCode); + expect(system0.hashCode, system0.copyWith().hashCode); + expect(system0.hashCode, system0.copyWith(link: dynamicColor0).hashCode); + expect(system0.hashCode, isNot(system0.copyWith(link: dynamicColor1).hashCode)); + }); + + test('CupertinoSystemColorsData.debugFillProperties', () { + final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); + system0.debugFillProperties(builder); + + expect( + builder.properties + .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) + .map((DiagnosticsNode node) => node.toString()) + .toList(), + [ + 'label: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'secondaryLabel: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'tertiaryLabel: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'quaternaryLabel: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemFill: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'secondarySystemFill: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'tertiarySystemFill: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'quaternarySystemFill: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'placeholderText: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemBackground: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'secondarySystemBackground: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'tertiarySystemBackground: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemGroupedBackground: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'secondarySystemGroupedBackground: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'tertiarySystemGroupedBackground: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'separator: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'opaqueSeparator: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'link: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemBlue: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemGreen: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemIndigo: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemOrange: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemPink: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemPurple: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemRed: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemTeal: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemYellow: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemGray: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemGray2: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemGray3: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemGray4: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemGray5: CupertinoDynamicColor(*color = Color(0x00000000)*)', + 'systemGray6: CupertinoDynamicColor(*color = Color(0x00000000)*)', + ], + ); + }); + }); +} diff --git a/packages/flutter/test/cupertino/slider_test.dart b/packages/flutter/test/cupertino/slider_test.dart index c714356202..27c462e63e 100644 --- a/packages/flutter/test/cupertino/slider_test.dart +++ b/packages/flutter/test/cupertino/slider_test.dart @@ -24,26 +24,28 @@ void main() { final Key sliderKey = UniqueKey(); double value = 0.0; - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Material( - child: Center( - child: CupertinoSlider( - key: sliderKey, - value: value, - onChanged: (double newValue) { - setState(() { - value = newValue; - }); - }, - ), - ), - ); - }, + await tester.pumpWidget( + CupertinoApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Material( + child: Center( + child: CupertinoSlider( + key: sliderKey, + value: value, + onChanged: (double newValue) { + setState(() { value = newValue; }); + }, + ), + ), + ); + }, + ), + ), ), - )); + ); expect(value, equals(0.0)); await tester.tap(find.byKey(sliderKey)); @@ -58,26 +60,28 @@ void main() { final Key sliderKey = UniqueKey(); double value = 0.0; - await tester.pumpWidget(Directionality( - textDirection: TextDirection.rtl, - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Material( - child: Center( - child: CupertinoSlider( - key: sliderKey, - value: value, - onChanged: (double newValue) { - setState(() { - value = newValue; - }); - }, - ), - ), - ); - }, + await tester.pumpWidget( + CupertinoApp( + home: Directionality( + textDirection: TextDirection.rtl, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Material( + child: Center( + child: CupertinoSlider( + key: sliderKey, + value: value, + onChanged: (double newValue) { + setState(() { value = newValue; }); + }, + ), + ), + ); + }, + ), + ), ), - )); + ); expect(value, equals(0.0)); await tester.tap(find.byKey(sliderKey)); @@ -93,29 +97,31 @@ void main() { double value = 0.0; int numberOfTimesOnChangeStartIsCalled = 0; - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Material( - child: Center( - child: CupertinoSlider( - key: sliderKey, - value: value, - onChanged: (double newValue) { - setState(() { - value = newValue; - }); - }, - onChangeStart: (double value) { - numberOfTimesOnChangeStartIsCalled++; - }, - ), - ), - ); - }, + await tester.pumpWidget( + CupertinoApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Material( + child: Center( + child: CupertinoSlider( + key: sliderKey, + value: value, + onChanged: (double newValue) { + setState(() { value = newValue; }); + }, + onChangeStart: (double value) { + numberOfTimesOnChangeStartIsCalled++; + }, + ), + ), + ); + }, + ), + ), ), - )); + ); await _dragSlider(tester, sliderKey); @@ -132,29 +138,31 @@ void main() { double value = 0.0; int numberOfTimesOnChangeEndIsCalled = 0; - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Material( - child: Center( - child: CupertinoSlider( - key: sliderKey, - value: value, - onChanged: (double newValue) { - setState(() { - value = newValue; - }); - }, - onChangeEnd: (double value) { - numberOfTimesOnChangeEndIsCalled++; - }, - ), - ), - ); - }, + await tester.pumpWidget( + CupertinoApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Material( + child: Center( + child: CupertinoSlider( + key: sliderKey, + value: value, + onChanged: (double newValue) { + setState(() { value = newValue; }); + }, + onChangeEnd: (double value) { + numberOfTimesOnChangeEndIsCalled++; + }, + ), + ), + ); + }, + ), + ), ), - )); + ); await _dragSlider(tester, sliderKey); @@ -172,32 +180,34 @@ void main() { double startValue; double endValue; - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Material( - child: Center( - child: CupertinoSlider( - key: sliderKey, - value: value, - onChanged: (double newValue) { - setState(() { - value = newValue; - }); - }, - onChangeStart: (double value) { - startValue = value; - }, - onChangeEnd: (double value) { - endValue = value; - }, - ), - ), - ); - }, + await tester.pumpWidget( + CupertinoApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Material( + child: Center( + child: CupertinoSlider( + key: sliderKey, + value: value, + onChanged: (double newValue) { + setState(() { value = newValue; }); + }, + onChangeStart: (double value) { + startValue = value; + }, + onChangeEnd: (double value) { + endValue = value; + }, + ), + ), + ); + }, + ), + ), ), - )); + ); expect(value, equals(0.0)); @@ -224,36 +234,34 @@ void main() { double startValue; double endValue; - await tester.pumpWidget(Directionality( - textDirection: TextDirection.rtl, - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Material( - child: Center( - child: CupertinoSlider( - key: sliderKey, - value: value, - onChanged: (double newValue) { - setState(() { - value = newValue; - }); - }, - onChangeStart: (double value) { - setState(() { - startValue = value; - }); - }, - onChangeEnd: (double value) { - setState(() { - endValue = value; - }); - }, - ), - ), - ); - }, + await tester.pumpWidget( + CupertinoApp( + home: Directionality( + textDirection: TextDirection.rtl, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Material( + child: Center( + child: CupertinoSlider( + key: sliderKey, + value: value, + onChanged: (double newValue) { + setState(() { value = newValue; }); + }, + onChangeStart: (double value) { + setState(() { startValue = value; }); + }, + onChangeEnd: (double value) { + setState(() { endValue = value; }); + }, + ), + ), + ); + }, + ), + ), ), - )); + ); expect(value, equals(0.0)); @@ -277,13 +285,18 @@ void main() { testWidgets('Slider Semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: CupertinoSlider( - value: 0.5, - onChanged: (double v) { }, + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(), + child: Directionality( + textDirection: TextDirection.ltr, + child: CupertinoSlider( + value: 0.5, + onChanged: (double v) { }, + ), + ), ), - )); + ); expect(semantics, hasSemantics( TestSemantics.root( @@ -296,20 +309,25 @@ void main() { textDirection: TextDirection.ltr, actions: SemanticsAction.decrease.index | SemanticsAction.increase.index, ), - ] + ], ), ignoreRect: true, ignoreTransform: true, )); // Disable slider - await tester.pumpWidget(const Directionality( - textDirection: TextDirection.ltr, - child: CupertinoSlider( - value: 0.5, - onChanged: null, + await tester.pumpWidget( + const MediaQuery( + data: MediaQueryData(), + child: Directionality( + textDirection: TextDirection.ltr, + child: CupertinoSlider( + value: 0.5, + onChanged: null, + ), + ), ), - )); + ); expect(semantics, hasSemantics( TestSemantics.root(), @@ -323,13 +341,17 @@ void main() { testWidgets('Slider Semantics can be updated', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); double value = 0.5; - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: CupertinoSlider( - value: value, - onChanged: (double v) { }, + await tester.pumpWidget( + CupertinoApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: CupertinoSlider( + value: value, + onChanged: (double v) { }, + ), + ), ), - )); + ); expect(tester.getSemantics(find.byType(CupertinoSlider)), matchesSemantics( hasIncreaseAction: true, @@ -341,13 +363,17 @@ void main() { )); value = 0.6; - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: CupertinoSlider( - value: value, - onChanged: (double v) { }, + await tester.pumpWidget( + CupertinoApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: CupertinoSlider( + value: value, + onChanged: (double v) { }, + ), + ), ), - )); + ); expect(tester.getSemantics(find.byType(CupertinoSlider)), matchesSemantics( hasIncreaseAction: true, @@ -413,4 +439,108 @@ void main() { paints..rrect(color: CupertinoColors.activeGreen), ); }); + + testWidgets('Themes can be overridden by dynamic colors', (WidgetTester tester) async { + final CupertinoDynamicColor activeColor = CupertinoDynamicColor( + color: const Color(0x00000001), + darkColor: const Color(0x00000002), + elevatedColor: const Color(0x00000003), + highContrastColor: const Color(0x00000004), + darkElevatedColor: const Color(0x00000005), + darkHighContrastColor: const Color(0x00000006), + highContrastElevatedColor: const Color(0x00000007), + darkHighContrastElevatedColor: const Color(0x00000008), + ); + + Widget withTraits(Brightness brightness, CupertinoUserInterfaceLevelData level, bool highContrast) { + return CupertinoTheme( + data: CupertinoThemeData(brightness: brightness), + child: CupertinoUserInterfaceLevel( + data: level, + child: MediaQuery( + data: MediaQueryData(highContrast: highContrast), + child: Center( + child: CupertinoSlider( + activeColor: activeColor, + onChanged: (double value) { }, + value: 0.5, + ), + ), + ), + ), + ); + } + + await tester.pumpWidget(CupertinoApp(home: withTraits(Brightness.light, CupertinoUserInterfaceLevelData.base, false))); + expect(find.byType(CupertinoSlider), paints..rrect(color: activeColor.color)); + + await tester.pumpWidget(CupertinoApp(home: withTraits(Brightness.dark, CupertinoUserInterfaceLevelData.base, false))); + expect(find.byType(CupertinoSlider), paints..rrect(color: activeColor.darkColor)); + + await tester.pumpWidget(CupertinoApp(home: withTraits(Brightness.dark, CupertinoUserInterfaceLevelData.elevated, false))); + expect(find.byType(CupertinoSlider), paints..rrect(color: activeColor.darkElevatedColor)); + + await tester.pumpWidget(CupertinoApp(home: withTraits(Brightness.dark, CupertinoUserInterfaceLevelData.base, true))); + expect(find.byType(CupertinoSlider), paints..rrect(color: activeColor.darkHighContrastColor)); + + await tester.pumpWidget(CupertinoApp(home: withTraits(Brightness.dark, CupertinoUserInterfaceLevelData.elevated, true))); + expect(find.byType(CupertinoSlider), paints..rrect(color: activeColor.darkHighContrastElevatedColor)); + + await tester.pumpWidget(CupertinoApp(home: withTraits(Brightness.light, CupertinoUserInterfaceLevelData.base, true))); + expect(find.byType(CupertinoSlider), paints..rrect(color: activeColor.highContrastColor)); + + await tester.pumpWidget(CupertinoApp(home: withTraits(Brightness.light, CupertinoUserInterfaceLevelData.elevated, false))); + expect(find.byType(CupertinoSlider), paints..rrect(color: activeColor.elevatedColor)); + + await tester.pumpWidget(CupertinoApp(home: withTraits(Brightness.light, CupertinoUserInterfaceLevelData.elevated, true))); + expect(find.byType(CupertinoSlider), paints..rrect(color: activeColor.highContrastElevatedColor)); + }); + + testWidgets('track color is dynamic', (WidgetTester tester) async { + await tester.pumpWidget( + CupertinoApp( + theme: const CupertinoThemeData(brightness: Brightness.light), + home: Center( + child: CupertinoSlider( + activeColor: CupertinoColors.activeGreen, + onChanged: (double value) { }, + value: 0, + ), + ), + ), + ); + + expect( + find.byType(CupertinoSlider), + paints..rrect(color: CupertinoSystemColors.fallbackValues.systemFill.color), + ); + + expect( + find.byType(CupertinoSlider), + isNot(paints..rrect(color: CupertinoSystemColors.fallbackValues.systemFill.darkColor)), + ); + + await tester.pumpWidget( + CupertinoApp( + theme: const CupertinoThemeData(brightness: Brightness.dark), + home: Center( + child: CupertinoSlider( + activeColor: CupertinoColors.activeGreen, + onChanged: (double value) { }, + value: 0, + ), + ), + ), + ); + + expect( + find.byType(CupertinoSlider), + paints..rrect(color: CupertinoSystemColors.fallbackValues.systemFill.darkColor), + ); + + expect( + find.byType(CupertinoSlider), + isNot(paints..rrect(color: CupertinoSystemColors.fallbackValues.systemFill.color)), + ); + }); }