mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
This PR fixes #92525 and introduces the following changes according to [latest iOS HIG](https://developer.apple.com/design/human-interface-guidelines/buttons#iOS-iPadOS): - `CupertinoButton` now has a `size` property (type `enum CupertinoButtonSize`, values sm/md/lg, default `lg`) that allows the devs to apply new iOS 15+ button styles - Previously `CupertinoButton` had a larger padding when no background color was specified. With the new HIG, that is no longer the case - `CupertinoButton` now has a `.tinted` constructor that renders a translucent background (transparency % is brightness-dependent) and uses a different foreground color compared to `.filled` - `CupertinoButton` now uses the `actionTextStyle` TextStyle from the given theme - `CupertinoButton`'s child IconTheme's size will always be x1.2 the given TextStyle's size - `CupertinoTextThemeData` now has a `actionSmallTextStyle` property to use with small buttons (including a default `_kDefaultActionSmallTextStyle` TextStyle) Preview & example:  > **NOTE**: there is a discrepancy in dark mode button foreground color between the default CupertinoTheme and the HIG. A separate issue will be opened for this. ~EDIT: issue reported here https://github.com/flutter/flutter/issues/152846~ EDIT2: fixed by #153039 !  ## Example ```dart import 'package:flutter/cupertino.dart'; const Widget body = Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Icon( CupertinoIcons.play_fill, ), Text("Play"), ], ); void main() => runApp( CupertinoApp( home: Container( child: Wrap( direction: Axis.horizontal, children: <Widget>[ // header Text(''), Text('Plain'), Text('Grey'), Text('Tinted'), Text('Filled'), // small Text('Small'), CupertinoButton( child: body, onPressed: () {}, size: CupertinoButtonSize.small, ), CupertinoButton.tinted( child: body, onPressed: () {}, size: CupertinoButtonSize.small, color: CupertinoColors.systemGrey, ), CupertinoButton.tinted( child: body, onPressed: () {}, size: CupertinoButtonSize.small, ), CupertinoButton.filled( child: body, onPressed: () {}, size: CupertinoButtonSize.small, ), // medium Text('Medium'), CupertinoButton( child: body, onPressed: () {}, size: CupertinoButtonSize.medium, ), CupertinoButton.tinted( child: body, onPressed: () {}, size: CupertinoButtonSize.medium, color: CupertinoColors.systemGrey, ), CupertinoButton.tinted( child: body, onPressed: () {}, size: CupertinoButtonSize.medium, ), CupertinoButton.filled( child: body, onPressed: () {}, size: CupertinoButtonSize.medium, ), // large Text('Large'), CupertinoButton( child: body, onPressed: () {}, size: CupertinoButtonSize.large, ), CupertinoButton.tinted( child: body, onPressed: () {}, color: CupertinoColors.systemGrey, size: CupertinoButtonSize.large, ), CupertinoButton.tinted( child: body, onPressed: () {}, size: CupertinoButtonSize.large, ), CupertinoButton.filled( child: body, onPressed: () {}, size: CupertinoButtonSize.large, ), ].map((Widget w) => SizedBox(width: 110, height: 70, child: Center(child: w))).toList(), ), ) ), ); ``` *List which issues are fixed by this PR. You must list at least one issue. An issue is not required if the PR fixes something trivial like a typo.* *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
This commit is contained in:
parent
2f0415f37b
commit
dae3a87d93
@ -63,7 +63,6 @@ class _EditableTextToolbarBuilderExampleAppState extends State<EditableTextToolb
|
||||
// buttons depending on the platform.
|
||||
children: editableTextState.contextMenuButtonItems.map((ContextMenuButtonItem buttonItem) {
|
||||
return CupertinoButton(
|
||||
borderRadius: null,
|
||||
color: const Color(0xffaaaa00),
|
||||
disabledColor: const Color(0xffaaaaff),
|
||||
onPressed: buttonItem.onPressed,
|
||||
|
@ -10,14 +10,33 @@ import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'colors.dart';
|
||||
import 'constants.dart';
|
||||
import 'text_theme.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
// Measured against iOS 12 in Xcode.
|
||||
const EdgeInsets _kButtonPadding = EdgeInsets.all(16.0);
|
||||
const EdgeInsets _kBackgroundButtonPadding = EdgeInsets.symmetric(
|
||||
vertical: 14.0,
|
||||
horizontal: 64.0,
|
||||
);
|
||||
// Measured against iOS (17) [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/buttons#iOS-iPadOS).
|
||||
|
||||
/// The size of a [CupertinoButton].
|
||||
/// Based on the iOS (17) [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/buttons#iOS-iPadOS).
|
||||
enum CupertinoButtonSize {
|
||||
/// Displays a smaller button with round sides and smaller text (uses [CupertinoTextThemeData.actionSmallTextStyle]).
|
||||
small,
|
||||
/// Displays a medium sized button with round sides and regular-sized text.
|
||||
medium,
|
||||
/// Displays a (classic) large button with rounded edges and regular-sized text.
|
||||
large,
|
||||
}
|
||||
|
||||
/// The style of a [CupertinoButton] that changes the style of the button's background.
|
||||
///
|
||||
/// Based on the iOS Human Interface Guidelines (https://developer.apple.com/design/human-interface-guidelines/buttons#iOS-iPadOS).
|
||||
enum _CupertinoButtonStyle {
|
||||
/// No background or border, primary foreground color.
|
||||
plain,
|
||||
/// Translucent background, primary foreground color.
|
||||
tinted,
|
||||
/// Solid background, contrasting foreground color.
|
||||
filled,
|
||||
}
|
||||
|
||||
/// An iOS-style button.
|
||||
///
|
||||
@ -48,12 +67,13 @@ class CupertinoButton extends StatefulWidget {
|
||||
const CupertinoButton({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.sizeStyle = CupertinoButtonSize.large,
|
||||
this.padding,
|
||||
this.color,
|
||||
this.disabledColor = CupertinoColors.quaternarySystemFill,
|
||||
this.minSize = kMinInteractiveDimensionCupertino,
|
||||
this.minSize,
|
||||
this.pressedOpacity = 0.4,
|
||||
this.borderRadius = const BorderRadius.all(Radius.circular(8.0)),
|
||||
this.borderRadius,
|
||||
this.alignment = Alignment.center,
|
||||
this.focusColor,
|
||||
this.focusNode,
|
||||
@ -61,7 +81,34 @@ class CupertinoButton extends StatefulWidget {
|
||||
this.autofocus = false,
|
||||
required this.onPressed,
|
||||
}) : assert(pressedOpacity == null || (pressedOpacity >= 0.0 && pressedOpacity <= 1.0)),
|
||||
_filled = false;
|
||||
_style = _CupertinoButtonStyle.plain;
|
||||
|
||||
/// Creates an iOS-style button with a tinted background.
|
||||
///
|
||||
/// The background color is derived from the [CupertinoTheme]'s `primaryColor` + transparency.
|
||||
/// The foreground color is the [CupertinoTheme]'s `primaryColor`.
|
||||
///
|
||||
/// To specify a custom background color, use the [color] argument of the
|
||||
/// default constructor.
|
||||
///
|
||||
/// To match the iOS "grey" button style, set [color] to [CupertinoColors.systemGrey].
|
||||
const CupertinoButton.tinted({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.sizeStyle = CupertinoButtonSize.large,
|
||||
this.padding,
|
||||
this.color,
|
||||
this.disabledColor = CupertinoColors.tertiarySystemFill,
|
||||
this.minSize,
|
||||
this.pressedOpacity = 0.4,
|
||||
this.borderRadius,
|
||||
this.alignment = Alignment.center,
|
||||
this.focusColor,
|
||||
this.focusNode,
|
||||
this.onFocusChange,
|
||||
this.autofocus = false,
|
||||
required this.onPressed,
|
||||
}) : _style = _CupertinoButtonStyle.tinted;
|
||||
|
||||
/// Creates an iOS-style button with a filled background.
|
||||
///
|
||||
@ -72,11 +119,12 @@ class CupertinoButton extends StatefulWidget {
|
||||
const CupertinoButton.filled({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.sizeStyle = CupertinoButtonSize.large,
|
||||
this.padding,
|
||||
this.disabledColor = CupertinoColors.quaternarySystemFill,
|
||||
this.minSize = kMinInteractiveDimensionCupertino,
|
||||
this.disabledColor = CupertinoColors.tertiarySystemFill,
|
||||
this.minSize,
|
||||
this.pressedOpacity = 0.4,
|
||||
this.borderRadius = const BorderRadius.all(Radius.circular(8.0)),
|
||||
this.borderRadius,
|
||||
this.alignment = Alignment.center,
|
||||
this.focusColor,
|
||||
this.focusNode,
|
||||
@ -85,7 +133,7 @@ class CupertinoButton extends StatefulWidget {
|
||||
required this.onPressed,
|
||||
}) : assert(pressedOpacity == null || (pressedOpacity >= 0.0 && pressedOpacity <= 1.0)),
|
||||
color = null,
|
||||
_filled = true;
|
||||
_style = _CupertinoButtonStyle.filled;
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
///
|
||||
@ -133,9 +181,14 @@ class CupertinoButton extends StatefulWidget {
|
||||
|
||||
/// The radius of the button's corners when it has a background color.
|
||||
///
|
||||
/// Defaults to round corners of 8 logical pixels.
|
||||
/// Defaults to [kCupertinoButtonSizeBorderRadius], based on [sizeStyle].
|
||||
final BorderRadius? borderRadius;
|
||||
|
||||
/// The size of the button.
|
||||
///
|
||||
/// Defaults to [CupertinoButtonSize.large].
|
||||
final CupertinoButtonSize sizeStyle;
|
||||
|
||||
/// The alignment of the button's [child].
|
||||
///
|
||||
/// Typically buttons are sized to be just big enough to contain the child and its
|
||||
@ -166,7 +219,7 @@ class CupertinoButton extends StatefulWidget {
|
||||
/// {@macro flutter.widgets.Focus.autofocus}
|
||||
final bool autofocus;
|
||||
|
||||
final bool _filled;
|
||||
final _CupertinoButtonStyle _style;
|
||||
|
||||
/// Whether the button is enabled or disabled. Buttons are disabled by default. To
|
||||
/// enable a button, set its [onPressed] property to a non-null value.
|
||||
@ -273,15 +326,24 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
|
||||
final bool enabled = widget.enabled;
|
||||
final CupertinoThemeData themeData = CupertinoTheme.of(context);
|
||||
final Color primaryColor = themeData.primaryColor;
|
||||
final Color? backgroundColor = widget.color == null
|
||||
? (widget._filled ? primaryColor : null)
|
||||
: CupertinoDynamicColor.maybeResolve(widget.color, context);
|
||||
|
||||
final Color foregroundColor = backgroundColor != null
|
||||
final Color? backgroundColor = (
|
||||
widget.color == null
|
||||
? widget._style != _CupertinoButtonStyle.plain
|
||||
? primaryColor
|
||||
: null
|
||||
: CupertinoDynamicColor.maybeResolve(widget.color, context)
|
||||
)?.withOpacity(
|
||||
widget._style == _CupertinoButtonStyle.tinted
|
||||
? CupertinoTheme.brightnessOf(context) == Brightness.light
|
||||
? kCupertinoButtonTintedOpacityLight
|
||||
: kCupertinoButtonTintedOpacityDark
|
||||
: widget.color?.opacity ?? 1.0,
|
||||
);
|
||||
final Color foregroundColor = widget._style == _CupertinoButtonStyle.filled
|
||||
? themeData.primaryContrastingColor
|
||||
: enabled
|
||||
? primaryColor
|
||||
: CupertinoDynamicColor.resolve(CupertinoColors.placeholderText, context);
|
||||
: CupertinoDynamicColor.resolve(CupertinoColors.tertiaryLabel, context);
|
||||
|
||||
final Color effectiveFocusOutlineColor = widget.focusColor ??
|
||||
HSLColor
|
||||
@ -291,8 +353,17 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
|
||||
.withSaturation(kCupertinoFocusColorSaturation)
|
||||
.toColor();
|
||||
|
||||
final TextStyle textStyle = themeData.textTheme.textStyle.copyWith(color: foregroundColor);
|
||||
final IconThemeData iconTheme = IconTheme.of(context).copyWith(color: foregroundColor);
|
||||
final TextStyle textStyle = (
|
||||
widget.sizeStyle == CupertinoButtonSize.small
|
||||
? themeData.textTheme.actionSmallTextStyle
|
||||
: themeData.textTheme.actionTextStyle
|
||||
).copyWith(color: foregroundColor);
|
||||
final IconThemeData iconTheme = IconTheme.of(context).copyWith(
|
||||
color: foregroundColor,
|
||||
size: textStyle.fontSize != null
|
||||
? textStyle.fontSize! * 1.2
|
||||
: kCupertinoButtonDefaultIconSize,
|
||||
);
|
||||
|
||||
return MouseRegion(
|
||||
cursor: enabled && kIsWeb ? SystemMouseCursors.click : MouseCursor.defer,
|
||||
@ -311,12 +382,10 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
|
||||
child: Semantics(
|
||||
button: true,
|
||||
child: ConstrainedBox(
|
||||
constraints: widget.minSize == null
|
||||
? const BoxConstraints()
|
||||
: BoxConstraints(
|
||||
minWidth: widget.minSize!,
|
||||
minHeight: widget.minSize!,
|
||||
),
|
||||
constraints: BoxConstraints(
|
||||
minWidth: widget.minSize ?? kCupertinoButtonMinSize[widget.sizeStyle] ?? kMinInteractiveDimensionCupertino,
|
||||
minHeight: widget.minSize ?? kCupertinoButtonMinSize[widget.sizeStyle] ?? kMinInteractiveDimensionCupertino,
|
||||
),
|
||||
child: FadeTransition(
|
||||
opacity: _opacityAnimation,
|
||||
child: DecoratedBox(
|
||||
@ -330,15 +399,13 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
|
||||
),
|
||||
)
|
||||
: null,
|
||||
borderRadius: widget.borderRadius,
|
||||
borderRadius: widget.borderRadius ?? kCupertinoButtonSizeBorderRadius[widget.sizeStyle],
|
||||
color: backgroundColor != null && !enabled
|
||||
? CupertinoDynamicColor.resolve(widget.disabledColor, context)
|
||||
: backgroundColor,
|
||||
),
|
||||
child: Padding(
|
||||
padding: widget.padding ?? (backgroundColor != null
|
||||
? _kBackgroundButtonPadding
|
||||
: _kButtonPadding),
|
||||
padding: widget.padding ?? kCupertinoButtonPadding[widget.sizeStyle]!,
|
||||
child: Align(
|
||||
alignment: widget.alignment,
|
||||
widthFactor: 1.0,
|
||||
|
@ -5,6 +5,10 @@
|
||||
/// @docImport 'package:flutter/material.dart';
|
||||
library;
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'button.dart';
|
||||
|
||||
/// The minimum dimension of any interactive region according to the iOS Human
|
||||
/// Interface Guidelines.
|
||||
///
|
||||
@ -31,3 +35,55 @@ const double kMinInteractiveDimensionCupertino = 44.0;
|
||||
const double kCupertinoFocusColorOpacity = 0.80,
|
||||
kCupertinoFocusColorBrightness = 0.69,
|
||||
kCupertinoFocusColorSaturation = 0.835;
|
||||
|
||||
/// Opacity values for the background of a [CupertinoButton.tinted].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * <https://developer.apple.com/design/human-interface-guidelines/buttons#iOS-iPadOS>
|
||||
const double kCupertinoButtonTintedOpacityLight = 0.12,
|
||||
kCupertinoButtonTintedOpacityDark = 0.26;
|
||||
|
||||
/// The default value for [IconThemeData.size] of [CupertinoButton.child].
|
||||
///
|
||||
/// Set to match the most-frequent size of icons in iOS (matches md/lg).
|
||||
///
|
||||
/// Used only when the [CupertinoTextThemeData.actionTextStyle] or [CupertinoTextThemeData.actionSmallTextStyle]
|
||||
/// has a null [TextStyle.fontSize].
|
||||
const double kCupertinoButtonDefaultIconSize = 20.0;
|
||||
|
||||
/// The padding values for the different [CupertinoButtonSize]s.
|
||||
///
|
||||
/// Based on the iOS (17) [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/buttons#iOS-iPadOS).
|
||||
const Map<CupertinoButtonSize, EdgeInsetsGeometry> kCupertinoButtonPadding = <CupertinoButtonSize, EdgeInsetsGeometry>{
|
||||
CupertinoButtonSize.small: EdgeInsets.symmetric(
|
||||
vertical: 6,
|
||||
horizontal: 12,
|
||||
),
|
||||
CupertinoButtonSize.medium: EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 15,
|
||||
),
|
||||
CupertinoButtonSize.large: EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
horizontal: 20,
|
||||
),
|
||||
};
|
||||
|
||||
/// The border radius values for the different [CupertinoButtonSize]s.
|
||||
///
|
||||
/// Based on the iOS (17) [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/buttons#iOS-iPadOS).
|
||||
final Map<CupertinoButtonSize, BorderRadius> kCupertinoButtonSizeBorderRadius = <CupertinoButtonSize, BorderRadius>{
|
||||
CupertinoButtonSize.small: BorderRadius.circular(40),
|
||||
CupertinoButtonSize.medium: BorderRadius.circular(40),
|
||||
CupertinoButtonSize.large: BorderRadius.circular(12),
|
||||
};
|
||||
|
||||
/// The minimum size of a [CupertinoButton] based on the [CupertinoButtonSize].
|
||||
///
|
||||
/// Based on the iOS (17) [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/buttons#iOS-iPadOS).
|
||||
const Map<CupertinoButtonSize, double> kCupertinoButtonMinSize = <CupertinoButtonSize, double>{
|
||||
CupertinoButtonSize.small: 28,
|
||||
CupertinoButtonSize.medium: 32,
|
||||
CupertinoButtonSize.large: 44,
|
||||
};
|
||||
|
@ -133,7 +133,6 @@ class _CupertinoTextSelectionToolbarButtonState extends State<CupertinoTextSelec
|
||||
color: isPressed
|
||||
? _kToolbarPressedColor.resolveFrom(context)
|
||||
: CupertinoColors.transparent,
|
||||
borderRadius: null,
|
||||
disabledColor: CupertinoColors.transparent,
|
||||
// This CupertinoButton does not actually handle the onPressed callback,
|
||||
// this is only here to correctly enable/disable the button (see
|
||||
|
@ -30,6 +30,7 @@ const TextStyle _kDefaultTextStyle = TextStyle(
|
||||
// field.
|
||||
//
|
||||
// Values derived from https://developer.apple.com/design/resources/.
|
||||
// See [iOS 17 + iPadOS 17 UI Kit](https://www.figma.com/community/file/1248375255495415511) for details.
|
||||
const TextStyle _kDefaultActionTextStyle = TextStyle(
|
||||
inherit: false,
|
||||
fontFamily: 'CupertinoSystemText',
|
||||
@ -39,6 +40,21 @@ const TextStyle _kDefaultActionTextStyle = TextStyle(
|
||||
decoration: TextDecoration.none,
|
||||
);
|
||||
|
||||
// Please update _TextThemeDefaultsBuilder accordingly after changing the default
|
||||
// color here, as their implementation depends on the default value of the color
|
||||
// field.
|
||||
//
|
||||
// Values derived from https://developer.apple.com/design/resources/.
|
||||
// See [iOS 17 + iPadOS 17 UI Kit](https://www.figma.com/community/file/1248375255495415511) for details.
|
||||
const TextStyle _kDefaultActionSmallTextStyle = TextStyle(
|
||||
inherit: false,
|
||||
fontFamily: 'CupertinoSystemText',
|
||||
fontSize: 15.0,
|
||||
letterSpacing: -0.23,
|
||||
color: CupertinoColors.activeBlue,
|
||||
decoration: TextDecoration.none,
|
||||
);
|
||||
|
||||
// Please update _TextThemeDefaultsBuilder accordingly after changing the default
|
||||
// color here, as their implementation depends on the default value of the color
|
||||
// field.
|
||||
@ -131,6 +147,7 @@ class CupertinoTextThemeData with Diagnosticable {
|
||||
Color primaryColor = CupertinoColors.systemBlue,
|
||||
TextStyle? textStyle,
|
||||
TextStyle? actionTextStyle,
|
||||
TextStyle? actionSmallTextStyle,
|
||||
TextStyle? tabLabelTextStyle,
|
||||
TextStyle? navTitleTextStyle,
|
||||
TextStyle? navLargeTitleTextStyle,
|
||||
@ -142,6 +159,7 @@ class CupertinoTextThemeData with Diagnosticable {
|
||||
primaryColor,
|
||||
textStyle,
|
||||
actionTextStyle,
|
||||
actionSmallTextStyle,
|
||||
tabLabelTextStyle,
|
||||
navTitleTextStyle,
|
||||
navLargeTitleTextStyle,
|
||||
@ -155,6 +173,7 @@ class CupertinoTextThemeData with Diagnosticable {
|
||||
this._primaryColor,
|
||||
this._textStyle,
|
||||
this._actionTextStyle,
|
||||
this._actionSmallTextStyle,
|
||||
this._tabLabelTextStyle,
|
||||
this._navTitleTextStyle,
|
||||
this._navLargeTitleTextStyle,
|
||||
@ -176,6 +195,12 @@ class CupertinoTextThemeData with Diagnosticable {
|
||||
return _actionTextStyle ?? _defaults.actionTextStyle(primaryColor: _primaryColor);
|
||||
}
|
||||
|
||||
final TextStyle? _actionSmallTextStyle;
|
||||
/// The [TextStyle] of interactive text content such as text in a small button.
|
||||
TextStyle get actionSmallTextStyle {
|
||||
return _actionSmallTextStyle ?? _defaults.actionSmallTextStyle(primaryColor: _primaryColor);
|
||||
}
|
||||
|
||||
final TextStyle? _tabLabelTextStyle;
|
||||
/// The [TextStyle] of unselected tabs.
|
||||
TextStyle get tabLabelTextStyle => _tabLabelTextStyle ?? _defaults.tabLabelTextStyle;
|
||||
@ -216,6 +241,7 @@ class CupertinoTextThemeData with Diagnosticable {
|
||||
CupertinoDynamicColor.maybeResolve(_primaryColor, context),
|
||||
_resolveTextStyle(_textStyle, context),
|
||||
_resolveTextStyle(_actionTextStyle, context),
|
||||
_resolveTextStyle(_actionSmallTextStyle, context),
|
||||
_resolveTextStyle(_tabLabelTextStyle, context),
|
||||
_resolveTextStyle(_navTitleTextStyle, context),
|
||||
_resolveTextStyle(_navLargeTitleTextStyle, context),
|
||||
@ -231,6 +257,7 @@ class CupertinoTextThemeData with Diagnosticable {
|
||||
Color? primaryColor,
|
||||
TextStyle? textStyle,
|
||||
TextStyle? actionTextStyle,
|
||||
TextStyle? actionSmallTextStyle,
|
||||
TextStyle? tabLabelTextStyle,
|
||||
TextStyle? navTitleTextStyle,
|
||||
TextStyle? navLargeTitleTextStyle,
|
||||
@ -243,6 +270,7 @@ class CupertinoTextThemeData with Diagnosticable {
|
||||
primaryColor ?? _primaryColor,
|
||||
textStyle ?? _textStyle,
|
||||
actionTextStyle ?? _actionTextStyle,
|
||||
actionSmallTextStyle ?? _actionSmallTextStyle,
|
||||
tabLabelTextStyle ?? _tabLabelTextStyle,
|
||||
navTitleTextStyle ?? _navTitleTextStyle,
|
||||
navLargeTitleTextStyle ?? _navLargeTitleTextStyle,
|
||||
@ -258,6 +286,7 @@ class CupertinoTextThemeData with Diagnosticable {
|
||||
const CupertinoTextThemeData defaultData = CupertinoTextThemeData();
|
||||
properties.add(DiagnosticsProperty<TextStyle>('textStyle', textStyle, defaultValue: defaultData.textStyle));
|
||||
properties.add(DiagnosticsProperty<TextStyle>('actionTextStyle', actionTextStyle, defaultValue: defaultData.actionTextStyle));
|
||||
properties.add(DiagnosticsProperty<TextStyle>('actionSmallTextStyle', actionSmallTextStyle, defaultValue: defaultData.actionSmallTextStyle));
|
||||
properties.add(DiagnosticsProperty<TextStyle>('tabLabelTextStyle', tabLabelTextStyle, defaultValue: defaultData.tabLabelTextStyle));
|
||||
properties.add(DiagnosticsProperty<TextStyle>('navTitleTextStyle', navTitleTextStyle, defaultValue: defaultData.navTitleTextStyle));
|
||||
properties.add(DiagnosticsProperty<TextStyle>('navLargeTitleTextStyle', navLargeTitleTextStyle, defaultValue: defaultData.navLargeTitleTextStyle));
|
||||
@ -279,6 +308,7 @@ class CupertinoTextThemeData with Diagnosticable {
|
||||
&& other._primaryColor == _primaryColor
|
||||
&& other._textStyle == _textStyle
|
||||
&& other._actionTextStyle == _actionTextStyle
|
||||
&& other._actionSmallTextStyle == _actionSmallTextStyle
|
||||
&& other._tabLabelTextStyle == _tabLabelTextStyle
|
||||
&& other._navTitleTextStyle == _navTitleTextStyle
|
||||
&& other._navLargeTitleTextStyle == _navLargeTitleTextStyle
|
||||
@ -293,6 +323,7 @@ class CupertinoTextThemeData with Diagnosticable {
|
||||
_primaryColor,
|
||||
_textStyle,
|
||||
_actionTextStyle,
|
||||
_actionSmallTextStyle,
|
||||
_tabLabelTextStyle,
|
||||
_navTitleTextStyle,
|
||||
_navLargeTitleTextStyle,
|
||||
@ -327,6 +358,7 @@ class _TextThemeDefaultsBuilder {
|
||||
TextStyle get dateTimePickerTextStyle => _applyLabelColor(_kDefaultDateTimePickerTextStyle, labelColor);
|
||||
|
||||
TextStyle actionTextStyle({ Color? primaryColor }) => _kDefaultActionTextStyle.copyWith(color: primaryColor);
|
||||
TextStyle actionSmallTextStyle({ Color? primaryColor }) => _kDefaultActionSmallTextStyle.copyWith(color: primaryColor);
|
||||
TextStyle navActionTextStyle({ Color? primaryColor }) => actionTextStyle(primaryColor: primaryColor);
|
||||
|
||||
_TextThemeDefaultsBuilder resolveFrom(BuildContext context) {
|
||||
|
@ -27,8 +27,8 @@ void main() {
|
||||
final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
|
||||
expect(
|
||||
buttonBox.size,
|
||||
// 1 10px character + 16px * 2 is smaller than the default 44px minimum.
|
||||
const Size.square(44.0),
|
||||
// 1 10px character + 20px * 2 = 50.0
|
||||
const Size(50.0, 44.0),
|
||||
);
|
||||
});
|
||||
|
||||
@ -44,7 +44,7 @@ void main() {
|
||||
final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
|
||||
expect(
|
||||
buttonBox.size,
|
||||
// 1 10px character + 16px * 2 is smaller than defined 60.0px minimum
|
||||
// 1 10px character + 20px * 2 = 50.0 (is smaller than minSize: 60.0)
|
||||
const Size.square(minSize),
|
||||
);
|
||||
});
|
||||
@ -59,8 +59,8 @@ void main() {
|
||||
final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
|
||||
expect(
|
||||
buttonBox.size.width,
|
||||
// 4 10px character + 16px * 2 = 72.
|
||||
72.0,
|
||||
// 4 10px character + 20px * 2 = 80.0
|
||||
80.0,
|
||||
);
|
||||
});
|
||||
|
||||
@ -129,17 +129,37 @@ void main() {
|
||||
expect(align.alignment, Alignment.centerLeft);
|
||||
});
|
||||
|
||||
testWidgets('Button with background is wider', (WidgetTester tester) async {
|
||||
testWidgets('Button size changes depending on size property', (WidgetTester tester) async {
|
||||
const Widget child = Text('X', style: testStyle);
|
||||
|
||||
await tester.pumpWidget(boilerplate(child: const CupertinoButton(
|
||||
onPressed: null,
|
||||
color: Color(0xFFFFFFFF),
|
||||
child: Text('X', style: testStyle),
|
||||
sizeStyle: CupertinoButtonSize.small,
|
||||
child: child,
|
||||
)));
|
||||
final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
|
||||
expect(
|
||||
buttonBox.size.width,
|
||||
// 1 10px character + 64 * 2 = 138 for buttons with background.
|
||||
138.0,
|
||||
buttonBox.size,
|
||||
const Size(34.0, 28.0)
|
||||
);
|
||||
|
||||
await tester.pumpWidget(boilerplate(child: const CupertinoButton(
|
||||
onPressed: null,
|
||||
sizeStyle: CupertinoButtonSize.medium,
|
||||
child: child,
|
||||
)));
|
||||
expect(
|
||||
buttonBox.size,
|
||||
const Size(40.0, 32.0),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(boilerplate(child: const CupertinoButton(
|
||||
onPressed: null,
|
||||
child: child,
|
||||
)));
|
||||
expect(
|
||||
buttonBox.size,
|
||||
const Size(50.0, 44.0),
|
||||
);
|
||||
});
|
||||
|
||||
@ -404,8 +424,27 @@ void main() {
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(textStyle.color, isSameColorAs(CupertinoColors.activeBlue));
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: CupertinoButton.tinted(
|
||||
onPressed: () { },
|
||||
child: Builder(builder: (BuildContext context) {
|
||||
textStyle = DefaultTextStyle.of(context).style;
|
||||
return const Placeholder();
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(textStyle.color, CupertinoColors.activeBlue);
|
||||
BoxDecoration decoration = tester.widget<DecoratedBox>(
|
||||
find.descendant(
|
||||
of: find.byType(CupertinoButton),
|
||||
matching: find.byType(DecoratedBox),
|
||||
),
|
||||
).decoration as BoxDecoration;
|
||||
expect(decoration.color, isSameColorAs(CupertinoColors.activeBlue.withOpacity(0.12)));
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
@ -418,15 +457,14 @@ void main() {
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(textStyle.color, isSameColorAs(CupertinoColors.white));
|
||||
BoxDecoration decoration = tester.widget<DecoratedBox>(
|
||||
decoration = tester.widget<DecoratedBox>(
|
||||
find.descendant(
|
||||
of: find.byType(CupertinoButton),
|
||||
matching: find.byType(DecoratedBox),
|
||||
),
|
||||
).decoration as BoxDecoration;
|
||||
expect(decoration.color, CupertinoColors.activeBlue);
|
||||
expect(decoration.color, isSameColorAs(CupertinoColors.activeBlue));
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
@ -442,6 +480,27 @@ void main() {
|
||||
);
|
||||
expect(textStyle.color, isSameColorAs(CupertinoColors.systemBlue.darkColor));
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
theme: const CupertinoThemeData(brightness: Brightness.dark),
|
||||
home: CupertinoButton.tinted(
|
||||
onPressed: () { },
|
||||
child: Builder(builder: (BuildContext context) {
|
||||
textStyle = DefaultTextStyle.of(context).style;
|
||||
return const Placeholder();
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(textStyle.color, isSameColorAs(CupertinoColors.systemBlue.darkColor));
|
||||
decoration = tester.widget<DecoratedBox>(
|
||||
find.descendant(
|
||||
of: find.byType(CupertinoButton),
|
||||
matching: find.byType(DecoratedBox),
|
||||
),
|
||||
).decoration as BoxDecoration;
|
||||
expect(decoration.color, isSameColorAs(CupertinoColors.activeBlue.darkColor.withOpacity(0.26)));
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
theme: const CupertinoThemeData(brightness: Brightness.dark),
|
||||
@ -464,6 +523,14 @@ void main() {
|
||||
expect(decoration.color, isSameColorAs(CupertinoColors.systemBlue.darkColor));
|
||||
});
|
||||
|
||||
testWidgets("All CupertinoButton const maps keys' match the available style sizes", (WidgetTester tester) async {
|
||||
for (final CupertinoButtonSize size in CupertinoButtonSize.values) {
|
||||
expect(kCupertinoButtonPadding[size], isNotNull);
|
||||
expect(kCupertinoButtonSizeBorderRadius[size], isNotNull);
|
||||
expect(kCupertinoButtonMinSize[size], isNotNull);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('Hovering over Cupertino button updates cursor to clickable on Web', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
@ -612,32 +679,57 @@ void main() {
|
||||
expect(focusNode.hasFocus, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('IconThemeData is not replaced by CupertinoButton', (WidgetTester tester) async {
|
||||
const IconThemeData givenIconTheme = IconThemeData(size: 12.0);
|
||||
testWidgets('IconThemeData falls back to default value when the TextStyle has a null size', (WidgetTester tester) async {
|
||||
const IconThemeData defaultIconTheme = IconThemeData(size: kCupertinoButtonDefaultIconSize);
|
||||
|
||||
IconThemeData? actualIconTheme;
|
||||
|
||||
// Large size.
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
theme: const CupertinoThemeData(
|
||||
textTheme: CupertinoTextThemeData(
|
||||
actionTextStyle: TextStyle(),
|
||||
),
|
||||
),
|
||||
home: Center(
|
||||
child: IconTheme(
|
||||
data: givenIconTheme,
|
||||
child: CupertinoButton(
|
||||
onPressed: () {},
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
actualIconTheme = IconTheme.of(context);
|
||||
child: CupertinoButton(
|
||||
onPressed: () {},
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
actualIconTheme = IconTheme.of(context);
|
||||
|
||||
return const Placeholder();
|
||||
}
|
||||
),
|
||||
return const Placeholder();
|
||||
}
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(actualIconTheme?.size, defaultIconTheme.size);
|
||||
|
||||
expect(actualIconTheme?.size, givenIconTheme.size);
|
||||
// Small size.
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
theme: const CupertinoThemeData(
|
||||
textTheme: CupertinoTextThemeData(
|
||||
actionSmallTextStyle: TextStyle(),
|
||||
),
|
||||
),
|
||||
home: Center(
|
||||
child: CupertinoButton(
|
||||
onPressed: () {},
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
actualIconTheme = IconTheme.of(context);
|
||||
|
||||
return const Placeholder();
|
||||
}
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,12 @@ void main() {
|
||||
expect(theme.actionTextStyle.letterSpacing, -0.41);
|
||||
expect(theme.actionTextStyle.fontWeight, null);
|
||||
|
||||
// ActionSmallTextStyle 15 -0.23 (aka "Subheadline/Regular")
|
||||
expect(theme.actionSmallTextStyle.fontSize, 15);
|
||||
expect(theme.actionSmallTextStyle.fontFamily, 'CupertinoSystemText');
|
||||
expect(theme.actionSmallTextStyle.letterSpacing, -0.23);
|
||||
expect(theme.actionSmallTextStyle.fontWeight, null);
|
||||
|
||||
// TextStyle 17 -0.41
|
||||
expect(theme.tabLabelTextStyle.fontSize, 10);
|
||||
expect(theme.tabLabelTextStyle.fontFamily, 'CupertinoSystemText');
|
||||
|
@ -189,6 +189,7 @@ void main() {
|
||||
'applyThemeToAll',
|
||||
'textStyle',
|
||||
'actionTextStyle',
|
||||
'actionSmallTextStyle',
|
||||
'tabLabelTextStyle',
|
||||
'navTitleTextStyle',
|
||||
'navLargeTitleTextStyle',
|
||||
|
Loading…
Reference in New Issue
Block a user