From 513ff44bce174e7e5efe1afbfc9757aca969ce63 Mon Sep 17 00:00:00 2001 From: Taha Tesser Date: Fri, 2 Jun 2023 02:29:28 +0300 Subject: [PATCH] Add `FilterChip.elevated`, `ChoiceChip.elevated`, & `ActionChip.elevated` variants (#128049) --- dev/tools/gen_defaults/bin/gen_defaults.dart | 3 +- .../lib/action_chip_template.dart | 35 +- dev/tools/gen_defaults/lib/chip_template.dart | 83 +++++ .../lib/filter_chip_template.dart | 51 ++- .../flutter/lib/src/material/action_chip.dart | 69 +++- .../flutter/lib/src/material/choice_chip.dart | 89 ++++- .../flutter/lib/src/material/filter_chip.dart | 89 ++++- .../test/material/action_chip_test.dart | 160 +++++++++ .../test/material/choice_chip_test.dart | 318 ++++++++++++++++-- .../test/material/filter_chip_test.dart | 282 ++++++++++++++++ 10 files changed, 1094 insertions(+), 85 deletions(-) create mode 100644 dev/tools/gen_defaults/lib/chip_template.dart diff --git a/dev/tools/gen_defaults/bin/gen_defaults.dart b/dev/tools/gen_defaults/bin/gen_defaults.dart index 506aeb3acc..51c54760cd 100644 --- a/dev/tools/gen_defaults/bin/gen_defaults.dart +++ b/dev/tools/gen_defaults/bin/gen_defaults.dart @@ -26,6 +26,7 @@ import 'package:gen_defaults/bottom_sheet_template.dart'; import 'package:gen_defaults/button_template.dart'; import 'package:gen_defaults/card_template.dart'; import 'package:gen_defaults/checkbox_template.dart'; +import 'package:gen_defaults/chip_template.dart'; import 'package:gen_defaults/color_scheme_template.dart'; import 'package:gen_defaults/date_picker_template.dart'; import 'package:gen_defaults/dialog_template.dart'; @@ -138,7 +139,7 @@ Future main(List args) async { tokens['colorsLight'] = _readTokenFile('color_light.json'); tokens['colorsDark'] = _readTokenFile('color_dark.json'); - ActionChipTemplate('Chip', '$materialLib/chip.dart', tokens).updateFile(); + ChipTemplate('Chip', '$materialLib/chip.dart', tokens).updateFile(); ActionChipTemplate('ActionChip', '$materialLib/action_chip.dart', tokens).updateFile(); AppBarTemplate('AppBar', '$materialLib/app_bar.dart', tokens).updateFile(); BottomAppBarTemplate('BottomAppBar', '$materialLib/bottom_app_bar.dart', tokens).updateFile(); diff --git a/dev/tools/gen_defaults/lib/action_chip_template.dart b/dev/tools/gen_defaults/lib/action_chip_template.dart index 03fb3a5ede..b86b654a1f 100644 --- a/dev/tools/gen_defaults/lib/action_chip_template.dart +++ b/dev/tools/gen_defaults/lib/action_chip_template.dart @@ -11,51 +11,66 @@ class ActionChipTemplate extends TokenTemplate { }); static const String tokenGroup = 'md.comp.assist-chip'; - static const String variant = '.flat'; + static const String flatVariant = '.flat'; + static const String elevatedVariant = '.elevated'; @override String generate() => ''' class _${blockName}DefaultsM3 extends ChipThemeData { - _${blockName}DefaultsM3(this.context, this.isEnabled) + _${blockName}DefaultsM3(this.context, this.isEnabled, this._chipVariant) : super( - elevation: ${elevation("$tokenGroup$variant.container")}, shape: ${shape("$tokenGroup.container")}, showCheckmark: true, ); final BuildContext context; final bool isEnabled; + final _ChipVariant _chipVariant; late final ColorScheme _colors = Theme.of(context).colorScheme; late final TextTheme _textTheme = Theme.of(context).textTheme; + @override + double? get elevation => _chipVariant == _ChipVariant.flat + ? ${elevation("$tokenGroup$flatVariant.container")} + : isEnabled ? ${elevation("$tokenGroup$elevatedVariant.container")} : ${elevation("$tokenGroup$elevatedVariant.disabled.container")}; + + @override + double? get pressElevation => ${elevation("$tokenGroup$elevatedVariant.pressed.container")}; + @override TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")}; @override - Color? get backgroundColor => ${componentColor("$tokenGroup$variant.container")}; + Color? get backgroundColor => ${componentColor("$tokenGroup$flatVariant.container")}; @override - Color? get shadowColor => ${colorOrTransparent("$tokenGroup.container.shadow-color")}; + Color? get shadowColor => _chipVariant == _ChipVariant.flat + ? ${colorOrTransparent("$tokenGroup$flatVariant.container.shadow-color")} + : ${colorOrTransparent("$tokenGroup$elevatedVariant.container.shadow-color")}; @override Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")}; @override - Color? get selectedColor => ${componentColor("$tokenGroup$variant.selected.container")}; + Color? get selectedColor => ${componentColor("$tokenGroup$flatVariant.selected.container")}; @override Color? get checkmarkColor => ${color("$tokenGroup.with-icon.selected.icon.color")}; @override - Color? get disabledColor => ${componentColor("$tokenGroup$variant.disabled.container")}; + Color? get disabledColor => _chipVariant == _ChipVariant.flat + ? ${componentColor("$tokenGroup$flatVariant.disabled.container")} + : ${componentColor("$tokenGroup$elevatedVariant.disabled.container")}; @override Color? get deleteIconColor => ${color("$tokenGroup.with-icon.selected.icon.color")}; @override - BorderSide? get side => isEnabled - ? ${border('$tokenGroup$variant.outline')} - : ${border('$tokenGroup$variant.disabled.outline')}; + BorderSide? get side => _chipVariant == _ChipVariant.flat + ? isEnabled + ? ${border('$tokenGroup$flatVariant.outline')} + : ${border('$tokenGroup$flatVariant.disabled.outline')} + : const BorderSide(color: Colors.transparent); @override IconThemeData? get iconTheme => IconThemeData( diff --git a/dev/tools/gen_defaults/lib/chip_template.dart b/dev/tools/gen_defaults/lib/chip_template.dart new file mode 100644 index 0000000000..490a284155 --- /dev/null +++ b/dev/tools/gen_defaults/lib/chip_template.dart @@ -0,0 +1,83 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'template.dart'; + +class ChipTemplate extends TokenTemplate { + const ChipTemplate(super.blockName, super.fileName, super.tokens, { + super.colorSchemePrefix = '_colors.', + super.textThemePrefix = '_textTheme.' + }); + + static const String tokenGroup = 'md.comp.assist-chip'; + static const String variant = '.flat'; + + @override + String generate() => ''' +class _${blockName}DefaultsM3 extends ChipThemeData { + _${blockName}DefaultsM3(this.context, this.isEnabled) + : super( + elevation: ${elevation("$tokenGroup$variant.container")}, + shape: ${shape("$tokenGroup.container")}, + showCheckmark: true, + ); + + final BuildContext context; + final bool isEnabled; + late final ColorScheme _colors = Theme.of(context).colorScheme; + late final TextTheme _textTheme = Theme.of(context).textTheme; + + @override + TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")}; + + @override + Color? get backgroundColor => ${componentColor("$tokenGroup$variant.container")}; + + @override + Color? get shadowColor => ${colorOrTransparent("$tokenGroup.container.shadow-color")}; + + @override + Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")}; + + @override + Color? get selectedColor => ${componentColor("$tokenGroup$variant.selected.container")}; + + @override + Color? get checkmarkColor => ${color("$tokenGroup.with-icon.selected.icon.color")}; + + @override + Color? get disabledColor => ${componentColor("$tokenGroup$variant.disabled.container")}; + + @override + Color? get deleteIconColor => ${color("$tokenGroup.with-icon.selected.icon.color")}; + + @override + BorderSide? get side => isEnabled + ? ${border('$tokenGroup$variant.outline')} + : ${border('$tokenGroup$variant.disabled.outline')}; + + @override + IconThemeData? get iconTheme => IconThemeData( + color: isEnabled + ? ${color("$tokenGroup.with-icon.icon.color")} + : ${color("$tokenGroup.with-icon.disabled.icon.color")}, + size: ${tokens["$tokenGroup.with-icon.icon.size"]}, + ); + + @override + EdgeInsetsGeometry? get padding => const EdgeInsets.all(8.0); + + /// The chip at text scale 1 starts with 8px on each side and as text scaling + /// gets closer to 2, the label padding is linearly interpolated from 8px to 4px. + /// Once the widget has a text scaling of 2 or higher than the label padding + /// remains 4px. + @override + EdgeInsetsGeometry? get labelPadding => EdgeInsets.lerp( + const EdgeInsets.symmetric(horizontal: 8.0), + const EdgeInsets.symmetric(horizontal: 4.0), + clampDouble(MediaQuery.textScaleFactorOf(context) - 1.0, 0.0, 1.0), + )!; +} +'''; +} diff --git a/dev/tools/gen_defaults/lib/filter_chip_template.dart b/dev/tools/gen_defaults/lib/filter_chip_template.dart index 7840086d13..911bdb80c7 100644 --- a/dev/tools/gen_defaults/lib/filter_chip_template.dart +++ b/dev/tools/gen_defaults/lib/filter_chip_template.dart @@ -11,14 +11,18 @@ class FilterChipTemplate extends TokenTemplate { }); static const String tokenGroup = 'md.comp.filter-chip'; - static const String variant = '.flat'; + static const String flatVariant = '.flat'; + static const String elevatedVariant = '.elevated'; @override String generate() => ''' class _${blockName}DefaultsM3 extends ChipThemeData { - _${blockName}DefaultsM3(this.context, this.isEnabled, this.isSelected) - : super( - elevation: ${elevation("$tokenGroup$variant.container")}, + _${blockName}DefaultsM3( + this.context, + this.isEnabled, + this.isSelected, + this._chipVariant, + ) : super( shape: ${shape("$tokenGroup.container")}, showCheckmark: true, ); @@ -26,42 +30,59 @@ class _${blockName}DefaultsM3 extends ChipThemeData { final BuildContext context; final bool isEnabled; final bool isSelected; + final _ChipVariant _chipVariant; late final ColorScheme _colors = Theme.of(context).colorScheme; late final TextTheme _textTheme = Theme.of(context).textTheme; + @override + double? get elevation => _chipVariant == _ChipVariant.flat + ? ${elevation("$tokenGroup$flatVariant.container")} + : isEnabled ? ${elevation("$tokenGroup$elevatedVariant.container")} : ${elevation("$tokenGroup$elevatedVariant.disabled.container")}; + + @override + double? get pressElevation => ${elevation("$tokenGroup$elevatedVariant.pressed.container")}; + @override TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")}; @override - Color? get backgroundColor => ${componentColor("$tokenGroup$variant.container")}; + Color? get backgroundColor => ${componentColor("$tokenGroup$flatVariant.container")}; @override - Color? get shadowColor => ${colorOrTransparent("$tokenGroup.container.shadow-color")}; + Color? get shadowColor => _chipVariant == _ChipVariant.flat + ? ${colorOrTransparent("$tokenGroup$flatVariant.container.shadow-color")} + : ${colorOrTransparent("$tokenGroup$elevatedVariant.container.shadow-color")}; @override Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")}; @override - Color? get selectedColor => isEnabled - ? ${componentColor("$tokenGroup$variant.selected.container")} - : ${componentColor("$tokenGroup$variant.disabled.selected.container")}; + Color? get selectedColor => _chipVariant == _ChipVariant.flat + ? isEnabled + ? ${componentColor("$tokenGroup$flatVariant.selected.container")} + : ${componentColor("$tokenGroup$flatVariant.disabled.selected.container")} + : isEnabled + ? ${componentColor("$tokenGroup$elevatedVariant.selected.container")} + : ${componentColor("$tokenGroup$elevatedVariant.disabled.container")}; @override Color? get checkmarkColor => ${color("$tokenGroup.with-leading-icon.selected.leading-icon.color")}; @override - Color? get disabledColor => isSelected - ? ${componentColor("$tokenGroup$variant.disabled.selected.container")} - : ${componentColor("$tokenGroup$variant.disabled.unselected.container")}; + Color? get disabledColor => _chipVariant == _ChipVariant.flat + ? isSelected + ? ${componentColor("$tokenGroup$flatVariant.disabled.selected.container")} + : ${componentColor("$tokenGroup$flatVariant.disabled.unselected.container")} + : ${componentColor("$tokenGroup$elevatedVariant.disabled.container")}; @override Color? get deleteIconColor => ${color("$tokenGroup.with-trailing-icon.selected.trailing-icon.color")}; @override - BorderSide? get side => !isSelected + BorderSide? get side => _chipVariant == _ChipVariant.flat && !isSelected ? isEnabled - ? ${border('$tokenGroup$variant.unselected.outline')} - : ${border('$tokenGroup$variant.disabled.unselected.outline')} + ? ${border('$tokenGroup$flatVariant.unselected.outline')} + : ${border('$tokenGroup$flatVariant.disabled.unselected.outline')} : const BorderSide(color: Colors.transparent); @override diff --git a/packages/flutter/lib/src/material/action_chip.dart b/packages/flutter/lib/src/material/action_chip.dart index a6bbb3430b..2327f75809 100644 --- a/packages/flutter/lib/src/material/action_chip.dart +++ b/packages/flutter/lib/src/material/action_chip.dart @@ -14,6 +14,8 @@ import 'text_theme.dart'; import 'theme.dart'; import 'theme_data.dart'; +enum _ChipVariant { flat, elevated } + /// A Material Design action chip. /// /// Action chips are a set of options which trigger an action related to primary @@ -89,7 +91,40 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip this.surfaceTintColor, this.iconTheme, }) : assert(pressElevation == null || pressElevation >= 0.0), - assert(elevation == null || elevation >= 0.0); + assert(elevation == null || elevation >= 0.0), + _chipVariant = _ChipVariant.flat; + + /// Create an elevated chip that acts like a button. + /// + /// The [label], [onPressed], [autofocus], and [clipBehavior] arguments must + /// not be null. The [pressElevation] and [elevation] must be null or + /// non-negative. Typically, [pressElevation] is greater than [elevation]. + const ActionChip.elevated({ + super.key, + this.avatar, + required this.label, + this.labelStyle, + this.labelPadding, + this.onPressed, + this.pressElevation, + this.tooltip, + this.side, + this.shape, + this.clipBehavior = Clip.none, + this.focusNode, + this.autofocus = false, + this.backgroundColor, + this.disabledColor, + this.padding, + this.visualDensity, + this.materialTapTargetSize, + this.elevation, + this.shadowColor, + this.surfaceTintColor, + this.iconTheme, + }) : assert(pressElevation == null || pressElevation >= 0.0), + assert(elevation == null || elevation >= 0.0), + _chipVariant = _ChipVariant.elevated; @override final Widget? avatar; @@ -137,11 +172,13 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip @override bool get isEnabled => onPressed != null; + final _ChipVariant _chipVariant; + @override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); final ChipThemeData? defaults = Theme.of(context).useMaterial3 - ? _ActionChipDefaultsM3(context, isEnabled) + ? _ActionChipDefaultsM3(context, isEnabled, _chipVariant) : null; return RawChip( defaultProperties: defaults, @@ -180,18 +217,26 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip // Token database version: v0_162 class _ActionChipDefaultsM3 extends ChipThemeData { - _ActionChipDefaultsM3(this.context, this.isEnabled) + _ActionChipDefaultsM3(this.context, this.isEnabled, this._chipVariant) : super( - elevation: 0.0, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))), showCheckmark: true, ); final BuildContext context; final bool isEnabled; + final _ChipVariant _chipVariant; late final ColorScheme _colors = Theme.of(context).colorScheme; late final TextTheme _textTheme = Theme.of(context).textTheme; + @override + double? get elevation => _chipVariant == _ChipVariant.flat + ? 0.0 + : isEnabled ? 1.0 : 0.0; + + @override + double? get pressElevation => 1.0; + @override TextStyle? get labelStyle => _textTheme.labelLarge; @@ -199,7 +244,9 @@ class _ActionChipDefaultsM3 extends ChipThemeData { Color? get backgroundColor => null; @override - Color? get shadowColor => Colors.transparent; + Color? get shadowColor => _chipVariant == _ChipVariant.flat + ? Colors.transparent + : _colors.shadow; @override Color? get surfaceTintColor => _colors.surfaceTint; @@ -211,15 +258,19 @@ class _ActionChipDefaultsM3 extends ChipThemeData { Color? get checkmarkColor => null; @override - Color? get disabledColor => null; + Color? get disabledColor => _chipVariant == _ChipVariant.flat + ? null + : _colors.onSurface.withOpacity(0.12); @override Color? get deleteIconColor => null; @override - BorderSide? get side => isEnabled - ? BorderSide(color: _colors.outline) - : BorderSide(color: _colors.onSurface.withOpacity(0.12)); + BorderSide? get side => _chipVariant == _ChipVariant.flat + ? isEnabled + ? BorderSide(color: _colors.outline) + : BorderSide(color: _colors.onSurface.withOpacity(0.12)) + : const BorderSide(color: Colors.transparent); @override IconThemeData? get iconTheme => IconThemeData( diff --git a/packages/flutter/lib/src/material/choice_chip.dart b/packages/flutter/lib/src/material/choice_chip.dart index f8602ea9d7..9000406c4e 100644 --- a/packages/flutter/lib/src/material/choice_chip.dart +++ b/packages/flutter/lib/src/material/choice_chip.dart @@ -14,6 +14,8 @@ import 'text_theme.dart'; import 'theme.dart'; import 'theme_data.dart'; +enum _ChipVariant { flat, elevated } + /// A Material Design choice chip. /// /// [ChoiceChip]s represent a single choice from a set. Choice chips contain @@ -89,7 +91,46 @@ class ChoiceChip extends StatelessWidget this.checkmarkColor, this.avatarBorder = const CircleBorder(), }) : assert(pressElevation == null || pressElevation >= 0.0), - assert(elevation == null || elevation >= 0.0); + assert(elevation == null || elevation >= 0.0), + _chipVariant = _ChipVariant.flat; + + /// Create an elevated chip that acts like a radio button. + /// + /// The [label], [selected], [autofocus], and [clipBehavior] arguments must + /// not be null. The [pressElevation] and [elevation] must be null or + /// non-negative. Typically, [pressElevation] is greater than [elevation]. + const ChoiceChip.elevated({ + super.key, + this.avatar, + required this.label, + this.labelStyle, + this.labelPadding, + this.onSelected, + this.pressElevation, + required this.selected, + this.selectedColor, + this.disabledColor, + this.tooltip, + this.side, + this.shape, + this.clipBehavior = Clip.none, + this.focusNode, + this.autofocus = false, + this.backgroundColor, + this.padding, + this.visualDensity, + this.materialTapTargetSize, + this.elevation, + this.shadowColor, + this.surfaceTintColor, + this.iconTheme, + this.selectedShadowColor, + this.showCheckmark, + this.checkmarkColor, + this.avatarBorder = const CircleBorder(), + }) : assert(pressElevation == null || pressElevation >= 0.0), + assert(elevation == null || elevation >= 0.0), + _chipVariant = _ChipVariant.elevated; @override final Widget? avatar; @@ -149,12 +190,14 @@ class ChoiceChip extends StatelessWidget @override bool get isEnabled => onSelected != null; + final _ChipVariant _chipVariant; + @override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); final ChipThemeData chipTheme = ChipTheme.of(context); final ChipThemeData? defaults = Theme.of(context).useMaterial3 - ? _ChoiceChipDefaultsM3(context, isEnabled, selected) + ? _ChoiceChipDefaultsM3(context, isEnabled, selected, _chipVariant) : null; return RawChip( defaultProperties: defaults, @@ -200,9 +243,12 @@ class ChoiceChip extends StatelessWidget // Token database version: v0_162 class _ChoiceChipDefaultsM3 extends ChipThemeData { - _ChoiceChipDefaultsM3(this.context, this.isEnabled, this.isSelected) - : super( - elevation: 0.0, + _ChoiceChipDefaultsM3( + this.context, + this.isEnabled, + this.isSelected, + this._chipVariant, + ) : super( shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))), showCheckmark: true, ); @@ -210,9 +256,18 @@ class _ChoiceChipDefaultsM3 extends ChipThemeData { final BuildContext context; final bool isEnabled; final bool isSelected; + final _ChipVariant _chipVariant; late final ColorScheme _colors = Theme.of(context).colorScheme; late final TextTheme _textTheme = Theme.of(context).textTheme; + @override + double? get elevation => _chipVariant == _ChipVariant.flat + ? 0.0 + : isEnabled ? 1.0 : 0.0; + + @override + double? get pressElevation => 1.0; + @override TextStyle? get labelStyle => _textTheme.labelLarge; @@ -220,29 +275,37 @@ class _ChoiceChipDefaultsM3 extends ChipThemeData { Color? get backgroundColor => null; @override - Color? get shadowColor => Colors.transparent; + Color? get shadowColor => _chipVariant == _ChipVariant.flat + ? Colors.transparent + : _colors.shadow; @override Color? get surfaceTintColor => _colors.surfaceTint; @override - Color? get selectedColor => isEnabled - ? _colors.secondaryContainer - : _colors.onSurface.withOpacity(0.12); + Color? get selectedColor => _chipVariant == _ChipVariant.flat + ? isEnabled + ? _colors.secondaryContainer + : _colors.onSurface.withOpacity(0.12) + : isEnabled + ? _colors.secondaryContainer + : _colors.onSurface.withOpacity(0.12); @override Color? get checkmarkColor => _colors.onSecondaryContainer; @override - Color? get disabledColor => isSelected - ? _colors.onSurface.withOpacity(0.12) - : null; + Color? get disabledColor => _chipVariant == _ChipVariant.flat + ? isSelected + ? _colors.onSurface.withOpacity(0.12) + : null + : _colors.onSurface.withOpacity(0.12); @override Color? get deleteIconColor => _colors.onSecondaryContainer; @override - BorderSide? get side => !isSelected + BorderSide? get side => _chipVariant == _ChipVariant.flat && !isSelected ? isEnabled ? BorderSide(color: _colors.outline) : BorderSide(color: _colors.onSurface.withOpacity(0.12)) diff --git a/packages/flutter/lib/src/material/filter_chip.dart b/packages/flutter/lib/src/material/filter_chip.dart index d599fbe4e7..70376e1c2f 100644 --- a/packages/flutter/lib/src/material/filter_chip.dart +++ b/packages/flutter/lib/src/material/filter_chip.dart @@ -14,6 +14,8 @@ import 'text_theme.dart'; import 'theme.dart'; import 'theme_data.dart'; +enum _ChipVariant { flat, elevated } + /// A Material Design filter chip. /// /// Filter chips use tags or descriptive words as a way to filter content. @@ -91,7 +93,46 @@ class FilterChip extends StatelessWidget this.checkmarkColor, this.avatarBorder = const CircleBorder(), }) : assert(pressElevation == null || pressElevation >= 0.0), - assert(elevation == null || elevation >= 0.0); + assert(elevation == null || elevation >= 0.0), + _chipVariant = _ChipVariant.flat; + + /// Create an elevated chip that acts like a checkbox. + /// + /// The [selected], [label], [autofocus], and [clipBehavior] arguments must + /// not be null. The [pressElevation] and [elevation] must be null or + /// non-negative. Typically, [pressElevation] is greater than [elevation]. + const FilterChip.elevated({ + super.key, + this.avatar, + required this.label, + this.labelStyle, + this.labelPadding, + this.selected = false, + required this.onSelected, + this.pressElevation, + this.disabledColor, + this.selectedColor, + this.tooltip, + this.side, + this.shape, + this.clipBehavior = Clip.none, + this.focusNode, + this.autofocus = false, + this.backgroundColor, + this.padding, + this.visualDensity, + this.materialTapTargetSize, + this.elevation, + this.shadowColor, + this.surfaceTintColor, + this.iconTheme, + this.selectedShadowColor, + this.showCheckmark, + this.checkmarkColor, + this.avatarBorder = const CircleBorder(), + }) : assert(pressElevation == null || pressElevation >= 0.0), + assert(elevation == null || elevation >= 0.0), + _chipVariant = _ChipVariant.elevated; @override final Widget? avatar; @@ -151,11 +192,13 @@ class FilterChip extends StatelessWidget @override bool get isEnabled => onSelected != null; + final _ChipVariant _chipVariant; + @override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); final ChipThemeData? defaults = Theme.of(context).useMaterial3 - ? _FilterChipDefaultsM3(context, isEnabled, selected) + ? _FilterChipDefaultsM3(context, isEnabled, selected, _chipVariant) : null; return RawChip( defaultProperties: defaults, @@ -200,9 +243,12 @@ class FilterChip extends StatelessWidget // Token database version: v0_162 class _FilterChipDefaultsM3 extends ChipThemeData { - _FilterChipDefaultsM3(this.context, this.isEnabled, this.isSelected) - : super( - elevation: 0.0, + _FilterChipDefaultsM3( + this.context, + this.isEnabled, + this.isSelected, + this._chipVariant, + ) : super( shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))), showCheckmark: true, ); @@ -210,9 +256,18 @@ class _FilterChipDefaultsM3 extends ChipThemeData { final BuildContext context; final bool isEnabled; final bool isSelected; + final _ChipVariant _chipVariant; late final ColorScheme _colors = Theme.of(context).colorScheme; late final TextTheme _textTheme = Theme.of(context).textTheme; + @override + double? get elevation => _chipVariant == _ChipVariant.flat + ? 0.0 + : isEnabled ? 1.0 : 0.0; + + @override + double? get pressElevation => 1.0; + @override TextStyle? get labelStyle => _textTheme.labelLarge; @@ -220,29 +275,37 @@ class _FilterChipDefaultsM3 extends ChipThemeData { Color? get backgroundColor => null; @override - Color? get shadowColor => Colors.transparent; + Color? get shadowColor => _chipVariant == _ChipVariant.flat + ? Colors.transparent + : _colors.shadow; @override Color? get surfaceTintColor => _colors.surfaceTint; @override - Color? get selectedColor => isEnabled - ? _colors.secondaryContainer - : _colors.onSurface.withOpacity(0.12); + Color? get selectedColor => _chipVariant == _ChipVariant.flat + ? isEnabled + ? _colors.secondaryContainer + : _colors.onSurface.withOpacity(0.12) + : isEnabled + ? _colors.secondaryContainer + : _colors.onSurface.withOpacity(0.12); @override Color? get checkmarkColor => _colors.onSecondaryContainer; @override - Color? get disabledColor => isSelected - ? _colors.onSurface.withOpacity(0.12) - : null; + Color? get disabledColor => _chipVariant == _ChipVariant.flat + ? isSelected + ? _colors.onSurface.withOpacity(0.12) + : null + : _colors.onSurface.withOpacity(0.12); @override Color? get deleteIconColor => _colors.onSecondaryContainer; @override - BorderSide? get side => !isSelected + BorderSide? get side => _chipVariant == _ChipVariant.flat && !isSelected ? isEnabled ? BorderSide(color: _colors.outline) : BorderSide(color: _colors.onSurface.withOpacity(0.12)) diff --git a/packages/flutter/test/material/action_chip_test.dart b/packages/flutter/test/material/action_chip_test.dart index 79e33c7306..92e0c1a842 100644 --- a/packages/flutter/test/material/action_chip_test.dart +++ b/packages/flutter/test/material/action_chip_test.dart @@ -26,6 +26,24 @@ Widget wrapForChip({ ); } +Material getMaterial(WidgetTester tester) { + return tester.widget( + find.descendant( + of: find.byType(ActionChip), + matching: find.byType(Material), + ), + ); +} + +DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) { + return tester.widget( + find.ancestor( + of: find.text(labelText), + matching: find.byType(DefaultTextStyle), + ).first, + ); +} + void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { final Iterable materials = tester.widgetList(find.byType(Material)); // There should be two Material widgets, first Material is from the "_wrapForChip" and @@ -36,6 +54,148 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { } void main() { + testWidgets('ActionChip defaults', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + const String label = 'action chip'; + + // Test enabled ActionChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: Center( + child: ActionChip( + onPressed: () {}, + label: const Text(label), + ), + ), + ), + ), + ); + + // Test default chip size. + expect(tester.getSize(find.byType(ActionChip)), const Size(190.0, 48.0)); + // Test default label style. + expect( + getLabelStyle(tester, label).style.color!.value, + theme.textTheme.labelLarge!.color!.value, + ); + + Material chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, Colors.transparent); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: theme.colorScheme.outline), + ), + ); + + ShapeDecoration decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test disabled ActionChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: ActionChip( + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, Colors.transparent); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: theme.colorScheme.onSurface.withOpacity(0.12)), + ), + ); + + decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + }); + + testWidgets('ActionChip.elevated defaults', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + const String label = 'action chip'; + + // Test enabled ActionChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: Center( + child: ActionChip.elevated( + onPressed: () {}, + label: const Text(label), + ), + ), + ), + ), + ); + + // Test default chip size. + expect(tester.getSize(find.byType(ActionChip)), const Size(190.0, 48.0)); + // Test default label style. + expect( + getLabelStyle(tester, label).style.color!.value, + theme.textTheme.labelLarge!.color!.value, + ); + + Material chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 1); + expect(chipMaterial.shadowColor, theme.colorScheme.shadow); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + ShapeDecoration decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test disabled ActionChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: ActionChip.elevated( + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, theme.colorScheme.shadow); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); + }); + testWidgetsWithLeakTracking('ActionChip can be tapped', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( diff --git a/packages/flutter/test/material/choice_chip_test.dart b/packages/flutter/test/material/choice_chip_test.dart index 5ec884ceb7..1d8da36934 100644 --- a/packages/flutter/test/material/choice_chip_test.dart +++ b/packages/flutter/test/material/choice_chip_test.dart @@ -64,36 +64,267 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { void main() { testWidgets('ChoiceChip defaults', (WidgetTester tester) async { - Widget buildFrame(Brightness brightness) { - return MaterialApp( - theme: ThemeData(brightness: brightness), - home: const Scaffold( - body: Center( + final ThemeData theme = ThemeData(useMaterial3: true); + const String label = 'choice chip'; + + // Test enabled ChoiceChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: Center( child: ChoiceChip( - label: Text('Chip A'), - selected: true, + selected: false, + onSelected: (bool valueChanged) { }, + label: const Text(label), ), ), ), - ); - } + ), + ); - await tester.pumpWidget(buildFrame(Brightness.light)); - expect(getMaterialBox(tester), paints..rrect(color: const Color(0x3d000000))); - expect(tester.getSize(find.byType(ChoiceChip)), const Size(108.0, 48.0)); - expect(getMaterial(tester).color, null); - expect(getMaterial(tester).elevation, 0); - expect(getMaterial(tester).shape, const StadiumBorder()); - expect(getLabelStyle(tester, 'Chip A').style.color?.value, 0xde000000); + // Test default chip size. + expect(tester.getSize(find.byType(ChoiceChip)), const Size(190.0, 48.0)); + // Test default label style. + expect( + getLabelStyle(tester, label).style.color!.value, + theme.textTheme.labelLarge!.color!.value, + ); - await tester.pumpWidget(buildFrame(Brightness.dark)); - await tester.pumpAndSettle(); // Theme transition animation - expect(getMaterialBox(tester), paints..rrect(color: const Color(0x3dffffff))); - expect(tester.getSize(find.byType(ChoiceChip)), const Size(108.0, 48.0)); - expect(getMaterial(tester).color, null); - expect(getMaterial(tester).elevation, 0); - expect(getMaterial(tester).shape, const StadiumBorder()); - expect(getLabelStyle(tester, 'Chip A').style.color?.value, 0xdeffffff); + Material chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, Colors.transparent); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: theme.colorScheme.outline), + ), + ); + + ShapeDecoration decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test disabled ChoiceChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: ChoiceChip( + selected: false, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, Colors.transparent); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: theme.colorScheme.onSurface.withOpacity(0.12)), + ), + ); + + decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test selected enabled ChoiceChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: ChoiceChip( + selected: true, + onSelected: (bool valueChanged) { }, + label: const Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, null); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.secondaryContainer); + + // Test selected disabled ChoiceChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: ChoiceChip( + selected: true, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, null); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); + }); + + testWidgets('ChoiceChip.elevated defaults', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + const String label = 'choice chip'; + + // Test enabled ChoiceChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: Center( + child: ChoiceChip.elevated( + selected: false, + onSelected: (bool valueChanged) { }, + label: const Text(label), + ), + ), + ), + ), + ); + + // Test default chip size. + expect(tester.getSize(find.byType(ChoiceChip)), const Size(190.0, 48.0)); + // Test default label style. + expect( + getLabelStyle(tester, label).style.color!.value, + theme.textTheme.labelLarge!.color!.value, + ); + + Material chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 1); + expect(chipMaterial.shadowColor, theme.colorScheme.shadow); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + ShapeDecoration decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test disabled ChoiceChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: ChoiceChip.elevated( + selected: false, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, theme.colorScheme.shadow); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); + + // Test selected enabled ChoiceChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: ChoiceChip.elevated( + selected: true, + onSelected: (bool valueChanged) { }, + label: const Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 1); + expect(chipMaterial.shadowColor, null); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.secondaryContainer); + + // Test selected disabled ChoiceChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: ChoiceChip.elevated( + selected: false, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, theme.colorScheme.shadow); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); }); testWidgets('ChoiceChip can be tapped', (WidgetTester tester) async { @@ -163,4 +394,43 @@ void main() { expect(rawChip.showCheckmark, showCheckmark); expect(rawChip.checkmarkColor, checkmarkColor); }); + + group('Material 2', () { + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. + + testWidgets('ChoiceChip defaults', (WidgetTester tester) async { + Widget buildFrame(Brightness brightness) { + return MaterialApp( + theme: ThemeData(brightness: brightness), + home: const Scaffold( + body: Center( + child: ChoiceChip( + label: Text('Chip A'), + selected: true, + ), + ), + ), + ); + } + + await tester.pumpWidget(buildFrame(Brightness.light)); + expect(getMaterialBox(tester), paints..rrect(color: const Color(0x3d000000))); + expect(tester.getSize(find.byType(ChoiceChip)), const Size(108.0, 48.0)); + expect(getMaterial(tester).color, null); + expect(getMaterial(tester).elevation, 0); + expect(getMaterial(tester).shape, const StadiumBorder()); + expect(getLabelStyle(tester, 'Chip A').style.color?.value, 0xde000000); + + await tester.pumpWidget(buildFrame(Brightness.dark)); + await tester.pumpAndSettle(); // Theme transition animation + expect(getMaterialBox(tester), paints..rrect(color: const Color(0x3dffffff))); + expect(tester.getSize(find.byType(ChoiceChip)), const Size(108.0, 48.0)); + expect(getMaterial(tester).color, null); + expect(getMaterial(tester).elevation, 0); + expect(getMaterial(tester).shape, const StadiumBorder()); + expect(getLabelStyle(tester, 'Chip A').style.color?.value, 0xdeffffff); + }); + }); } diff --git a/packages/flutter/test/material/filter_chip_test.dart b/packages/flutter/test/material/filter_chip_test.dart index 7369a9e501..9e62544b82 100644 --- a/packages/flutter/test/material/filter_chip_test.dart +++ b/packages/flutter/test/material/filter_chip_test.dart @@ -84,7 +84,289 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { expect(materials.last.clipBehavior, clipBehavior); } +Material getMaterial(WidgetTester tester) { + return tester.widget( + find.descendant( + of: find.byType(FilterChip), + matching: find.byType(Material), + ), + ); +} + +DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) { + return tester.widget( + find.ancestor( + of: find.text(labelText), + matching: find.byType(DefaultTextStyle), + ).first, + ); +} + void main() { + testWidgets('FilterChip defaults', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + const String label = 'filter chip'; + + // Test enabled FilterChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: Center( + child: FilterChip( + onSelected: (bool valueChanged) { }, + label: const Text(label), + ), + ), + ), + ), + ); + + // Test default chip size. + expect(tester.getSize(find.byType(FilterChip)), const Size(190.0, 48.0)); + // Test default label style. + expect( + getLabelStyle(tester, label).style.color!.value, + theme.textTheme.labelLarge!.color!.value, + ); + + Material chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, Colors.transparent); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: theme.colorScheme.outline), + ), + ); + + ShapeDecoration decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test disabled FilterChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: FilterChip( + onSelected: null, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, Colors.transparent); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: theme.colorScheme.onSurface.withOpacity(0.12)), + ), + ); + + decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test selected enabled FilterChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: FilterChip( + selected: true, + onSelected: (bool valueChanged) { }, + label: const Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, null); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.secondaryContainer); + + // Test selected disabled FilterChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: FilterChip( + selected: true, + onSelected: null, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, null); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); + }); + + testWidgets('FilterChip.elevated defaults', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + const String label = 'filter chip'; + + // Test enabled FilterChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: Center( + child: FilterChip.elevated( + onSelected: (bool valueChanged) { }, + label: const Text(label), + ), + ), + ), + ), + ); + + // Test default chip size. + expect(tester.getSize(find.byType(FilterChip)), const Size(190.0, 48.0)); + // Test default label style. + expect( + getLabelStyle(tester, 'filter chip').style.color!.value, + theme.textTheme.labelLarge!.color!.value, + ); + + Material chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 1); + expect(chipMaterial.shadowColor, theme.colorScheme.shadow); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + ShapeDecoration decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test disabled FilterChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: FilterChip.elevated( + onSelected: null, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, theme.colorScheme.shadow); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); + + // Test selected enabled FilterChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: FilterChip.elevated( + selected: true, + onSelected: (bool valueChanged) { }, + label: const Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 1); + expect(chipMaterial.shadowColor, null); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.secondaryContainer); + + // Test selected disabled FilterChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: FilterChip.elevated( + selected: true, + onSelected: null, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, null); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); + }); + testWidgets('FilterChip can be tapped', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp(