Unverified Commit 4dbbf678 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Adding ChipTheme, ChipThemeData, and some more tests. (#16447)

This converts the chips to use a ChipThemeData to get most of their customization values from (if not overridden by specific arguments to the chip constructors), and to have the base ThemeData contain one of these. It also adds the ChipTheme widget that will allow overriding the theme for a particular subtree in the widget hierarchy.

Added tests for both, and just more tests in general for the Chips.
parent c31706f9
......@@ -30,6 +30,7 @@ export 'src/material/card.dart';
export 'src/material/checkbox.dart';
export 'src/material/checkbox_list_tile.dart';
export 'src/material/chip.dart';
export 'src/material/chip_theme.dart';
export 'src/material/circle_avatar.dart';
export 'src/material/colors.dart';
export 'src/material/constants.dart';
......
......@@ -10,6 +10,7 @@ import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'chip_theme.dart';
import 'colors.dart';
import 'debug.dart';
import 'icons.dart';
......@@ -22,25 +23,20 @@ import 'tooltip.dart';
// Some design constants
const double _kChipHeight = 32.0;
const double _kDeleteIconSize = 18.0;
const int _kTextLabelAlpha = 0xde; // 87%
const double _kDeleteIconOpacity = 0.87;
const EdgeInsetsGeometry _kDefaultPadding = const EdgeInsets.all(4.0);
const EdgeInsetsGeometry _kDefaultLabelPadding = const EdgeInsets.symmetric(horizontal: 8.0);
const EdgeInsetsGeometry _kDefaultAvatarPadding = EdgeInsets.zero;
const EdgeInsetsGeometry _kDefaultDeleteIconPadding = EdgeInsets.zero;
const int _kDisabledAlpha = 0x5e; // 36%
const int _kCheckmarkAlpha = 0xde; // 87%
const int _kDisabledAlpha = 0x61; // 38%
const double _kCheckmarkStrokeWidth = 2.0;
const double _kPressElevation = 8.0;
const Duration _kSelectDuration = const Duration(milliseconds: 195);
const Duration _kCheckmarkDuration = const Duration(milliseconds: 150);
const Duration _kCheckmarkReverseDuration = const Duration(milliseconds: 50);
const Duration _kDrawerDuration = const Duration(milliseconds: 150);
const Duration _kReverseDrawerDuration = const Duration(milliseconds: 100);
const Duration _kDisableDuration = const Duration(milliseconds: 75);
const Color _kSelectScrimColor = const Color(0x60191919);
const Color _kDefaultSelectedColor = const Color(0x30000000); // 19% black
const Color _kDefaultBackgroundColor = const Color(0x14000000); // 8% black
const Color _kDefaultDisabledColor = const Color(0x08000000); // 3% black
const Icon _kDefaultDeleteIcon = const Icon(Icons.cancel, size: _kDeleteIconSize);
/// An interface defining the base attributes for a material design chip.
......@@ -86,7 +82,7 @@ abstract class ChipAttributes {
/// The [ShapeBorder] to draw around the chip.
///
/// Defaults to a [StadiumBorder]. Must not be null.
/// Defaults to the shape in the ambient [ChipThemeData].
ShapeBorder get shape;
/// Color to be used for the unselected, enabled chip's background.
......@@ -104,11 +100,6 @@ abstract class ChipAttributes {
/// By default, this is 4 logical pixels at the beginning and the end of the
/// label, and zero on top and bottom.
EdgeInsetsGeometry get labelPadding;
/// The padding around the [avatar] widget.
///
/// By default, this is zero on all sides.
EdgeInsetsGeometry get avatarPadding;
}
/// An interface for material design chips that can be deleted.
......@@ -199,9 +190,6 @@ abstract class DeletableChipAttributes {
/// The message to be used for the chip's delete button tooltip.
String get deleteButtonTooltipMessage;
/// The padding around the [deleteIcon] widget.
EdgeInsetsGeometry get deleteIconPadding;
}
/// An interface for material design chips that can be selected.
......@@ -422,24 +410,21 @@ abstract class TappableChipAttributes {
class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttributes {
/// Creates a material design chip.
///
/// The [label], [deleteIcon], and [border] arguments must not be null.
/// The [label] argument must not be null.
const Chip({
Key key,
this.avatar,
@required this.label,
this.labelStyle,
this.labelPadding,
this.avatarPadding,
this.deleteIconPadding,
this.deleteIcon,
this.onDeleted,
this.deleteIconColor,
this.deleteButtonTooltipMessage,
this.shape: const StadiumBorder(),
this.shape,
this.backgroundColor,
this.padding,
}) : assert(label != null),
assert(shape != null),
super(key: key);
@override
......@@ -451,16 +436,12 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri
@override
final EdgeInsetsGeometry labelPadding;
@override
final EdgeInsetsGeometry avatarPadding;
@override
final ShapeBorder shape;
@override
final Color backgroundColor;
@override
final EdgeInsetsGeometry padding;
@override
final EdgeInsetsGeometry deleteIconPadding;
@override
final Widget deleteIcon;
@override
final VoidCallback onDeleted;
......@@ -477,8 +458,6 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri
label: label,
labelStyle: labelStyle,
labelPadding: labelPadding,
avatarPadding: avatarPadding,
deleteIconPadding: deleteIconPadding,
deleteIcon: deleteIcon,
onDeleted: onDeleted,
deleteIconColor: deleteIconColor,
......@@ -548,16 +527,13 @@ class InputChip extends StatelessWidget
/// The [onPressed] and [onSelected] callbacks must not both be specified at
/// the same time.
///
/// The [label], [isEnabled], [selected], and [border] arguments must not be
/// null.
/// The [label], [isEnabled] and [selected] arguments must not be null.
const InputChip({
Key key,
this.avatar,
@required this.label,
this.labelStyle,
this.labelPadding,
this.avatarPadding,
this.deleteIconPadding,
this.selected: false,
this.isEnabled: true,
this.onSelected,
......@@ -569,13 +545,12 @@ class InputChip extends StatelessWidget
this.disabledColor,
this.selectedColor,
this.tooltip,
this.shape: const StadiumBorder(),
this.shape,
this.backgroundColor,
this.padding,
}) : assert(selected != null),
assert(isEnabled != null),
assert(label != null),
assert(shape != null),
super(key: key);
@override
......@@ -587,10 +562,6 @@ class InputChip extends StatelessWidget
@override
final EdgeInsetsGeometry labelPadding;
@override
final EdgeInsetsGeometry avatarPadding;
@override
final EdgeInsetsGeometry deleteIconPadding;
@override
final bool selected;
@override
final bool isEnabled;
......@@ -627,8 +598,6 @@ class InputChip extends StatelessWidget
label: label,
labelStyle: labelStyle,
labelPadding: labelPadding,
avatarPadding: avatarPadding,
deleteIconPadding: deleteIconPadding,
deleteIcon: deleteIcon,
onDeleted: onDeleted,
deleteIconColor: deleteIconColor,
......@@ -706,25 +675,23 @@ class ChoiceChip extends StatelessWidget
DisabledChipAttributes {
/// Create a chip that acts like a radio button.
///
/// The [selected], [label], and [border] arguments must not be null.
/// The [label] and [selected] attributes must not be null.
const ChoiceChip({
Key key,
this.avatar,
@required this.label,
this.labelStyle,
this.labelPadding,
this.avatarPadding,
this.onSelected,
@required this.selected,
this.selectedColor,
this.disabledColor,
this.tooltip,
this.shape: const StadiumBorder(),
this.shape,
this.backgroundColor,
this.padding,
}) : assert(selected != null),
assert(label != null),
assert(shape != null),
super(key: key);
@override
......@@ -736,8 +703,6 @@ class ChoiceChip extends StatelessWidget
@override
final EdgeInsetsGeometry labelPadding;
@override
final EdgeInsetsGeometry avatarPadding;
@override
final ValueChanged<bool> onSelected;
@override
final bool selected;
......@@ -760,12 +725,12 @@ class ChoiceChip extends StatelessWidget
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
final ChipThemeData chipTheme = ChipTheme.of(context);
return new RawChip(
avatar: avatar,
label: label,
labelStyle: labelStyle,
labelStyle: labelStyle ?? (selected ? chipTheme.secondaryLabelStyle : null),
labelPadding: labelPadding,
avatarPadding: avatarPadding,
onSelected: onSelected,
selected: selected,
showCheckmark: false,
......@@ -773,7 +738,7 @@ class ChoiceChip extends StatelessWidget
tooltip: tooltip,
shape: shape,
disabledColor: disabledColor,
selectedColor: selectedColor,
selectedColor: selectedColor ?? chipTheme.secondarySelectedColor,
backgroundColor: backgroundColor,
padding: padding,
isEnabled: isEnabled,
......@@ -789,8 +754,7 @@ class ChoiceChip extends StatelessWidget
/// Unlike these alternatives, filter chips allow for clearly delineated and
/// exposed options in a compact area.
///
/// Requires one of its ancestors to be a [Material] widget. The [label]
/// and [border] arguments must not be null.
/// Requires one of its ancestors to be a [Material] widget.
///
/// ## Sample code
///
......@@ -874,25 +838,23 @@ class FilterChip extends StatelessWidget
DisabledChipAttributes {
/// Create a chip that acts like a checkbox.
///
/// The [selected], [label], and [border] arguments must not be null.
/// The [selected] and [label] attributes must not be null.
const FilterChip({
Key key,
this.avatar,
@required this.label,
this.labelStyle,
this.labelPadding,
this.avatarPadding,
this.selected: false,
@required this.onSelected,
this.disabledColor,
this.selectedColor,
this.tooltip,
this.shape: const StadiumBorder(),
this.shape,
this.backgroundColor,
this.padding,
}) : assert(selected != null),
assert(label != null),
assert(shape != null),
super(key: key);
@override
......@@ -904,8 +866,6 @@ class FilterChip extends StatelessWidget
@override
final EdgeInsetsGeometry labelPadding;
@override
final EdgeInsetsGeometry avatarPadding;
@override
final bool selected;
@override
final ValueChanged<bool> onSelected;
......@@ -933,7 +893,6 @@ class FilterChip extends StatelessWidget
label: label,
labelStyle: labelStyle,
labelPadding: labelPadding,
avatarPadding: avatarPadding,
onSelected: onSelected,
selected: selected,
tooltip: tooltip,
......@@ -964,8 +923,7 @@ class FilterChip extends StatelessWidget
/// [OutlineButton], are an alternative to action chips, which should appear
/// statically and consistently in a UI.
///
/// Requires one of its ancestors to be a [Material] widget. The [onPressed],
/// [label], and [border] arguments must not be null.
/// Requires one of its ancestors to be a [Material] widget.
///
/// ## Sample code
///
......@@ -997,21 +955,19 @@ class FilterChip extends StatelessWidget
class ActionChip extends StatelessWidget implements ChipAttributes, TappableChipAttributes {
/// Create a chip that acts like a button.
///
/// The [label], [border], and [onPressed] arguments must not be null.
/// The [label] and [onPressed] arguments must not be null.
const ActionChip({
Key key,
this.avatar,
@required this.label,
this.labelStyle,
this.labelPadding,
this.avatarPadding,
@required this.onPressed,
this.tooltip,
this.shape: const StadiumBorder(),
this.shape,
this.backgroundColor,
this.padding,
}) : assert(label != null),
assert(shape != null),
assert(
onPressed != null,
'Rather than disabling an ActionChip by setting onPressed to null, '
......@@ -1028,8 +984,6 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
@override
final EdgeInsetsGeometry labelPadding;
@override
final EdgeInsetsGeometry avatarPadding;
@override
final VoidCallback onPressed;
@override
final String tooltip;
......@@ -1053,7 +1007,6 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
shape: shape,
padding: padding,
labelPadding: labelPadding,
avatarPadding: avatarPadding,
isEnabled: true,
);
}
......@@ -1101,16 +1054,14 @@ class RawChip extends StatefulWidget
/// The [onPressed] and [onSelected] callbacks must not both be specified at
/// the same time.
///
/// The [label], [isEnabled], and [border] arguments must not be null.
/// The [label] and [isEnabled] arguments must not be null.
const RawChip({
Key key,
this.avatar,
@required this.label,
this.labelStyle,
EdgeInsetsGeometry padding,
EdgeInsetsGeometry labelPadding,
EdgeInsetsGeometry avatarPadding,
EdgeInsetsGeometry deleteIconPadding,
this.padding,
this.labelPadding,
Widget deleteIcon,
this.onDeleted,
this.deleteIconColor,
......@@ -1121,22 +1072,14 @@ class RawChip extends StatefulWidget
this.selected,
this.showCheckmark: true,
this.isEnabled: true,
Color disabledColor,
Color selectedColor,
this.disabledColor,
this.selectedColor,
this.tooltip,
@required this.shape,
Color backgroundColor,
this.shape,
this.backgroundColor,
}) : assert(label != null),
assert(shape != null),
assert(isEnabled != null),
padding = padding ?? _kDefaultPadding,
labelPadding = labelPadding ?? _kDefaultLabelPadding,
avatarPadding = avatarPadding ?? _kDefaultAvatarPadding,
deleteIconPadding = deleteIconPadding ?? _kDefaultDeleteIconPadding,
deleteIcon = deleteIcon ?? _kDefaultDeleteIcon,
disabledColor = disabledColor ?? _kDefaultDisabledColor,
selectedColor = selectedColor ?? _kDefaultSelectedColor,
backgroundColor = backgroundColor ?? _kDefaultBackgroundColor,
super(key: key);
@override
......@@ -1148,10 +1091,6 @@ class RawChip extends StatefulWidget
@override
final EdgeInsetsGeometry labelPadding;
@override
final EdgeInsetsGeometry avatarPadding;
@override
final EdgeInsetsGeometry deleteIconPadding;
@override
final Widget deleteIcon;
@override
final VoidCallback onDeleted;
......@@ -1337,14 +1276,14 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
/// Picks between three different colors, depending upon the state of two
/// different animations.
Color get backgroundColor {
Color getBackgroundColor(ChipThemeData theme) {
final ColorTween backgroundTween = new ColorTween(
begin: widget.disabledColor,
end: widget.backgroundColor,
begin: widget.disabledColor ?? theme.disabledColor,
end: widget.backgroundColor ?? theme.backgroundColor,
);
final ColorTween selectTween = new ColorTween(
begin: backgroundTween.evaluate(enableController),
end: widget.selectedColor,
end: widget.selectedColor ?? theme.selectedColor,
);
return selectTween.evaluate(selectionFade);
}
......@@ -1400,7 +1339,7 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
);
}
Widget _buildDeleteIcon(BuildContext context, ThemeData theme) {
Widget _buildDeleteIcon(BuildContext context, ThemeData theme, ChipThemeData chipTheme) {
if (!hasDeleteButton) {
return null;
}
......@@ -1409,13 +1348,12 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
onTap: widget.isEnabled ? widget.onDeleted : null,
child: new IconTheme(
data: theme.iconTheme.copyWith(
color: widget.deleteIconColor ?? theme.iconTheme.color,
opacity: _kDeleteIconOpacity,
color: (widget.deleteIconColor ?? chipTheme.deleteIconColor) ?? theme.iconTheme.color,
),
child: widget.deleteIcon,
),
),
widget.deleteButtonTooltipMessage ?? MaterialLocalizations.of(context).deleteButtonTooltip,
widget.deleteButtonTooltipMessage ?? MaterialLocalizations.of(context)?.deleteButtonTooltip,
widget.onDeleted,
);
}
......@@ -1423,12 +1361,18 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
assert(debugCheckHasMediaQuery(context));
assert(debugCheckHasDirectionality(context));
final ThemeData theme = Theme.of(context);
final ChipThemeData chipTheme = ChipTheme.of(context);
final TextDirection textDirection = Directionality.of(context);
final ShapeBorder shape = widget.shape ?? chipTheme.shape;
return new Material(
elevation: isTapping ? _kPressElevation : 0.0,
animationDuration: pressedAnimationDuration,
shape: widget.shape,
shape: shape,
child: new InkResponse(
onTap: canTap ? _handleTap : null,
onTapDown: canTap ? _handleTapDown : null,
......@@ -1438,8 +1382,8 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
builder: (BuildContext context, Widget child) {
return new Container(
decoration: new ShapeDecoration(
shape: widget.shape,
color: backgroundColor,
shape: shape,
color: getBackgroundColor(chipTheme),
),
child: child,
);
......@@ -1452,10 +1396,7 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
textAlign: TextAlign.start,
maxLines: 1,
softWrap: false,
style: widget.labelStyle ??
theme.textTheme.body2.copyWith(
color: Colors.black.withAlpha(_kTextLabelAlpha),
),
style: widget.labelStyle ?? chipTheme.labelStyle,
child: widget.label,
),
avatar: new AnimatedChildSwitcher(
......@@ -1464,14 +1405,13 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
switchInCurve: Curves.fastOutSlowIn,
),
deleteIcon: new AnimatedChildSwitcher(
child: _buildDeleteIcon(context, theme),
child: _buildDeleteIcon(context, theme, chipTheme),
duration: _kDrawerDuration,
switchInCurve: Curves.fastOutSlowIn,
),
padding: widget.padding?.resolve(textDirection),
labelPadding: widget.labelPadding?.resolve(textDirection),
avatarPadding: widget.avatarPadding?.resolve(textDirection),
deleteIconPadding: widget.deleteIconPadding?.resolve(textDirection),
brightness: chipTheme.brightness,
padding: (widget.padding ?? chipTheme.padding).resolve(textDirection),
labelPadding: (widget.labelPadding ?? chipTheme.labelPadding).resolve(textDirection),
showAvatar: hasAvatar,
showCheckmark: widget.showCheckmark,
canTapBody: canTap,
......@@ -1662,10 +1602,9 @@ class _ChipRenderTheme {
@required this.avatar,
@required this.label,
@required this.deleteIcon,
@required this.brightness,
@required this.padding,
@required this.labelPadding,
@required this.avatarPadding,
@required this.deleteIconPadding,
@required this.showAvatar,
@required this.showCheckmark,
@required this.canTapBody,
......@@ -1674,10 +1613,9 @@ class _ChipRenderTheme {
final Widget avatar;
final Widget label;
final Widget deleteIcon;
final Brightness brightness;
final EdgeInsets padding;
final EdgeInsets labelPadding;
final EdgeInsets avatarPadding;
final EdgeInsets deleteIconPadding;
final bool showAvatar;
final bool showCheckmark;
final bool canTapBody;
......@@ -1694,10 +1632,9 @@ class _ChipRenderTheme {
return typedOther.avatar == avatar
&& typedOther.label == label
&& typedOther.deleteIcon == deleteIcon
&& typedOther.brightness == brightness
&& typedOther.padding == padding
&& typedOther.labelPadding == labelPadding
&& typedOther.avatarPadding == avatarPadding
&& typedOther.deleteIconPadding == deleteIconPadding
&& typedOther.showAvatar == showAvatar
&& typedOther.showCheckmark == showCheckmark
&& typedOther.canTapBody == canTapBody;
......@@ -1709,10 +1646,9 @@ class _ChipRenderTheme {
avatar,
label,
deleteIcon,
brightness,
padding,
labelPadding,
avatarPadding,
deleteIconPadding,
showAvatar,
showCheckmark,
canTapBody,
......@@ -1888,9 +1824,7 @@ class _RenderChip extends RenderBox {
// because we add the padding regardless to give extra padding for the label
// when they're missing.
final double overallPadding = theme.padding.horizontal +
theme.labelPadding.horizontal +
theme.deleteIconPadding.horizontal +
theme.avatarPadding.horizontal;
theme.labelPadding.horizontal;
return overallPadding +
_minWidth(avatar, height) +
_minWidth(label, height) +
......@@ -1900,9 +1834,7 @@ class _RenderChip extends RenderBox {
@override
double computeMaxIntrinsicWidth(double height) {
final double overallPadding = theme.padding.vertical +
theme.labelPadding.horizontal +
theme.deleteIconPadding.horizontal +
theme.avatarPadding.horizontal;
theme.labelPadding.horizontal;
return overallPadding +
_maxWidth(avatar, height) +
_maxWidth(label, height) +
......@@ -1961,7 +1893,7 @@ class _RenderChip extends RenderBox {
}
Size _layoutAvatar(BoxConstraints contentConstraints, double contentSize) {
final double requestedSize = math.max(0.0, contentSize - theme.avatarPadding.vertical);
final double requestedSize = math.max(0.0, contentSize);
final BoxConstraints avatarConstraints = new BoxConstraints.tightFor(
width: requestedSize,
height: requestedSize,
......@@ -1970,8 +1902,8 @@ class _RenderChip extends RenderBox {
if (!theme.showCheckmark && !theme.showAvatar) {
return new Size(0.0, contentSize);
}
double avatarWidth = theme.avatarPadding.horizontal;
double avatarHeight = theme.avatarPadding.vertical;
double avatarWidth = 0.0;
double avatarHeight = 0.0;
final Size avatarBoxSize = _boxSize(avatar);
if (theme.showAvatar) {
avatarWidth += avatarDrawerAnimation.value * avatarBoxSize.width;
......@@ -1983,7 +1915,7 @@ class _RenderChip extends RenderBox {
}
Size _layoutDeleteIcon(BoxConstraints contentConstraints, double contentSize) {
final double requestedSize = math.max(0.0, contentSize - theme.deleteIconPadding.vertical);
final double requestedSize = math.max(0.0, contentSize);
final BoxConstraints deleteIconConstraints = new BoxConstraints.tightFor(
width: requestedSize,
height: requestedSize,
......@@ -1992,8 +1924,8 @@ class _RenderChip extends RenderBox {
if (!deleteIconShowing) {
return new Size(0.0, contentSize);
}
double deleteIconWidth = theme.deleteIconPadding.horizontal;
double deleteIconHeight = theme.deleteIconPadding.vertical;
double deleteIconWidth = 0.0;
double deleteIconHeight = 0.0;
final Size boxSize = _boxSize(deleteIcon);
deleteIconWidth += deleteDrawerAnimation.value * boxSize.width;
deleteIconHeight += boxSize.height;
......@@ -2118,9 +2050,9 @@ class _RenderChip extends RenderBox {
0.0,
((labelSize.height - theme.labelPadding.vertical) - _boxSize(label).height) / 2.0,
);
_boxParentData(avatar).offset = theme.padding.topLeft + avatarOffset + theme.avatarPadding.topLeft;
_boxParentData(avatar).offset = theme.padding.topLeft + avatarOffset;
_boxParentData(label).offset = theme.padding.topLeft + labelOffset + theme.labelPadding.topLeft;
_boxParentData(deleteIcon).offset = theme.padding.topLeft + deleteIconOffset + theme.deleteIconPadding.topLeft;
_boxParentData(deleteIcon).offset = theme.padding.topLeft + deleteIconOffset;
final Size paddedSize = new Size(
overallSize.width + theme.padding.horizontal,
overallSize.height + theme.padding.vertical,
......@@ -2136,11 +2068,6 @@ class _RenderChip extends RenderBox {
'${constraints.constrainWidth(paddedSize.width)}');
}
static final ColorTween enableTween = new ColorTween(
begin: Colors.white.withAlpha(_kDisabledAlpha),
end: Colors.white,
);
static final ColorTween selectionScrimTween = new ColorTween(
begin: Colors.transparent,
end: _kSelectScrimColor,
......@@ -2150,11 +2077,35 @@ class _RenderChip extends RenderBox {
if (enableAnimation == null || enableAnimation.isCompleted) {
return Colors.white;
}
ColorTween enableTween;
switch (theme.brightness) {
case Brightness.light:
enableTween = new ColorTween(
begin: Colors.white.withAlpha(_kDisabledAlpha),
end: Colors.white,
);
break;
case Brightness.dark:
enableTween = new ColorTween(
begin: Colors.black.withAlpha(_kDisabledAlpha),
end: Colors.black,
);
break;
}
return enableTween.evaluate(enableAnimation);
}
void _paintCheck(Canvas canvas, Offset origin, double size) {
Color paintColor = theme.showAvatar ? Colors.white : Colors.black87;
Color paintColor;
switch (theme.brightness) {
case Brightness.light:
paintColor = theme.showAvatar ? Colors.white : Colors.black.withAlpha(_kCheckmarkAlpha);
break;
case Brightness.dark:
paintColor = theme.showAvatar ? Colors.black : Colors.white.withAlpha(_kCheckmarkAlpha);
break;
}
final ColorTween fadeTween = new ColorTween(begin: Colors.transparent, end: paintColor);
paintColor = checkmarkAnimation.status == AnimationStatus.reverse
......@@ -2196,11 +2147,13 @@ class _RenderChip extends RenderBox {
void _paintSelectionOverlay(PaintingContext context, Offset offset) {
if (isDrawingCheckmark) {
if (theme.showAvatar) {
final Rect avatarRect = _boxRect(avatar).shift(offset);
final Paint darkenPaint = new Paint()
..color = selectionScrimTween.evaluate(checkmarkAnimation)
..blendMode = BlendMode.srcATop;
context.canvas.drawRect(avatarRect, darkenPaint);
}
// Need to make the check mark be a little smaller than the avatar.
final double checkSize = avatar.size.height * 0.75;
final Offset checkOffset = _boxParentData(avatar).offset +
......@@ -2218,14 +2171,15 @@ class _RenderChip extends RenderBox {
if (theme.showAvatar == false && avatarDrawerAnimation.isDismissed) {
return;
}
final int disabledColorAlpha = _disabledColor.alpha;
final Color disabledColor = _disabledColor;
final int disabledColorAlpha = disabledColor.alpha;
if (needsCompositing) {
context.pushLayer(new OpacityLayer(alpha: disabledColorAlpha), paintWithOverlay, offset);
} else {
if (disabledColorAlpha != 0xff) {
context.canvas.saveLayer(
_boxRect(avatar).shift(offset).inflate(20.0),
new Paint()..color = _disabledColor,
new Paint()..color = disabledColor,
);
}
paintWithOverlay(context, offset);
......
// Copyright 2018 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/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'theme.dart';
import 'theme_data.dart';
/// Applies a chip theme to descendant [RawChip]-based widgets, like [Chip],
/// [InputChip], [ChoiceChip], [FilterChip], and [ActionChip].
///
/// A chip theme describes the color, shape and text styles for the chips it is
/// applied to
///
/// Descendant widgets obtain the current theme's [ChipThemeData] object using
/// [ChipTheme.of]. When a widget uses [ChipTheme.of], it is automatically
/// rebuilt if the theme later changes.
///
/// The [ThemeData] object given by the [Theme.of] call also contains a default
/// [Theme.chipTheme] that can be customized by copying it (using
/// [ChipThemeData.copyWith]).
///
/// See also:
///
/// * [Chip], a chip that displays information and can be deleted.
/// * [InputChip], a chip that represents a complex piece of information, such
/// as an entity (person, place, or thing) or conversational text, in a
/// compact form.
/// * [ChoiceChip], allows a single selection from a set of options. Choice
/// chips contain related descriptive text or categories.
/// * [FilterChip], uses tags or descriptive words as a way to filter content.
/// * [ActionChip], represents an action related to primary content.
/// * [ChipThemeData], which describes the actual configuration of a chip
/// theme.
/// * [ThemeData], which describes the overall theme information for the
/// application.
class ChipTheme extends InheritedWidget {
/// Applies the given theme [data] to [child].
///
/// The [data] and [child] arguments must not be null.
const ChipTheme({
Key key,
@required this.data,
@required Widget child,
}) : assert(child != null),
assert(data != null),
super(key: key, child: child);
/// Specifies the color, shape, and text style values for descendant chip
/// widgets.
final ChipThemeData data;
/// Returns the data from the closest [ChipTheme] instance that encloses
/// the given context.
///
/// Defaults to the ambient [ThemeData.chipTheme] if there is no
/// [ChipTheme] in the given build context.
///
/// ## Sample code
///
/// ```dart
/// class Spaceship extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return new ChipTheme(
/// data: ChipTheme.of(context).copyWith(backgroundColor: Colors.red),
/// child: new ActionChip(
/// label: const Text('Launch'),
/// onPressed: () { print('We have liftoff!'); },
/// ),
/// );
/// }
/// }
/// ```
///
/// See also:
///
/// * [ChipThemeData], which describes the actual configuration of a chip
/// theme.
static ChipThemeData of(BuildContext context) {
final ChipTheme inheritedTheme = context.inheritFromWidgetOfExactType(ChipTheme);
return inheritedTheme?.data ?? Theme.of(context).chipTheme;
}
@override
bool updateShouldNotify(ChipTheme oldWidget) => data != oldWidget.data;
}
/// Holds the color, shape, and text styles for a material design chip theme.
///
/// Use this class to configure a [ChipTheme] widget, or to set the
/// [ThemeData.chipTheme] for a [Theme] widget.
///
/// To obtain the current ambient chip theme, use [ChipTheme.of].
///
/// The parts of a chip are:
///
/// * The "avatar", which is a widget that appears at the beginning of the
/// chip. This is typically a [CircleAvatar] widget.
/// * The "label", which is the widget displayed in the center of the chip.
/// Typically this is a [Text] widget.
/// * The "delete icon", which is a widget that appears at the end of the chip.
/// * The chip is disabled when it is not accepting user input. Only some chips
/// have a disabled state: [InputChip], [ChoiceChip], and [FilterChip].
///
/// The simplest way to create a ChipThemeData is to use [copyWith] on the one
/// you get from [ChipTheme.of], or create an entirely new one with
/// [ChipThemeData..fromDefaults].
///
/// ## Sample code
///
/// ```dart
/// class CarColor extends StatefulWidget {
/// @override
/// State createState() => new _CarColorState();
/// }
///
/// class _CarColorState extends State<CarColor> {
/// Color _color = Colors.red;
///
/// @override
/// Widget build(BuildContext context) {
/// return new ChipTheme(
/// data: ChipTheme.of(context).copyWith(backgroundColor: Colors.lightBlue),
/// child: new ChoiceChip(
/// label: new Text('Light Blue'),
/// onSelected: (bool value) {
/// setState(() {
/// _color = value ? Colors.lightBlue : Colors.red;
/// });
/// },
/// selected: _color == Colors.lightBlue,
/// ),
/// );
/// }
/// }
/// ```
///
/// See also:
///
/// * [Chip], a chip that displays information and can be deleted.
/// * [InputChip], a chip that represents a complex piece of information, such
/// as an entity (person, place, or thing) or conversational text, in a
/// compact form.
/// * [ChoiceChip], allows a single selection from a set of options. Choice
/// chips contain related descriptive text or categories.
/// * [FilterChip], uses tags or descriptive words as a way to filter content.
/// * [ActionChip], represents an action related to primary content.
/// * [CircleAvatar], which shows images or initials of entities.
/// * [Wrap], A widget that displays its children in multiple horizontal or
/// vertical runs.
/// * [ChipTheme] widget, which can override the chip theme of its
/// children.
/// * [Theme] widget, which performs a similar function to [ChipTheme],
/// but for overall themes.
/// * [ThemeData], which has a default [ChipThemeData].
class ChipThemeData extends Diagnosticable {
/// Create a [ChipThemeData] given a set of exact values. All the values
/// must be specified.
///
/// This will rarely be used directly. It is used by [lerp] to
/// create intermediate themes based on two themes.
const ChipThemeData({
@required this.backgroundColor,
this.deleteIconColor,
@required this.disabledColor,
@required this.selectedColor,
@required this.secondarySelectedColor,
@required this.labelPadding,
@required this.padding,
@required this.shape,
@required this.labelStyle,
@required this.secondaryLabelStyle,
@required this.brightness,
}) : assert(backgroundColor != null),
assert(disabledColor != null),
assert(selectedColor != null),
assert(secondarySelectedColor != null),
assert(labelPadding != null),
assert(padding != null),
assert(shape != null),
assert(labelStyle != null),
assert(secondaryLabelStyle != null),
assert(brightness != null);
/// Generates a ChipThemeData from a brightness, a primary color, and a text
/// style.
///
/// The [brightness] is used to select a primary color from the default
/// values.
///
/// The optional [primaryColor] is used as the base color for the other
/// colors. The opacity of the [primaryColor] is ignored. If a [primaryColor]
/// is specified, then the [brightness] is ignored, and the theme brightness
/// is determined from the [primaryColor].
///
/// Only one of [primaryColor] or [brightness] may be specified.
///
/// The [secondaryColor] is used for the selection colors needed by
/// [ChoiceChip].
///
/// This is used to generate the default chip theme for a [ThemeData].
factory ChipThemeData.fromDefaults({
Brightness brightness,
Color primaryColor,
@required Color secondaryColor,
@required TextStyle labelStyle,
}) {
assert(primaryColor != null || brightness != null,
'One of primaryColor or brightness must be specified');
assert(primaryColor == null || brightness == null,
'Only one of primaryColor or brightness may be specified');
assert(secondaryColor != null);
assert(labelStyle != null);
if (primaryColor != null) {
brightness = ThemeData.estimateBrightnessForColor(primaryColor);
}
// These are Material Design defaults, and are used to derive
// component Colors (with opacity) from base colors.
const int backgroundAlpha = 0x1f; // 12%
const int deleteIconAlpha = 0xde; // 87%
const int disabledAlpha = 0x0c; // 38% * 12% = 5%
const int selectAlpha = 0x3d; // 12% + 12% = 24%
const int textLabelAlpha = 0xde; // 87%
const ShapeBorder shape = const StadiumBorder();
const EdgeInsetsGeometry labelPadding = const EdgeInsets.symmetric(horizontal: 8.0);
const EdgeInsetsGeometry padding = const EdgeInsets.all(4.0);
primaryColor = primaryColor ?? (brightness == Brightness.light ? Colors.black : Colors.white);
final Color backgroundColor = primaryColor.withAlpha(backgroundAlpha);
final Color deleteIconColor = primaryColor.withAlpha(deleteIconAlpha);
final Color disabledColor = primaryColor.withAlpha(disabledAlpha);
final Color selectedColor = primaryColor.withAlpha(selectAlpha);
final Color secondarySelectedColor = secondaryColor.withAlpha(selectAlpha);
final TextStyle secondaryLabelStyle = labelStyle.copyWith(
color: secondaryColor.withAlpha(textLabelAlpha),
);
labelStyle = labelStyle.copyWith(color: primaryColor.withAlpha(textLabelAlpha));
return new ChipThemeData(
backgroundColor: backgroundColor,
deleteIconColor: deleteIconColor,
disabledColor: disabledColor,
selectedColor: selectedColor,
secondarySelectedColor: secondarySelectedColor,
labelPadding: labelPadding,
padding: padding,
shape: shape,
labelStyle: labelStyle,
secondaryLabelStyle: secondaryLabelStyle,
brightness: brightness,
);
}
/// Color to be used for the unselected, enabled chip's background.
///
/// The default is light grey.
final Color backgroundColor;
/// The [Color] for the delete icon. The default is Color(0xde000000)
/// (slightly transparent black) for light themes, and Color(0xdeffffff)
/// (slightly transparent white) for dark themes.
///
/// May be set to null, in which case the ambient [IconTheme.color] is used.
final Color deleteIconColor;
/// Color to be used for the chip's background indicating that it is disabled.
///
/// The chip is disabled when [isEnabled] is false, or all three of
/// [SelectableChipAttributes.onSelected], [TappableChipAttributes.onPressed],
/// and [DeletableChipAttributes.onDelete] are null.
///
/// It defaults to [Colors.black38].
final Color disabledColor;
/// Color to be used for the chip's background, indicating that it is
/// selected.
///
/// The chip is selected when [selected] is true.
final Color selectedColor;
/// An alternate color to be used for the chip's background, indicating that
/// it is selected. For example, this color is used by [ChoiceChip] when the
/// choice is selected.
///
/// The chip is selected when [selected] is true.
final Color secondarySelectedColor;
/// The padding around the [label] widget.
///
/// By default, this is 4 logical pixels at the beginning and the end of the
/// label, and zero on top and bottom.
final EdgeInsetsGeometry labelPadding;
/// The padding between the contents of the chip and the outside [border].
///
/// Defaults to 4 logical pixels on all sides.
final EdgeInsetsGeometry padding;
/// The border to draw around the chip.
///
/// Defaults to a [StadiumBorder]. Must not be null.
final ShapeBorder shape;
/// The style to be applied to the chip's label.
///
/// This only has an effect on label widgets that respect the
/// [DefaultTextStyle], such as [Text].
final TextStyle labelStyle;
/// An alternate style to be applied to the chip's label. For example, this
/// style is applied to the text of a selected [ChoiceChip].
///
/// This only has an effect on label widgets that respect the
/// [DefaultTextStyle], such as [Text].
final TextStyle secondaryLabelStyle;
/// The brightness setting for this theme.
///
/// This affects various base material color choices in the chip rendering.
final Brightness brightness;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
ChipThemeData copyWith({
Color backgroundColor,
Color deleteIconColor,
Color disabledColor,
Color selectedColor,
Color secondarySelectedColor,
EdgeInsetsGeometry labelPadding,
EdgeInsetsGeometry padding,
ShapeBorder shape,
TextStyle labelStyle,
TextStyle secondaryLabelStyle,
Brightness brightness,
}) {
return new ChipThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor,
deleteIconColor: deleteIconColor ?? this.deleteIconColor,
disabledColor: disabledColor ?? this.disabledColor,
selectedColor: selectedColor ?? this.selectedColor,
secondarySelectedColor: secondarySelectedColor ?? this.secondarySelectedColor,
labelPadding: labelPadding ?? this.labelPadding,
padding: padding ?? this.padding,
shape: shape ?? this.shape,
labelStyle: labelStyle ?? this.labelStyle,
secondaryLabelStyle: secondaryLabelStyle ?? this.secondaryLabelStyle,
brightness: brightness ?? this.brightness,
);
}
/// Linearly interpolate between two chip themes.
///
/// The arguments must not be null.
///
/// The `t` argument represents position on the timeline, with 0.0 meaning
/// that the interpolation has not started, returning `a` (or something
/// equivalent to `a`), 1.0 meaning that the interpolation has finished,
/// returning `b` (or something equivalent to `b`), and values in between
/// meaning that the interpolation is at the relevant point on the timeline
/// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
/// 1.0, so negative values and values greater than 1.0 are valid (and can
/// easily be generated by curves such as [Curves.elasticInOut]).
///
/// Values for `t` are usually obtained from an [Animation<double>], such as
/// an [AnimationController].
static ChipThemeData lerp(ChipThemeData a, ChipThemeData b, double t) {
assert(t != null);
if (a == null && b == null)
return null;
return new ChipThemeData(
backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
deleteIconColor: Color.lerp(a?.deleteIconColor, b?.deleteIconColor, t),
disabledColor: Color.lerp(a?.disabledColor, b?.disabledColor, t),
selectedColor: Color.lerp(a?.selectedColor, b?.selectedColor, t),
secondarySelectedColor: Color.lerp(a?.secondarySelectedColor, b?.secondarySelectedColor, t),
labelPadding: EdgeInsetsGeometry.lerp(a?.labelPadding, b?.labelPadding, t),
padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
labelStyle: TextStyle.lerp(a?.labelStyle, b?.labelStyle, t),
secondaryLabelStyle: TextStyle.lerp(a?.secondaryLabelStyle, b?.secondaryLabelStyle, t),
brightness: t < 0.5 ? a?.brightness ?? Brightness.light : b?.brightness ?? Brightness.light,
);
}
@override
int get hashCode {
return hashValues(
backgroundColor,
deleteIconColor,
disabledColor,
selectedColor,
secondarySelectedColor,
labelPadding,
padding,
shape,
labelStyle,
secondaryLabelStyle,
brightness,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
final ChipThemeData otherData = other;
return otherData.backgroundColor == backgroundColor
&& otherData.deleteIconColor == deleteIconColor
&& otherData.disabledColor == disabledColor
&& otherData.selectedColor == selectedColor
&& otherData.secondarySelectedColor == secondarySelectedColor
&& otherData.labelPadding == labelPadding
&& otherData.padding == padding
&& otherData.shape == shape
&& otherData.labelStyle == labelStyle
&& otherData.secondaryLabelStyle == secondaryLabelStyle
&& otherData.brightness == brightness;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
final ThemeData defaultTheme = new ThemeData.fallback();
final ChipThemeData defaultData = new ChipThemeData.fromDefaults(
secondaryColor: defaultTheme.primaryColor,
brightness: defaultTheme.brightness,
labelStyle: defaultTheme.textTheme.body2,
);
properties.add(new DiagnosticsProperty<Color>('backgroundColor', backgroundColor, defaultValue: defaultData.backgroundColor));
properties.add(new DiagnosticsProperty<Color>('deleteIconColor', deleteIconColor, defaultValue: defaultData.deleteIconColor));
properties.add(new DiagnosticsProperty<Color>('disabledColor', disabledColor, defaultValue: defaultData.disabledColor));
properties.add(new DiagnosticsProperty<Color>('selectedColor', selectedColor, defaultValue: defaultData.selectedColor));
properties.add(new DiagnosticsProperty<Color>('secondarySelectedColor', secondarySelectedColor, defaultValue: defaultData.secondarySelectedColor));
properties.add(new DiagnosticsProperty<EdgeInsetsGeometry>('labelPadding', labelPadding, defaultValue: defaultData.labelPadding));
properties.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: defaultData.padding));
properties.add(new DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: defaultData.shape));
properties.add(new DiagnosticsProperty<TextStyle>('labelStyle', labelStyle, defaultValue: defaultData.labelStyle));
properties.add(new DiagnosticsProperty<TextStyle>('secondaryLabelStyle', secondaryLabelStyle, defaultValue: defaultData.secondaryLabelStyle));
properties.add(new EnumProperty<Brightness>('brightness', brightness, defaultValue: defaultData.brightness));
}
}
......@@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'button_theme.dart';
import 'chip_theme.dart';
import 'colors.dart';
import 'ink_splash.dart';
import 'ink_well.dart' show InteractiveInkFeatureFactory;
......@@ -113,6 +114,7 @@ class ThemeData extends Diagnosticable {
IconThemeData primaryIconTheme,
IconThemeData accentIconTheme,
SliderThemeData sliderTheme,
ChipThemeData chipTheme,
TargetPlatform platform,
}) {
brightness ??= Brightness.light;
......@@ -168,6 +170,11 @@ class ThemeData extends Diagnosticable {
primaryColorDark: primaryColorDark,
valueIndicatorTextStyle: accentTextTheme.body2,
);
chipTheme ??= new ChipThemeData.fromDefaults(
secondaryColor: primaryColor,
brightness: brightness,
labelStyle: textTheme.body2,
);
return new ThemeData.raw(
brightness: brightness,
primaryColor: primaryColor,
......@@ -205,6 +212,7 @@ class ThemeData extends Diagnosticable {
primaryIconTheme: primaryIconTheme,
accentIconTheme: accentIconTheme,
sliderTheme: sliderTheme,
chipTheme: chipTheme,
platform: platform,
);
}
......@@ -252,6 +260,7 @@ class ThemeData extends Diagnosticable {
@required this.primaryIconTheme,
@required this.accentIconTheme,
@required this.sliderTheme,
@required this.chipTheme,
@required this.platform,
}) : assert(brightness != null),
assert(primaryColor != null),
......@@ -288,6 +297,7 @@ class ThemeData extends Diagnosticable {
assert(primaryIconTheme != null),
assert(accentIconTheme != null),
assert(sliderTheme != null),
assert(chipTheme != null),
assert(platform != null);
/// A default light blue theme.
......@@ -463,6 +473,11 @@ class ThemeData extends Diagnosticable {
/// This is the value returned from [SliderTheme.of].
final SliderThemeData sliderTheme;
/// The colors and styles used to render [Chip], [
///
/// This is the value returned from [ChipTheme.of].
final ChipThemeData chipTheme;
/// The platform the material widgets should adapt to target.
///
/// Defaults to the current platform.
......@@ -506,6 +521,7 @@ class ThemeData extends Diagnosticable {
IconThemeData primaryIconTheme,
IconThemeData accentIconTheme,
SliderThemeData sliderTheme,
ChipThemeData chipTheme,
TargetPlatform platform,
}) {
return new ThemeData.raw(
......@@ -545,6 +561,7 @@ class ThemeData extends Diagnosticable {
primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme,
accentIconTheme: accentIconTheme ?? this.accentIconTheme,
sliderTheme: sliderTheme ?? this.sliderTheme,
chipTheme: chipTheme ?? this.chipTheme,
platform: platform ?? this.platform,
);
}
......@@ -670,6 +687,7 @@ class ThemeData extends Diagnosticable {
primaryIconTheme: IconThemeData.lerp(a.primaryIconTheme, b.primaryIconTheme, t),
accentIconTheme: IconThemeData.lerp(a.accentIconTheme, b.accentIconTheme, t),
sliderTheme: SliderThemeData.lerp(a.sliderTheme, b.sliderTheme, t),
chipTheme: ChipThemeData.lerp(a.chipTheme, b.chipTheme, t),
platform: t < 0.5 ? a.platform : b.platform,
);
}
......@@ -713,6 +731,7 @@ class ThemeData extends Diagnosticable {
(otherData.primaryIconTheme == primaryIconTheme) &&
(otherData.accentIconTheme == accentIconTheme) &&
(otherData.sliderTheme == sliderTheme) &&
(otherData.chipTheme == chipTheme) &&
(otherData.platform == platform);
}
......@@ -754,6 +773,7 @@ class ThemeData extends Diagnosticable {
primaryIconTheme,
accentIconTheme,
sliderTheme,
chipTheme,
platform,
),
);
......@@ -797,6 +817,7 @@ class ThemeData extends Diagnosticable {
properties.add(new DiagnosticsProperty<IconThemeData>('primaryIconTheme', primaryIconTheme));
properties.add(new DiagnosticsProperty<IconThemeData>('accentIconTheme', accentIconTheme));
properties.add(new DiagnosticsProperty<SliderThemeData>('sliderTheme', sliderTheme));
properties.add(new DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme));
}
}
......
......@@ -504,17 +504,58 @@ class TextStyle extends Diagnosticable {
/// Values for `t` are usually obtained from an [Animation<double>], such as
/// an [AnimationController].
static TextStyle lerp(TextStyle a, TextStyle b, double t) {
assert(a != null);
assert(b != null);
assert(t != null);
assert(a.inherit == b.inherit);
assert(a == null || b == null || a.inherit == b.inherit);
if (a == null && b == null) {
return null;
}
String lerpDebugLabel;
assert(() {
lerpDebugLabel = 'lerp(${a.debugLabel ?? _kDefaultDebugLabel}${t.toStringAsFixed(1)}${b.debugLabel ?? _kDefaultDebugLabel})';
lerpDebugLabel = 'lerp(${a?.debugLabel ?? _kDefaultDebugLabel}${t.toStringAsFixed(1)}${b?.debugLabel ?? _kDefaultDebugLabel})';
return true;
}());
if (a == null) {
return new TextStyle(
inherit: b.inherit,
color: Color.lerp(null, b.color, t),
fontFamily: t < 0.5 ? null : b.fontFamily,
fontSize: t < 0.5 ? null : b.fontSize,
fontWeight: FontWeight.lerp(null, b.fontWeight, t),
fontStyle: t < 0.5 ? null : b.fontStyle,
letterSpacing: t < 0.5 ? null : b.letterSpacing,
wordSpacing: t < 0.5 ? null : b.wordSpacing,
textBaseline: t < 0.5 ? null : b.textBaseline,
height: t < 0.5 ? null : b.height,
locale: t < 0.5 ? null : b.locale,
decoration: t < 0.5 ? null : b.decoration,
decorationColor: Color.lerp(null, b.decorationColor, t),
decorationStyle: t < 0.5 ? null : b.decorationStyle,
debugLabel: lerpDebugLabel,
);
}
if (b == null) {
return new TextStyle(
inherit: a.inherit,
color: Color.lerp(a.color, null, t),
fontFamily: t < 0.5 ? a.fontFamily : null,
fontSize: t < 0.5 ? a.fontSize : null,
fontWeight: FontWeight.lerp(a.fontWeight, null, t),
fontStyle: t < 0.5 ? a.fontStyle : null,
letterSpacing: t < 0.5 ? a.letterSpacing : null,
wordSpacing: t < 0.5 ? a.wordSpacing : null,
textBaseline: t < 0.5 ? a.textBaseline : null,
height: t < 0.5 ? a.height : null,
locale: t < 0.5 ? a.locale : null,
decoration: t < 0.5 ? a.decoration : null,
decorationColor: Color.lerp(a.decorationColor, null, t),
decorationStyle: t < 0.5 ? a.decorationStyle : null,
debugLabel: lerpDebugLabel,
);
}
return new TextStyle(
inherit: b.inherit,
color: Color.lerp(a.color, b.color, t),
......
......@@ -2,16 +2,49 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show window;
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import '../rendering/mock_canvas.dart';
import 'feedback_tester.dart';
Finder findRenderChipElement() {
return find.byElementPredicate((Element e) => '${e.runtimeType}' == '_RenderChipElement');
}
RenderBox getMaterialBox(WidgetTester tester) {
return tester.firstRenderObject<RenderBox>(
find.descendant(
of: find.byType(RawChip),
matching: find.byType(CustomPaint),
),
);
}
IconThemeData getIconData(WidgetTester tester) {
final IconTheme iconTheme = tester.firstWidget(
find.descendant(
of: find.byType(RawChip),
matching: find.byType(IconTheme),
),
);
return iconTheme.data;
}
DefaultTextStyle getLabelStyle(WidgetTester tester) {
return tester.widget(
find
.descendant(
of: find.byType(RawChip),
matching: find.byType(DefaultTextStyle),
)
.last,
);
}
dynamic getRenderChip(WidgetTester tester) {
if (!tester.any(findRenderChipElement())) {
return null;
......@@ -25,6 +58,30 @@ double getAvatarDrawerProgress(WidgetTester tester) => getRenderChip(tester)?.av
double getDeleteDrawerProgress(WidgetTester tester) => getRenderChip(tester)?.deleteDrawerAnimation?.value;
double getEnableProgress(WidgetTester tester) => getRenderChip(tester)?.enableAnimation?.value;
/// Adds the basic requirements for a Chip.
Widget _wrapForChip({
Widget child,
TextDirection textDirection: TextDirection.ltr,
double textScaleFactor: 1.0,
}) {
return new MaterialApp(
home: new Localizations(
locale: const Locale('en', 'US'),
delegates: const <LocalizationsDelegate<dynamic>>[
DefaultWidgetsLocalizations.delegate,
DefaultMaterialLocalizations.delegate,
],
child: new Directionality(
textDirection: textDirection,
child: new MediaQuery(
data: new MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScaleFactor),
child: new Material(child: child),
),
),
),
);
}
/// Tests that a [Chip] that has its size constrained by its parent is
/// further constraining the size of its child, the label widget.
/// Optionally, adding an avatar or delete icon to the chip should not
......@@ -41,8 +98,7 @@ Future<Null> _testConstrainedLabel(
final Key labelKey = new UniqueKey();
await tester.pumpWidget(
new MaterialApp(
home: new Material(
_wrapForChip(
child: new Center(
child: new Container(
width: chipParentWidth,
......@@ -59,7 +115,6 @@ Future<Null> _testConstrainedLabel(
),
),
),
),
);
final Size labelSize = tester.getSize(find.byKey(labelKey));
......@@ -75,9 +130,10 @@ void main() {
testWidgets('Chip control test', (WidgetTester tester) async {
final FeedbackTester feedback = new FeedbackTester();
final List<String> deletedChipLabels = <String>[];
await tester.pumpWidget(new MaterialApp(
home: new Material(
child: new Column(children: <Widget>[
await tester.pumpWidget(
_wrapForChip(
child: new Column(
children: <Widget>[
new Chip(
avatar: const CircleAvatar(child: const Text('A')),
label: const Text('Chip A'),
......@@ -94,7 +150,10 @@ void main() {
},
deleteButtonTooltipMessage: 'Delete chip B',
),
]))));
],
),
),
);
expect(tester.widget(find.byTooltip('Delete chip A')), isNotNull);
expect(tester.widget(find.byTooltip('Delete chip B')), isNotNull);
......@@ -125,9 +184,8 @@ void main() {
final Key labelKey = new UniqueKey();
await tester.pumpWidget(
new Material(
child: new MaterialApp(
home: new Center(
_wrapForChip(
child: new Center(
child: new Container(
width: 500.0,
height: 500.0,
......@@ -145,7 +203,6 @@ void main() {
),
),
),
),
);
final Size labelSize = tester.getSize(find.byKey(labelKey));
......@@ -190,41 +247,35 @@ void main() {
testWidgets('Chip in row works ok', (WidgetTester tester) async {
const TextStyle style = const TextStyle(fontFamily: 'Ahem', fontSize: 10.0);
await tester.pumpWidget(
new MaterialApp(
home: new Material(
_wrapForChip(
child: new Row(
children: const <Widget>[
const Chip(label: const Text('Test'), labelStyle: style),
],
),
),
),
);
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
expect(tester.getSize(find.byType(Chip)), const Size(64.0, 32.0));
await tester.pumpWidget(
new MaterialApp(
home: new Material(
_wrapForChip(
child: new Row(
children: const <Widget>[
const Flexible(child: const Chip(label: const Text('Test'), labelStyle: style)),
],
),
),
),
);
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
expect(tester.getSize(find.byType(Chip)), const Size(64.0, 32.0));
await tester.pumpWidget(
new MaterialApp(
home: new Material(
_wrapForChip(
child: new Row(
children: const <Widget>[
const Expanded(child: const Chip(label: const Text('Test'), labelStyle: style)),
],
),
),
),
);
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
expect(tester.getSize(find.byType(Chip)), const Size(800.0, 32.0));
......@@ -249,32 +300,18 @@ void main() {
);
await tester.pumpWidget(
new Localizations(
locale: const Locale('en', 'US'),
delegates: const <LocalizationsDelegate<dynamic>>[
DefaultWidgetsLocalizations.delegate,
DefaultMaterialLocalizations.delegate,
],
child: new Directionality(
textDirection: TextDirection.rtl,
_wrapForChip(
child: test,
),
textDirection: TextDirection.rtl,
),
);
await tester.pumpAndSettle(const Duration(milliseconds: 500));
expect(tester.getCenter(find.text('ABC')).dx, greaterThan(tester.getCenter(find.byKey(iconKey)).dx));
await tester.pumpWidget(
new Localizations(
locale: const Locale('en', 'US'),
delegates: const <LocalizationsDelegate<dynamic>>[
DefaultWidgetsLocalizations.delegate,
DefaultMaterialLocalizations.delegate,
],
child: new Directionality(
_wrapForChip(
textDirection: TextDirection.ltr,
child: test,
),
),
);
await tester.pumpAndSettle(const Duration(milliseconds: 500));
expect(tester.getCenter(find.text('ABC')).dx, lessThan(tester.getCenter(find.byKey(iconKey)).dx));
......@@ -282,8 +319,7 @@ void main() {
testWidgets('Chip responds to textScaleFactor', (WidgetTester tester) async {
await tester.pumpWidget(
new MaterialApp(
home: new Material(
_wrapForChip(
child: new Column(
children: const <Widget>[
const Chip(
......@@ -297,7 +333,6 @@ void main() {
],
),
),
),
);
// TODO(gspencer): Update this test when the font metric bug is fixed to remove the anyOfs.
......@@ -314,10 +349,8 @@ void main() {
expect(tester.getSize(find.byType(Chip).last), anyOf(const Size(132.0, 32.0), const Size(131.0, 32.0)));
await tester.pumpWidget(
new MaterialApp(
home: new MediaQuery(
data: const MediaQueryData(textScaleFactor: 3.0),
child: new Material(
_wrapForChip(
textScaleFactor: 3.0,
child: new Column(
children: const <Widget>[
const Chip(
......@@ -331,8 +364,6 @@ void main() {
],
),
),
),
),
);
// TODO(gspencer): Update this test when the font metric bug is fixed to remove the anyOfs.
......@@ -346,8 +377,7 @@ void main() {
// Check that individual text scales are taken into account.
await tester.pumpWidget(
new MaterialApp(
home: new Material(
_wrapForChip(
child: new Column(
children: const <Widget>[
const Chip(
......@@ -361,7 +391,6 @@ void main() {
],
),
),
),
);
// TODO(gspencer): Update this test when the font metric bug is fixed to remove the anyOfs.
......@@ -377,8 +406,7 @@ void main() {
final Key keyA = new GlobalKey();
final Key keyB = new GlobalKey();
await tester.pumpWidget(
new MaterialApp(
home: new Material(
_wrapForChip(
child: new Column(
children: <Widget>[
new Chip(
......@@ -392,7 +420,6 @@ void main() {
],
),
),
),
);
// TODO(gspencer): Update this test when the font metric bug is fixed to remove the anyOfs.
......@@ -412,8 +439,7 @@ void main() {
testWidgets('Avatars can be non-circle avatar widgets', (WidgetTester tester) async {
final Key keyA = new GlobalKey();
await tester.pumpWidget(
new MaterialApp(
home: new Material(
_wrapForChip(
child: new Column(
children: <Widget>[
new Chip(
......@@ -423,7 +449,6 @@ void main() {
],
),
),
),
);
expect(tester.getSize(find.byKey(keyA)), equals(const Size(20.0, 20.0)));
......@@ -432,8 +457,7 @@ void main() {
testWidgets('Delete icons can be non-icon widgets', (WidgetTester tester) async {
final Key keyA = new GlobalKey();
await tester.pumpWidget(
new MaterialApp(
home: new Material(
_wrapForChip(
child: new Column(
children: <Widget>[
new Chip(
......@@ -444,7 +468,6 @@ void main() {
],
),
),
),
);
expect(tester.getSize(find.byKey(keyA)), equals(const Size(20.0, 20.0)));
......@@ -454,13 +477,7 @@ void main() {
final GlobalKey keyA = new GlobalKey();
final GlobalKey keyB = new GlobalKey();
await tester.pumpWidget(
new Localizations(
locale: const Locale('en', 'US'),
delegates: const <LocalizationsDelegate<dynamic>>[
DefaultWidgetsLocalizations.delegate,
DefaultMaterialLocalizations.delegate,
],
child: new Directionality(
_wrapForChip(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[
......@@ -484,7 +501,6 @@ void main() {
],
),
),
),
);
expect(tester.getTopLeft(find.byKey(keyA)), const Offset(332.0, 280.0));
expect(tester.getBottomRight(find.byKey(keyA)), const Offset(372.0, 320.0));
......@@ -498,13 +514,7 @@ void main() {
final GlobalKey keyA = new GlobalKey();
final GlobalKey keyB = new GlobalKey();
await tester.pumpWidget(
new Localizations(
locale: const Locale('en', 'US'),
delegates: const <LocalizationsDelegate<dynamic>>[
DefaultWidgetsLocalizations.delegate,
DefaultMaterialLocalizations.delegate,
],
child: new Directionality(
_wrapForChip(
textDirection: TextDirection.rtl,
child: new Overlay(
initialEntries: <OverlayEntry>[
......@@ -528,7 +538,6 @@ void main() {
],
),
),
),
);
expect(tester.getTopLeft(find.byKey(keyA)), const Offset(428.0, 280.0));
......@@ -543,8 +552,7 @@ void main() {
final GlobalKey labelKey = new GlobalKey();
Future<Null> pushChip({Widget avatar}) async {
return tester.pumpWidget(
new MaterialApp(
home: new Material(
_wrapForChip(
child: new Wrap(
children: <Widget>[
new RawChip(
......@@ -555,7 +563,6 @@ void main() {
],
),
),
),
);
}
......@@ -659,8 +666,7 @@ void main() {
bool wasDeleted = false;
Future<Null> pushChip({bool deletable: false}) async {
return tester.pumpWidget(
new MaterialApp(
home: new Material(
_wrapForChip(
child: new Wrap(
children: <Widget>[
new StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
......@@ -680,7 +686,6 @@ void main() {
],
),
),
),
);
}
......@@ -776,8 +781,7 @@ void main() {
final UniqueKey labelKey = new UniqueKey();
Future<Null> pushChip({Widget avatar, bool selectable: false}) async {
return tester.pumpWidget(
new MaterialApp(
home: new Material(
_wrapForChip(
child: new Wrap(
children: <Widget>[
new StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
......@@ -801,7 +805,6 @@ void main() {
],
),
),
),
);
}
......@@ -861,8 +864,7 @@ void main() {
final UniqueKey labelKey = new UniqueKey();
Future<Null> pushChip({bool selectable: false}) async {
return tester.pumpWidget(
new MaterialApp(
home: new Material(
_wrapForChip(
child: new Wrap(
children: <Widget>[
new StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
......@@ -885,7 +887,6 @@ void main() {
],
),
),
),
);
}
......@@ -939,8 +940,7 @@ void main() {
final UniqueKey labelKey = new UniqueKey();
Future<Null> pushChip({Widget avatar, bool selectable: false}) async {
return tester.pumpWidget(
new MaterialApp(
home: new Material(
_wrapForChip(
child: new Wrap(
children: <Widget>[
new StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
......@@ -964,7 +964,6 @@ void main() {
],
),
),
),
);
}
......@@ -993,4 +992,157 @@ void main() {
expect(getDeleteDrawerProgress(tester), equals(0.0));
await tester.pumpAndSettle();
});
testWidgets('Chip uses ThemeData chip theme if present', (WidgetTester tester) async {
final ThemeData theme = new ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.red,
);
final ChipThemeData chipTheme = theme.chipTheme;
Widget buildChip(ChipThemeData data) {
return _wrapForChip(
textDirection: TextDirection.ltr,
child: new Theme(
data: theme,
child: const InputChip(
label: const Text('Label'),
),
),
);
}
await tester.pumpWidget(buildChip(chipTheme));
final RenderBox materialBox = tester.firstRenderObject<RenderBox>(
find.descendant(
of: find.byType(RawChip),
matching: find.byType(CustomPaint),
),
);
expect(materialBox, paints..path(color: chipTheme.disabledColor));
});
testWidgets('Chip uses the right theme colors for the right components', (WidgetTester tester) async {
final ThemeData themeData = new ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
);
final ChipThemeData chipTheme = themeData.chipTheme;
bool value = false;
Widget buildApp({
ChipThemeData theme,
Widget avatar,
Widget deleteIcon,
bool isSelectable: true,
bool isPressable: false,
bool isDeletable: true,
bool showCheckmark: true,
}) {
theme ??= chipTheme;
return _wrapForChip(
child: new Theme(
data: themeData,
child: new ChipTheme(
data: theme,
child: new StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return new RawChip(
showCheckmark: showCheckmark,
onDeleted: isDeletable ? () {} : null,
tapEnabled: true,
avatar: avatar,
deleteIcon: deleteIcon,
isEnabled: isSelectable || isPressable,
shape: theme.shape,
selected: isSelectable ? value : null,
label: new Text('$value'),
onSelected: isSelectable
? (bool newValue) {
setState(() {
value = newValue;
});
}
: null,
onPressed: isPressable
? () {
setState(() {
value = true;
});
}
: null,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
RenderBox materialBox = getMaterialBox(tester);
IconThemeData iconData = getIconData(tester);
DefaultTextStyle labelStyle = getLabelStyle(tester);
// Check default theme for enabled widget.
expect(materialBox, paints..path(color: chipTheme.backgroundColor));
expect(iconData.color, equals(const Color(0xde000000)));
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
await tester.tap(find.byType(RawChip));
await tester.pumpAndSettle();
materialBox = getMaterialBox(tester);
expect(materialBox, paints..path(color: chipTheme.selectedColor));
await tester.tap(find.byType(RawChip));
await tester.pumpAndSettle();
// Check default theme with disabled widget.
await tester.pumpWidget(buildApp(isSelectable: false, isPressable: false, isDeletable: true));
await tester.pumpAndSettle();
materialBox = getMaterialBox(tester);
labelStyle = getLabelStyle(tester);
expect(materialBox, paints..path(color: chipTheme.disabledColor));
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
// Apply a custom theme.
const Color customColor1 = const Color(0xcafefeed);
const Color customColor2 = const Color(0xdeadbeef);
const Color customColor3 = const Color(0xbeefcafe);
const Color customColor4 = const Color(0xaddedabe);
final ChipThemeData customTheme = chipTheme.copyWith(
brightness: Brightness.dark,
backgroundColor: customColor1,
disabledColor: customColor2,
selectedColor: customColor3,
deleteIconColor: customColor4,
);
await tester.pumpWidget(buildApp(theme: customTheme));
await tester.pumpAndSettle();
materialBox = getMaterialBox(tester);
iconData = getIconData(tester);
labelStyle = getLabelStyle(tester);
// Check custom theme for enabled widget.
expect(materialBox, paints..path(color: customTheme.backgroundColor));
expect(iconData.color, equals(customTheme.deleteIconColor));
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
await tester.tap(find.byType(RawChip));
await tester.pumpAndSettle();
materialBox = getMaterialBox(tester);
expect(materialBox, paints..path(color: customTheme.selectedColor));
await tester.tap(find.byType(RawChip));
await tester.pumpAndSettle();
// Check custom theme with disabled widget.
await tester.pumpWidget(buildApp(
theme: customTheme,
isSelectable: false,
isPressable: false,
isDeletable: true,
));
await tester.pumpAndSettle();
materialBox = getMaterialBox(tester);
labelStyle = getLabelStyle(tester);
expect(materialBox, paints..path(color: customTheme.disabledColor));
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
});
}
// Copyright 2018 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 'dart:ui' show window;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/painting.dart';
import '../rendering/mock_canvas.dart';
RenderBox getMaterialBox(WidgetTester tester) {
return tester.firstRenderObject<RenderBox>(
find.descendant(
of: find.byType(RawChip),
matching: find.byType(CustomPaint),
),
);
}
IconThemeData getIconData(WidgetTester tester) {
final IconTheme iconTheme = tester.firstWidget(
find.descendant(
of: find.byType(RawChip),
matching: find.byType(IconTheme),
),
);
return iconTheme.data;
}
DefaultTextStyle getLabelStyle(WidgetTester tester) {
return tester.widget(
find
.descendant(
of: find.byType(RawChip),
matching: find.byType(DefaultTextStyle),
)
.last,
);
}
void main() {
testWidgets('Chip theme is built by ThemeData', (WidgetTester tester) async {
final ThemeData theme = new ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.red,
);
final ChipThemeData chipTheme = theme.chipTheme;
expect(chipTheme.backgroundColor, equals(Colors.black.withAlpha(0x1f)));
expect(chipTheme.selectedColor, equals(Colors.black.withAlpha(0x3d)));
expect(chipTheme.deleteIconColor, equals(Colors.black.withAlpha(0xde)));
});
testWidgets('Chip uses ThemeData chip theme if present', (WidgetTester tester) async {
final ThemeData theme = new ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.red,
backgroundColor: Colors.blue,
);
final ChipThemeData chipTheme = theme.chipTheme;
bool value;
Widget buildChip(ChipThemeData data) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: new MediaQueryData.fromWindow(window),
child: new Material(
child: new Center(
child: new Theme(
data: theme,
child: new RawChip(
showCheckmark: true,
onDeleted: () {},
tapEnabled: true,
avatar: const Placeholder(),
deleteIcon: const Placeholder(),
isEnabled: true,
selected: value,
label: new Text('$value'),
onSelected: (bool newValue) {},
onPressed: null,
),
),
),
),
),
);
}
await tester.pumpWidget(buildChip(chipTheme));
await tester.pumpAndSettle();
final RenderBox materialBox = getMaterialBox(tester);
expect(materialBox, paints..path(color: chipTheme.backgroundColor));
});
testWidgets('Chip overrides ThemeData theme if ChipTheme present', (WidgetTester tester) async {
final ThemeData theme = new ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.red,
);
final ChipThemeData chipTheme = theme.chipTheme;
final ChipThemeData customTheme = chipTheme.copyWith(
backgroundColor: Colors.purple,
deleteIconColor: Colors.purple.withAlpha(0x3d),
);
const bool value = false;
Widget buildChip(ChipThemeData data) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: new MediaQueryData.fromWindow(window),
child: new Material(
child: new Center(
child: new Theme(
data: theme,
child: new ChipTheme(
data: customTheme,
child: new RawChip(
showCheckmark: true,
onDeleted: () {},
tapEnabled: true,
avatar: const Placeholder(),
deleteIcon: const Placeholder(),
isEnabled: true,
selected: value,
label: const Text('$value'),
onSelected: (bool newValue) {},
onPressed: null,
),
),
),
),
),
),
);
}
await tester.pumpWidget(buildChip(chipTheme));
await tester.pumpAndSettle();
final RenderBox materialBox = getMaterialBox(tester);
expect(materialBox, paints..path(color: new Color(customTheme.backgroundColor.value)));
});
testWidgets('ChipThemeData generates correct opacities for defaults', (WidgetTester tester) async {
const Color customColor1 = const Color(0xcafefeed);
const Color customColor2 = const Color(0xdeadbeef);
final TextStyle customStyle = new ThemeData.fallback().accentTextTheme.body2.copyWith(color: customColor2);
final ChipThemeData lightTheme = new ChipThemeData.fromDefaults(
secondaryColor: customColor1,
brightness: Brightness.light,
labelStyle: customStyle,
);
expect(lightTheme.backgroundColor, equals(Colors.black.withAlpha(0x1f)));
expect(lightTheme.deleteIconColor, equals(Colors.black.withAlpha(0xde)));
expect(lightTheme.disabledColor, equals(Colors.black.withAlpha(0x0c)));
expect(lightTheme.selectedColor, equals(Colors.black.withAlpha(0x3d)));
expect(lightTheme.secondarySelectedColor, equals(customColor1.withAlpha(0x3d)));
expect(lightTheme.labelPadding, equals(const EdgeInsets.symmetric(horizontal: 8.0)));
expect(lightTheme.padding, equals(const EdgeInsets.all(4.0)));
expect(lightTheme.shape, equals(const isInstanceOf<StadiumBorder>()));
expect(lightTheme.labelStyle.color, equals(Colors.black.withAlpha(0xde)));
expect(lightTheme.secondaryLabelStyle.color, equals(customColor1.withAlpha(0xde)));
expect(lightTheme.brightness, equals(Brightness.light));
final ChipThemeData darkTheme = new ChipThemeData.fromDefaults(
secondaryColor: customColor1,
brightness: Brightness.dark,
labelStyle: customStyle,
);
expect(darkTheme.backgroundColor, equals(Colors.white.withAlpha(0x1f)));
expect(darkTheme.deleteIconColor, equals(Colors.white.withAlpha(0xde)));
expect(darkTheme.disabledColor, equals(Colors.white.withAlpha(0x0c)));
expect(darkTheme.selectedColor, equals(Colors.white.withAlpha(0x3d)));
expect(darkTheme.secondarySelectedColor, equals(customColor1.withAlpha(0x3d)));
expect(darkTheme.labelPadding, equals(const EdgeInsets.symmetric(horizontal: 8.0)));
expect(darkTheme.padding, equals(const EdgeInsets.all(4.0)));
expect(darkTheme.shape, equals(const isInstanceOf<StadiumBorder>()));
expect(darkTheme.labelStyle.color, equals(Colors.white.withAlpha(0xde)));
expect(darkTheme.secondaryLabelStyle.color, equals(customColor1.withAlpha(0xde)));
expect(darkTheme.brightness, equals(Brightness.dark));
final ChipThemeData customTheme = new ChipThemeData.fromDefaults(
primaryColor: customColor1,
secondaryColor: customColor2,
labelStyle: customStyle,
);
expect(customTheme.backgroundColor, equals(customColor1.withAlpha(0x1f)));
expect(customTheme.deleteIconColor, equals(customColor1.withAlpha(0xde)));
expect(customTheme.disabledColor, equals(customColor1.withAlpha(0x0c)));
expect(customTheme.selectedColor, equals(customColor1.withAlpha(0x3d)));
expect(customTheme.secondarySelectedColor, equals(customColor2.withAlpha(0x3d)));
expect(customTheme.labelPadding, equals(const EdgeInsets.symmetric(horizontal: 8.0)));
expect(customTheme.padding, equals(const EdgeInsets.all(4.0)));
expect(customTheme.shape, equals(const isInstanceOf<StadiumBorder>()));
expect(customTheme.labelStyle.color, equals(customColor1.withAlpha(0xde)));
expect(customTheme.secondaryLabelStyle.color, equals(customColor2.withAlpha(0xde)));
expect(customTheme.brightness, equals(Brightness.light));
});
testWidgets('ChipThemeData lerps correctly', (WidgetTester tester) async {
final ChipThemeData chipThemeBlack = new ChipThemeData.fromDefaults(
secondaryColor: Colors.black,
brightness: Brightness.dark,
labelStyle: new ThemeData.fallback().accentTextTheme.body2.copyWith(color: Colors.black),
);
final ChipThemeData chipThemeWhite = new ChipThemeData.fromDefaults(
secondaryColor: Colors.white,
brightness: Brightness.light,
labelStyle: new ThemeData.fallback().accentTextTheme.body2.copyWith(color: Colors.white),
).copyWith(padding: const EdgeInsets.all(2.0), labelPadding: const EdgeInsets.only(top: 8.0, bottom: 8.0));
final ChipThemeData lerp = ChipThemeData.lerp(chipThemeBlack, chipThemeWhite, 0.5);
const Color middleGrey = const Color(0xff7f7f7f);
expect(lerp.backgroundColor, equals(middleGrey.withAlpha(0x1f)));
expect(lerp.deleteIconColor, equals(middleGrey.withAlpha(0xde)));
expect(lerp.disabledColor, equals(middleGrey.withAlpha(0x0c)));
expect(lerp.selectedColor, equals(middleGrey.withAlpha(0x3d)));
expect(lerp.secondarySelectedColor, equals(middleGrey.withAlpha(0x3d)));
expect(lerp.labelPadding, equals(const EdgeInsets.all(4.0)));
expect(lerp.padding, equals(const EdgeInsets.all(3.0)));
expect(lerp.shape, equals(const isInstanceOf<StadiumBorder>()));
expect(lerp.labelStyle.color, equals(middleGrey.withAlpha(0xde)));
expect(lerp.secondaryLabelStyle.color, equals(middleGrey.withAlpha(0xde)));
expect(lerp.brightness, equals(Brightness.light));
expect(ChipThemeData.lerp(null, null, 0.25), isNull);
final ChipThemeData lerpANull25 = ChipThemeData.lerp(null, chipThemeWhite, 0.25);
expect(lerpANull25.backgroundColor, equals(Colors.black.withAlpha(0x08)));
expect(lerpANull25.deleteIconColor, equals(Colors.black.withAlpha(0x38)));
expect(lerpANull25.disabledColor, equals(Colors.black.withAlpha(0x03)));
expect(lerpANull25.selectedColor, equals(Colors.black.withAlpha(0x0f)));
expect(lerpANull25.secondarySelectedColor, equals(Colors.white.withAlpha(0x0f)));
expect(lerpANull25.labelPadding, equals(const EdgeInsets.only(left: 0.0, top: 2.0, right: 0.0, bottom: 2.0)));
expect(lerpANull25.padding, equals(const EdgeInsets.all(0.5)));
expect(lerpANull25.shape, equals(const isInstanceOf<StadiumBorder>()));
expect(lerpANull25.labelStyle.color, equals(Colors.black.withAlpha(0x38)));
expect(lerpANull25.secondaryLabelStyle.color, equals(Colors.white.withAlpha(0x38)));
expect(lerpANull25.brightness, equals(Brightness.light));
final ChipThemeData lerpANull75 = ChipThemeData.lerp(null, chipThemeWhite, 0.75);
expect(lerpANull75.backgroundColor, equals(Colors.black.withAlpha(0x17)));
expect(lerpANull75.deleteIconColor, equals(Colors.black.withAlpha(0xa7)));
expect(lerpANull75.disabledColor, equals(Colors.black.withAlpha(0x09)));
expect(lerpANull75.selectedColor, equals(Colors.black.withAlpha(0x2e)));
expect(lerpANull75.secondarySelectedColor, equals(Colors.white.withAlpha(0x2e)));
expect(lerpANull75.labelPadding, equals(const EdgeInsets.only(left: 0.0, top: 6.0, right: 0.0, bottom: 6.0)));
expect(lerpANull75.padding, equals(const EdgeInsets.all(1.5)));
expect(lerpANull75.shape, equals(const isInstanceOf<StadiumBorder>()));
expect(lerpANull75.labelStyle.color, equals(Colors.black.withAlpha(0xa7)));
expect(lerpANull75.secondaryLabelStyle.color, equals(Colors.white.withAlpha(0xa7)));
expect(lerpANull75.brightness, equals(Brightness.light));
final ChipThemeData lerpBNull25 = ChipThemeData.lerp(chipThemeBlack, null, 0.25);
expect(lerpBNull25.backgroundColor, equals(Colors.white.withAlpha(0x17)));
expect(lerpBNull25.deleteIconColor, equals(Colors.white.withAlpha(0xa7)));
expect(lerpBNull25.disabledColor, equals(Colors.white.withAlpha(0x09)));
expect(lerpBNull25.selectedColor, equals(Colors.white.withAlpha(0x2e)));
expect(lerpBNull25.secondarySelectedColor, equals(Colors.black.withAlpha(0x2e)));
expect(lerpBNull25.labelPadding, equals(const EdgeInsets.only(left: 6.0, top: 0.0, right: 6.0, bottom: 0.0)));
expect(lerpBNull25.padding, equals(const EdgeInsets.all(3.0)));
expect(lerpBNull25.shape, equals(const isInstanceOf<StadiumBorder>()));
expect(lerpBNull25.labelStyle.color, equals(Colors.white.withAlpha(0xa7)));
expect(lerpBNull25.secondaryLabelStyle.color, equals(Colors.black.withAlpha(0xa7)));
expect(lerpBNull25.brightness, equals(Brightness.dark));
final ChipThemeData lerpBNull75 = ChipThemeData.lerp(chipThemeBlack, null, 0.75);
expect(lerpBNull75.backgroundColor, equals(Colors.white.withAlpha(0x08)));
expect(lerpBNull75.deleteIconColor, equals(Colors.white.withAlpha(0x38)));
expect(lerpBNull75.disabledColor, equals(Colors.white.withAlpha(0x03)));
expect(lerpBNull75.selectedColor, equals(Colors.white.withAlpha(0x0f)));
expect(lerpBNull75.secondarySelectedColor, equals(Colors.black.withAlpha(0x0f)));
expect(lerpBNull75.labelPadding, equals(const EdgeInsets.only(left: 2.0, top: 0.0, right: 2.0, bottom: 0.0)));
expect(lerpBNull75.padding, equals(const EdgeInsets.all(1.0)));
expect(lerpBNull75.shape, equals(const isInstanceOf<StadiumBorder>()));
expect(lerpBNull75.labelStyle.color, equals(Colors.white.withAlpha(0x38)));
expect(lerpBNull75.secondaryLabelStyle.color, equals(Colors.black.withAlpha(0x38)));
expect(lerpBNull75.brightness, equals(Brightness.light));
});
}
......@@ -100,7 +100,7 @@ void main() {
expect(sliderBox, paints..rect(color: customTheme.disabledActiveRailColor)..rect(color: customTheme.disabledInactiveRailColor));
});
testWidgets('SliderThemeData generates correct opacities for materialDefaults', (WidgetTester tester) async {
testWidgets('SliderThemeData generates correct opacities for fromPrimaryColors', (WidgetTester tester) async {
const Color customColor1 = const Color(0xcafefeed);
const Color customColor2 = const Color(0xdeadbeef);
const Color customColor3 = const Color(0xdecaface);
......
......@@ -107,6 +107,60 @@ void main() {
expect(s5.height, 123.0);
expect(s5.color, isNull);
expect(TextStyle.lerp(null, null, 0.5), isNull);
final TextStyle s6 = TextStyle.lerp(null, s3, 0.25);
expect(s3.fontFamily, isNull);
expect(s3.fontSize, 18.0);
expect(s3.fontWeight, FontWeight.w400);
expect(s3.height, 123.0);
expect(s3.color, isNull);
expect(s3, isNot(equals(s6)));
expect(s6.fontFamily, isNull);
expect(s6.fontSize, isNull);
expect(s6.fontWeight, FontWeight.w400);
expect(s6.height, isNull);
expect(s6.color, isNull);
final TextStyle s7 = TextStyle.lerp(null, s3, 0.75);
expect(s3.fontFamily, isNull);
expect(s3.fontSize, 18.0);
expect(s3.fontWeight, FontWeight.w400);
expect(s3.height, 123.0);
expect(s3.color, isNull);
expect(s3, equals(s7));
expect(s7.fontFamily, isNull);
expect(s7.fontSize, 18.0);
expect(s7.fontWeight, FontWeight.w400);
expect(s7.height, 123.0);
expect(s7.color, isNull);
final TextStyle s8 = TextStyle.lerp(s3, null, 0.25);
expect(s3.fontFamily, isNull);
expect(s3.fontSize, 18.0);
expect(s3.fontWeight, FontWeight.w400);
expect(s3.height, 123.0);
expect(s3.color, isNull);
expect(s3, equals(s8));
expect(s8.fontFamily, isNull);
expect(s8.fontSize, 18.0);
expect(s8.fontWeight, FontWeight.w400);
expect(s8.height, 123.0);
expect(s8.color, isNull);
final TextStyle s9 = TextStyle.lerp(s3, null, 0.75);
expect(s3.fontFamily, isNull);
expect(s3.fontSize, 18.0);
expect(s3.fontWeight, FontWeight.w400);
expect(s3.height, 123.0);
expect(s3.color, isNull);
expect(s3, isNot(equals(s9)));
expect(s9.fontFamily, isNull);
expect(s9.fontSize, isNull);
expect(s9.fontWeight, FontWeight.w400);
expect(s9.height, isNull);
expect(s9.color, isNull);
final ui.TextStyle ts5 = s5.getTextStyle();
expect(ts5, equals(new ui.TextStyle(fontWeight: FontWeight.w700, fontSize: 12.0, height: 123.0)));
expect(ts5.toString(), 'TextStyle(color: unspecified, decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, fontWeight: FontWeight.w700, fontStyle: unspecified, textBaseline: unspecified, fontFamily: unspecified, fontSize: 12.0, letterSpacing: unspecified, wordSpacing: unspecified, height: 123.0x, locale: unspecified)');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment