blob: f68c3f417f331a6ae1a76e9dd66549937f20ed35 [file] [log] [blame]
// 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,
_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.
///
/// The [data] and [child] parameters must not be null.
const CupertinoTheme({
super.key,
required this.data,
required this.child,
}) : assert(child != null),
assert(data != null);
/// 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.of(context).platformBrightness;
}
/// 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.maybeOf(context)?.platformBrightness;
}
/// 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,
}) : assert(theme != null);
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,
}) : this.raw(
brightness,
primaryColor,
primaryContrastingColor,
textTheme,
barBackgroundColor,
scaffoldBackgroundColor,
);
/// 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,
) : this._rawWithDefaults(
brightness,
primaryColor,
primaryContrastingColor,
textTheme,
barBackgroundColor,
scaffoldBackgroundColor,
_kDefaultTheme,
);
const CupertinoThemeData._rawWithDefaults(
Brightness? brightness,
Color? primaryColor,
Color? primaryContrastingColor,
CupertinoTextThemeData? textTheme,
Color? barBackgroundColor,
Color? scaffoldBackgroundColor,
this._defaults,
) : super(
brightness: brightness,
primaryColor: primaryColor,
primaryContrastingColor: primaryContrastingColor,
textTheme: textTheme,
barBackgroundColor: barBackgroundColor,
scaffoldBackgroundColor: scaffoldBackgroundColor,
);
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
NoDefaultCupertinoThemeData noDefault() {
return NoDefaultCupertinoThemeData(
brightness: super.brightness,
primaryColor: super.primaryColor,
primaryContrastingColor: super.primaryContrastingColor,
textTheme: super.textTheme,
barBackgroundColor: super.barBackgroundColor,
scaffoldBackgroundColor: super.scaffoldBackgroundColor,
);
}
@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),
_defaults.resolveFrom(context, super.textTheme == null),
);
}
@override
CupertinoThemeData copyWith({
Brightness? brightness,
Color? primaryColor,
Color? primaryContrastingColor,
CupertinoTextThemeData? textTheme,
Color? barBackgroundColor,
Color? scaffoldBackgroundColor,
}) {
return CupertinoThemeData._rawWithDefaults(
brightness ?? super.brightness,
primaryColor ?? super.primaryColor,
primaryContrastingColor ?? super.primaryContrastingColor,
textTheme ?? super.textTheme,
barBackgroundColor ?? super.barBackgroundColor,
scaffoldBackgroundColor ?? super.scaffoldBackgroundColor,
_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));
textTheme.debugFillProperties(properties);
}
}
/// 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,
});
/// 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;
/// 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),
);
}
/// 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,
}) {
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,
);
}
}
@immutable
class _CupertinoThemeDefaults {
const _CupertinoThemeDefaults(
this.brightness,
this.primaryColor,
this.primaryContrastingColor,
this.barBackgroundColor,
this.scaffoldBackgroundColor,
this.textThemeDefaults,
);
final Brightness? brightness;
final Color primaryColor;
final Color primaryContrastingColor;
final Color barBackgroundColor;
final Color scaffoldBackgroundColor;
final _CupertinoTextThemeDefaults textThemeDefaults;
_CupertinoThemeDefaults resolveFrom(BuildContext context, bool resolveTextTheme) {
Color convertColor(Color color) => CupertinoDynamicColor.resolve(color, context);
return _CupertinoThemeDefaults(
brightness,
convertColor(primaryColor),
convertColor(primaryContrastingColor),
convertColor(barBackgroundColor),
convertColor(scaffoldBackgroundColor),
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 }) {
assert(primaryColor != null);
return _DefaultCupertinoTextThemeData(
primaryColor: primaryColor,
labelColor: labelColor,
inactiveGray: inactiveGray,
);
}
}
// CupertinoTextThemeData with no text styles explicitly specified.
// The implementation of this class may need to be updated when any of the default
// text styles changes.
class _DefaultCupertinoTextThemeData extends CupertinoTextThemeData {
const _DefaultCupertinoTextThemeData({
required this.labelColor,
required this.inactiveGray,
required super.primaryColor,
}) : assert(labelColor != null),
assert(inactiveGray != null),
assert(primaryColor != null);
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);
}