// Copyright 2014 The Flutter 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/foundation.dart'; import 'package:flutter/widgets.dart'; import 'colors.dart'; import 'icon_theme_data.dart'; import 'text_theme.dart'; export 'package:flutter/foundation.dart' show Brightness; // Values derived from https://developer.apple.com/design/resources/. const _CupertinoThemeDefaults _kDefaultTheme = _CupertinoThemeDefaults( null, CupertinoColors.systemBlue, CupertinoColors.systemBackground, CupertinoDynamicColor.withBrightness( color: Color(0xF0F9F9F9), darkColor: Color(0xF01D1D1D), // Values extracted from navigation bar. For toolbar or tabbar the dark color is 0xF0161616. ), CupertinoColors.systemBackground, false, _CupertinoTextThemeDefaults(CupertinoColors.label, CupertinoColors.inactiveGray), ); /// Applies a visual styling theme to descendant Cupertino widgets. /// /// Affects the color and text styles of Cupertino widgets whose styling /// are not overridden when constructing the respective widgets instances. /// /// Descendant widgets can retrieve the current [CupertinoThemeData] by calling /// [CupertinoTheme.of]. An [InheritedWidget] dependency is created when /// an ancestor [CupertinoThemeData] is retrieved via [CupertinoTheme.of]. /// /// The [CupertinoTheme] widget implies an [IconTheme] widget, whose /// [IconTheme.data] has the same color as [CupertinoThemeData.primaryColor] /// /// See also: /// /// * [CupertinoThemeData], specifies the theme's visual styling. /// * [CupertinoApp], which will automatically add a [CupertinoTheme] based on the /// value of [CupertinoApp.theme]. /// * [Theme], a Material theme which will automatically add a [CupertinoTheme] /// with a [CupertinoThemeData] derived from the Material [ThemeData]. class CupertinoTheme extends StatelessWidget { /// Creates a [CupertinoTheme] to change descendant Cupertino widgets' styling. const CupertinoTheme({ super.key, required this.data, required this.child, }); /// The [CupertinoThemeData] styling for this theme. final CupertinoThemeData data; /// Retrieves the [CupertinoThemeData] from the closest ancestor [CupertinoTheme] /// widget, or a default [CupertinoThemeData] if no [CupertinoTheme] ancestor /// exists. /// /// Resolves all the colors defined in that [CupertinoThemeData] against the /// given [BuildContext] on a best-effort basis. static CupertinoThemeData of(BuildContext context) { final _InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedCupertinoTheme>(); return (inheritedTheme?.theme.data ?? const CupertinoThemeData()).resolveFrom(context); } /// Retrieves the [Brightness] to use for descendant Cupertino widgets, based /// on the value of [CupertinoThemeData.brightness] in the given [context]. /// /// If no [CupertinoTheme] can be found in the given [context], or its `brightness` /// is null, it will fall back to [MediaQueryData.platformBrightness]. /// /// Throws an exception if no valid [CupertinoTheme] or [MediaQuery] widgets /// exist in the ancestry tree. /// /// See also: /// /// * [maybeBrightnessOf], which returns null if no valid [CupertinoTheme] or /// [MediaQuery] exists, instead of throwing. /// * [CupertinoThemeData.brightness], the property takes precedence over /// [MediaQueryData.platformBrightness] for descendant Cupertino widgets. static Brightness brightnessOf(BuildContext context) { final _InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedCupertinoTheme>(); return inheritedTheme?.theme.data.brightness ?? MediaQuery.platformBrightnessOf(context); } /// Retrieves the [Brightness] to use for descendant Cupertino widgets, based /// on the value of [CupertinoThemeData.brightness] in the given [context]. /// /// If no [CupertinoTheme] can be found in the given [context], it will fall /// back to [MediaQueryData.platformBrightness]. /// /// Returns null if no valid [CupertinoTheme] or [MediaQuery] widgets exist in /// the ancestry tree. /// /// See also: /// /// * [CupertinoThemeData.brightness], the property takes precedence over /// [MediaQueryData.platformBrightness] for descendant Cupertino widgets. /// * [brightnessOf], which throws if no valid [CupertinoTheme] or /// [MediaQuery] exists, instead of returning null. static Brightness? maybeBrightnessOf(BuildContext context) { final _InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedCupertinoTheme>(); return inheritedTheme?.theme.data.brightness ?? MediaQuery.maybePlatformBrightnessOf(context); } /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.ProxyWidget.child} final Widget child; @override Widget build(BuildContext context) { return _InheritedCupertinoTheme( theme: this, child: IconTheme( data: CupertinoIconThemeData(color: data.primaryColor), child: child, ), ); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); data.debugFillProperties(properties); } } class _InheritedCupertinoTheme extends InheritedWidget { const _InheritedCupertinoTheme({ required this.theme, required super.child, }); final CupertinoTheme theme; @override bool updateShouldNotify(_InheritedCupertinoTheme old) => theme.data != old.theme.data; } /// Styling specifications for a [CupertinoTheme]. /// /// All constructor parameters can be null, in which case a /// [CupertinoColors.activeBlue] based default iOS theme styling is used. /// /// Parameters can also be partially specified, in which case some parameters /// will cascade down to other dependent parameters to create a cohesive /// visual effect. For instance, if a [primaryColor] is specified, it would /// cascade down to affect some fonts in [textTheme] if [textTheme] is not /// specified. /// /// See also: /// /// * [CupertinoTheme], in which this [CupertinoThemeData] is inserted. /// * [ThemeData], a Material equivalent that also configures Cupertino /// styling via a [CupertinoThemeData] subclass [MaterialBasedCupertinoThemeData]. @immutable class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable { /// Creates a [CupertinoTheme] styling specification. /// /// Unspecified parameters default to a reasonable iOS default style. const CupertinoThemeData({ Brightness? brightness, Color? primaryColor, Color? primaryContrastingColor, CupertinoTextThemeData? textTheme, Color? barBackgroundColor, Color? scaffoldBackgroundColor, bool? applyThemeToAll, }) : this.raw( brightness, primaryColor, primaryContrastingColor, textTheme, barBackgroundColor, scaffoldBackgroundColor, applyThemeToAll, ); /// Same as the default constructor but with positional arguments to avoid /// forgetting any and to specify all arguments. /// /// Used by subclasses to get the superclass's defaulting behaviors. @protected const CupertinoThemeData.raw( Brightness? brightness, Color? primaryColor, Color? primaryContrastingColor, CupertinoTextThemeData? textTheme, Color? barBackgroundColor, Color? scaffoldBackgroundColor, bool? applyThemeToAll, ) : this._rawWithDefaults( brightness, primaryColor, primaryContrastingColor, textTheme, barBackgroundColor, scaffoldBackgroundColor, applyThemeToAll, _kDefaultTheme, ); const CupertinoThemeData._rawWithDefaults( Brightness? brightness, Color? primaryColor, Color? primaryContrastingColor, CupertinoTextThemeData? textTheme, Color? barBackgroundColor, Color? scaffoldBackgroundColor, bool? applyThemeToAll, this._defaults, ) : super( brightness: brightness, primaryColor: primaryColor, primaryContrastingColor: primaryContrastingColor, textTheme: textTheme, barBackgroundColor: barBackgroundColor, scaffoldBackgroundColor: scaffoldBackgroundColor, applyThemeToAll: applyThemeToAll, ); final _CupertinoThemeDefaults _defaults; @override Color get primaryColor => super.primaryColor ?? _defaults.primaryColor; @override Color get primaryContrastingColor => super.primaryContrastingColor ?? _defaults.primaryContrastingColor; @override CupertinoTextThemeData get textTheme { return super.textTheme ?? _defaults.textThemeDefaults.createDefaults(primaryColor: primaryColor); } @override Color get barBackgroundColor => super.barBackgroundColor ?? _defaults.barBackgroundColor; @override Color get scaffoldBackgroundColor => super.scaffoldBackgroundColor ?? _defaults.scaffoldBackgroundColor; @override bool get applyThemeToAll => super.applyThemeToAll ?? _defaults.applyThemeToAll; @override NoDefaultCupertinoThemeData noDefault() { return NoDefaultCupertinoThemeData( brightness: super.brightness, primaryColor: super.primaryColor, primaryContrastingColor: super.primaryContrastingColor, textTheme: super.textTheme, barBackgroundColor: super.barBackgroundColor, scaffoldBackgroundColor: super.scaffoldBackgroundColor, applyThemeToAll: super.applyThemeToAll, ); } @override CupertinoThemeData resolveFrom(BuildContext context) { Color? convertColor(Color? color) => CupertinoDynamicColor.maybeResolve(color, context); return CupertinoThemeData._rawWithDefaults( brightness, convertColor(super.primaryColor), convertColor(super.primaryContrastingColor), super.textTheme?.resolveFrom(context), convertColor(super.barBackgroundColor), convertColor(super.scaffoldBackgroundColor), applyThemeToAll, _defaults.resolveFrom(context, super.textTheme == null), ); } @override CupertinoThemeData copyWith({ Brightness? brightness, Color? primaryColor, Color? primaryContrastingColor, CupertinoTextThemeData? textTheme, Color? barBackgroundColor, Color? scaffoldBackgroundColor, bool? applyThemeToAll, }) { return CupertinoThemeData._rawWithDefaults( brightness ?? super.brightness, primaryColor ?? super.primaryColor, primaryContrastingColor ?? super.primaryContrastingColor, textTheme ?? super.textTheme, barBackgroundColor ?? super.barBackgroundColor, scaffoldBackgroundColor ?? super.scaffoldBackgroundColor, applyThemeToAll ?? super.applyThemeToAll, _defaults, ); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); const CupertinoThemeData defaultData = CupertinoThemeData(); properties.add(EnumProperty<Brightness>('brightness', brightness, defaultValue: null)); properties.add(createCupertinoColorProperty('primaryColor', primaryColor, defaultValue: defaultData.primaryColor)); properties.add(createCupertinoColorProperty('primaryContrastingColor', primaryContrastingColor, defaultValue: defaultData.primaryContrastingColor)); properties.add(createCupertinoColorProperty('barBackgroundColor', barBackgroundColor, defaultValue: defaultData.barBackgroundColor)); properties.add(createCupertinoColorProperty('scaffoldBackgroundColor', scaffoldBackgroundColor, defaultValue: defaultData.scaffoldBackgroundColor)); properties.add(DiagnosticsProperty<bool>('applyThemeToAll', applyThemeToAll, defaultValue: defaultData.applyThemeToAll)); textTheme.debugFillProperties(properties); } @override bool operator == (Object other) { if (identical(this, other)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is CupertinoThemeData && other.brightness == brightness && other.primaryColor == primaryColor && other.primaryContrastingColor == primaryContrastingColor && other.textTheme == textTheme && other.barBackgroundColor == barBackgroundColor && other.scaffoldBackgroundColor == scaffoldBackgroundColor && other.applyThemeToAll == applyThemeToAll; } @override int get hashCode => Object.hash( brightness, primaryColor, primaryContrastingColor, textTheme, barBackgroundColor, scaffoldBackgroundColor, applyThemeToAll, ); } /// Styling specifications for a cupertino theme without default values for /// unspecified properties. /// /// Unlike [CupertinoThemeData] instances of this class do not return default /// values for properties that have been left unspecified in the constructor. /// Instead, unspecified properties will return null. This is used by /// Material's [ThemeData.cupertinoOverrideTheme]. /// /// See also: /// /// * [CupertinoThemeData], which uses reasonable default values for /// unspecified theme properties. class NoDefaultCupertinoThemeData { /// Creates a [NoDefaultCupertinoThemeData] styling specification. /// /// Unspecified properties default to null. const NoDefaultCupertinoThemeData({ this.brightness, this.primaryColor, this.primaryContrastingColor, this.textTheme, this.barBackgroundColor, this.scaffoldBackgroundColor, this.applyThemeToAll, }); /// The brightness override for Cupertino descendants. /// /// Defaults to null. If a non-null [Brightness] is specified, the value will /// take precedence over the ambient [MediaQueryData.platformBrightness], when /// determining the brightness of descendant Cupertino widgets. /// /// If coming from a Material [Theme] and unspecified, [brightness] will be /// derived from the Material [ThemeData]'s [brightness]. /// /// See also: /// /// * [MaterialBasedCupertinoThemeData], a [CupertinoThemeData] that defers /// [brightness] to its Material [Theme] parent if it's unspecified. /// /// * [CupertinoTheme.brightnessOf], a method used to retrieve the overall /// [Brightness] from a [BuildContext], for Cupertino widgets. final Brightness? brightness; /// A color used on interactive elements of the theme. /// /// This color is generally used on text and icons in buttons and tappable /// elements. Defaults to [CupertinoColors.activeBlue]. /// /// If coming from a Material [Theme] and unspecified, [primaryColor] will be /// derived from the Material [ThemeData]'s `colorScheme.primary`. However, in /// iOS styling, the [primaryColor] is more sparsely used than in Material /// Design where the [primaryColor] can appear on non-interactive surfaces like /// the [AppBar] background, [TextField] borders etc. /// /// See also: /// /// * [MaterialBasedCupertinoThemeData], a [CupertinoThemeData] that defers /// [primaryColor] to its Material [Theme] parent if it's unspecified. final Color? primaryColor; /// A color that must be easy to see when rendered on a [primaryColor] background. /// /// For example, this color is used for a [CupertinoButton]'s text and icons /// when the button's background is [primaryColor]. /// /// If coming from a Material [Theme] and unspecified, [primaryContrastingColor] /// will be derived from the Material [ThemeData]'s `colorScheme.onPrimary`. /// /// See also: /// /// * [MaterialBasedCupertinoThemeData], a [CupertinoThemeData] that defers /// [primaryContrastingColor] to its Material [Theme] parent if it's unspecified. final Color? primaryContrastingColor; /// Text styles used by Cupertino widgets. /// /// Derived from [primaryColor] if unspecified. final CupertinoTextThemeData? textTheme; /// Background color of the top nav bar and bottom tab bar. /// /// Defaults to a light gray in light mode, or a dark translucent gray color in /// dark mode. final Color? barBackgroundColor; /// Background color of the scaffold. /// /// Defaults to [CupertinoColors.systemBackground]. final Color? scaffoldBackgroundColor; /// Flag to apply this theme to all descendant Cupertino widgets. /// /// Certain Cupertino widgets previously didn't use theming, matching past /// versions of iOS. For example, [CupertinoSwitch]s always used /// [CupertinoColors.systemGreen] when active. /// /// Today, however, these widgets can indeed be themed on iOS. Moreover on /// macOS, the accent color is reflected in these widgets. Turning this flag /// on ensures that descendant Cupertino widgets will be themed accordingly. /// /// This flag currently applies to the following widgets: /// - [CupertinoSwitch] & [Switch.adaptive] /// /// Defaults to false. final bool? applyThemeToAll; /// Returns an instance of the theme data whose property getters only return /// the construction time specifications with no derived values. /// /// Used in Material themes to let unspecified properties fallback to Material /// theme properties instead of iOS defaults. NoDefaultCupertinoThemeData noDefault() => this; /// Returns a new theme data with all its colors resolved against the /// given [BuildContext]. /// /// Called by [CupertinoTheme.of] to resolve colors defined in the retrieved /// [CupertinoThemeData]. @protected NoDefaultCupertinoThemeData resolveFrom(BuildContext context) { Color? convertColor(Color? color) => CupertinoDynamicColor.maybeResolve(color, context); return NoDefaultCupertinoThemeData( brightness: brightness, primaryColor: convertColor(primaryColor), primaryContrastingColor: convertColor(primaryContrastingColor), textTheme: textTheme?.resolveFrom(context), barBackgroundColor: convertColor(barBackgroundColor), scaffoldBackgroundColor: convertColor(scaffoldBackgroundColor), applyThemeToAll: applyThemeToAll, ); } /// Creates a copy of the theme data with specified attributes overridden. /// /// Only the current instance's specified attributes are copied instead of /// derived values. For instance, if the current [textTheme] is implied from /// the current [primaryColor] because it was not specified, copying with a /// different [primaryColor] will also change the copy's implied [textTheme]. NoDefaultCupertinoThemeData copyWith({ Brightness? brightness, Color? primaryColor, Color? primaryContrastingColor, CupertinoTextThemeData? textTheme, Color? barBackgroundColor , Color? scaffoldBackgroundColor, bool? applyThemeToAll, }) { return NoDefaultCupertinoThemeData( brightness: brightness ?? this.brightness, primaryColor: primaryColor ?? this.primaryColor, primaryContrastingColor: primaryContrastingColor ?? this.primaryContrastingColor, textTheme: textTheme ?? this.textTheme, barBackgroundColor: barBackgroundColor ?? this.barBackgroundColor, scaffoldBackgroundColor: scaffoldBackgroundColor ?? this.scaffoldBackgroundColor, applyThemeToAll: applyThemeToAll ?? this.applyThemeToAll, ); } } @immutable class _CupertinoThemeDefaults { const _CupertinoThemeDefaults( this.brightness, this.primaryColor, this.primaryContrastingColor, this.barBackgroundColor, this.scaffoldBackgroundColor, this.applyThemeToAll, this.textThemeDefaults, ); final Brightness? brightness; final Color primaryColor; final Color primaryContrastingColor; final Color barBackgroundColor; final Color scaffoldBackgroundColor; final bool applyThemeToAll; final _CupertinoTextThemeDefaults textThemeDefaults; _CupertinoThemeDefaults resolveFrom(BuildContext context, bool resolveTextTheme) { Color convertColor(Color color) => CupertinoDynamicColor.resolve(color, context); return _CupertinoThemeDefaults( brightness, convertColor(primaryColor), convertColor(primaryContrastingColor), convertColor(barBackgroundColor), convertColor(scaffoldBackgroundColor), applyThemeToAll, resolveTextTheme ? textThemeDefaults.resolveFrom(context) : textThemeDefaults, ); } } @immutable class _CupertinoTextThemeDefaults { const _CupertinoTextThemeDefaults( this.labelColor, this.inactiveGray, ); final Color labelColor; final Color inactiveGray; _CupertinoTextThemeDefaults resolveFrom(BuildContext context) { return _CupertinoTextThemeDefaults( CupertinoDynamicColor.resolve(labelColor, context), CupertinoDynamicColor.resolve(inactiveGray, context), ); } CupertinoTextThemeData createDefaults({ required Color primaryColor }) { return _DefaultCupertinoTextThemeData( primaryColor: primaryColor, labelColor: labelColor, inactiveGray: inactiveGray, ); } } // CupertinoTextThemeData with no text styles explicitly specified. // The implementation of this class may need to be updated when any of the default // text styles changes. class _DefaultCupertinoTextThemeData extends CupertinoTextThemeData { const _DefaultCupertinoTextThemeData({ required this.labelColor, required this.inactiveGray, required super.primaryColor, }); final Color labelColor; final Color inactiveGray; @override TextStyle get textStyle => super.textStyle.copyWith(color: labelColor); @override TextStyle get tabLabelTextStyle => super.tabLabelTextStyle.copyWith(color: inactiveGray); @override TextStyle get navTitleTextStyle => super.navTitleTextStyle.copyWith(color: labelColor); @override TextStyle get navLargeTitleTextStyle => super.navLargeTitleTextStyle.copyWith(color: labelColor); @override TextStyle get pickerTextStyle => super.pickerTextStyle.copyWith(color: labelColor); @override TextStyle get dateTimePickerTextStyle => super.dateTimePickerTextStyle.copyWith(color: labelColor); }