blob: d75df0fc84b92f9f461783fe758b061a2a14f5a1 [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/services.dart';
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'icon_theme_data.dart';
import 'text_theme.dart';
export 'package:flutter/services.dart' show Brightness;
// Values derived from https://developer.apple.com/design/resources/.
const _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({
Key key,
@required this.data,
@required this.child,
}) : assert(child != null),
assert(data != null),
super(key: key);
/// 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, nullOk: true);
}
/// 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.brightness].
///
/// Throws an exception if no valid [CupertinoTheme] or [MediaQuery] widgets
/// exist in the ancestry tree, unless [nullOk] is set to true.
///
/// See also:
///
/// * [CupertinoThemeData.brightness], the property takes precedence over
/// [MediaQueryData.platformBrightness] for descendant Cupertino widgets.
static Brightness brightnessOf(BuildContext context, { bool nullOk = false }) {
final _InheritedCupertinoTheme inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedCupertinoTheme>();
return inheritedTheme?.theme?.data?.brightness ?? MediaQuery.of(context, nullOk: nullOk)?.platformBrightness;
}
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.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({
Key key,
@required this.theme,
@required Widget child,
}) : assert(theme != null),
super(key: key, child: 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 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(
this.brightness,
this._primaryColor,
this._primaryContrastingColor,
this._textTheme,
this._barBackgroundColor,
this._scaffoldBackgroundColor,
this._defaults,
);
final _CupertinoThemeDefaults _defaults;
/// 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.
Color get primaryColor => _primaryColor ?? _defaults.primaryColor;
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.
Color get primaryContrastingColor => _primaryContrastingColor ?? _defaults.primaryContrastingColor;
final Color _primaryContrastingColor;
/// Text styles used by Cupertino widgets.
///
/// Derived from [primaryColor] if unspecified.
CupertinoTextThemeData get textTheme {
return _textTheme ?? _defaults.textThemeDefaults.createDefaults(primaryColor: primaryColor);
}
final CupertinoTextThemeData _textTheme;
/// Background color of the top nav bar and bottom tab bar.
///
/// Defaults to a light gray in light mode, or a dark translucent gray color in
/// dark mode.
Color get barBackgroundColor => _barBackgroundColor ?? _defaults.barBackgroundColor;
final Color _barBackgroundColor;
/// Background color of the scaffold.
///
/// Defaults to [CupertinoColors.systemBackground].
Color get scaffoldBackgroundColor => _scaffoldBackgroundColor ?? _defaults.scaffoldBackgroundColor;
final Color _scaffoldBackgroundColor;
/// Returns an instance of the [CupertinoThemeData] whose property getters
/// only return the construction time specifications with no derived values.
///
/// Used in Material themes to let unspecified properties fallback to Material
/// theme properties instead of iOS defaults.
CupertinoThemeData noDefault() {
return _NoDefaultCupertinoThemeData(
brightness,
_primaryColor,
_primaryContrastingColor,
_textTheme,
_barBackgroundColor,
_scaffoldBackgroundColor,
);
}
/// Returns a new `CupertinoThemeData` with all its colors resolved against the
/// given [BuildContext].
///
/// Called by [CupertinoTheme.of] to resolve colors defined in the retrieved
/// [CupertinoThemeData].
@protected
CupertinoThemeData resolveFrom(BuildContext context, { bool nullOk = false }) {
Color convertColor(Color color) => CupertinoDynamicColor.resolve(color, context, nullOk: nullOk);
return CupertinoThemeData._rawWithDefaults(
brightness,
convertColor(_primaryColor),
convertColor(_primaryContrastingColor),
_textTheme?.resolveFrom(context, nullOk: nullOk),
convertColor(_barBackgroundColor),
convertColor(_scaffoldBackgroundColor),
_defaults.resolveFrom(context, _textTheme == null, nullOk: nullOk),
);
}
/// Creates a copy of [CupertinoThemeData] with specified attributes overridden.
///
/// Only the current instance's specified attributes are copied instead of
/// derived values. For instance, if the current [CupertinoThemeData.textTheme]
/// is implied from the current [primaryColor] because it was not specified,
/// copying with a different [primaryColor] will also change the copy's implied
/// [textTheme].
CupertinoThemeData copyWith({
Brightness brightness,
Color primaryColor,
Color primaryContrastingColor,
CupertinoTextThemeData textTheme,
Color barBackgroundColor,
Color scaffoldBackgroundColor,
}) {
return CupertinoThemeData._rawWithDefaults(
brightness ?? this.brightness,
primaryColor ?? _primaryColor,
primaryContrastingColor ?? _primaryContrastingColor,
textTheme ?? _textTheme,
barBackgroundColor ?? _barBackgroundColor,
scaffoldBackgroundColor ?? _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);
}
}
class _NoDefaultCupertinoThemeData extends CupertinoThemeData {
const _NoDefaultCupertinoThemeData(
Brightness brightness,
this.primaryColor,
this.primaryContrastingColor,
this.textTheme,
this.barBackgroundColor,
this.scaffoldBackgroundColor,
) : super._rawWithDefaults(
brightness,
primaryColor,
primaryContrastingColor,
textTheme,
barBackgroundColor,
scaffoldBackgroundColor,
null,
);
@override
final Color primaryColor;
@override
final Color primaryContrastingColor;
@override
final CupertinoTextThemeData textTheme;
@override
final Color barBackgroundColor;
@override
final Color scaffoldBackgroundColor;
@override
_NoDefaultCupertinoThemeData resolveFrom(BuildContext context, { bool nullOk = false }) {
Color convertColor(Color color) => 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,
);
}
}
@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, { @required bool nullOk }) {
assert(nullOk != null);
Color convertColor(Color color) => CupertinoDynamicColor.resolve(color, context, nullOk: nullOk);
return _CupertinoThemeDefaults(
brightness,
convertColor(primaryColor),
convertColor(primaryContrastingColor),
convertColor(barBackgroundColor),
convertColor(scaffoldBackgroundColor),
resolveTextTheme ? textThemeDefaults?.resolveFrom(context, nullOk: nullOk) : textThemeDefaults,
);
}
}
@immutable
class _CupertinoTextThemeDefaults {
const _CupertinoTextThemeDefaults(
this.labelColor,
this.inactiveGray,
);
final Color labelColor;
final Color inactiveGray;
_CupertinoTextThemeDefaults resolveFrom(BuildContext context, { @required bool nullOk }) {
return _CupertinoTextThemeDefaults(
CupertinoDynamicColor.resolve(labelColor, context, nullOk: nullOk),
CupertinoDynamicColor.resolve(inactiveGray, context, nullOk: nullOk),
);
}
CupertinoTextThemeData createDefaults({ @required Color primaryColor }) {
assert(primaryColor != null);
return _DefaultCupertinoTextThemeData(
primaryColor: primaryColor,
labelColor: labelColor,
inactiveGray: inactiveGray,
);
}
}
// CupertinoTextThemeData with no text styles explicitly specified.
// The implementation of this class may need to be updated when any of the default
// text styles changes.
class _DefaultCupertinoTextThemeData extends CupertinoTextThemeData {
const _DefaultCupertinoTextThemeData({
@required this.labelColor,
@required this.inactiveGray,
@required Color primaryColor,
}) : assert(labelColor != null),
assert(inactiveGray != null),
assert(primaryColor != null),
super(primaryColor: primaryColor);
final Color labelColor;
final Color inactiveGray;
@override
TextStyle get textStyle => super.textStyle.copyWith(color: labelColor);
@override
TextStyle get tabLabelTextStyle => super.tabLabelTextStyle.copyWith(color: inactiveGray);
@override
TextStyle get navTitleTextStyle => super.navTitleTextStyle.copyWith(color: labelColor);
@override
TextStyle get navLargeTitleTextStyle => super.navLargeTitleTextStyle.copyWith(color: labelColor);
@override
TextStyle get pickerTextStyle => super.pickerTextStyle.copyWith(color: labelColor);
@override
TextStyle get dateTimePickerTextStyle => super.dateTimePickerTextStyle.copyWith(color: labelColor);
}