Unverified Commit 467c970b authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Introduce MaterialState `color` property for chips (#128584)

fixes https://github.com/flutter/flutter/issues/115827
fixes https://github.com/flutter/flutter/issues/101325

### Description
1. This PR adds a new MaterialState `color` property to all the chips (this makes it possible to customize chips in all states from the M3 specs).
2. Updated defaults to use the new  MaterialState `color` property.
3. Updated and added new tests to all the chip test classes.

<details> 
<summary>code sample</summary> 

```dart
import 'package:flutter/material.dart';

const Color disabledColor = Colors.black26;
const Color backgroundColor = Colors.cyan;
final Color disabledSelectedColor = Colors.red.shade100;
const Color selectedColor = Colors.amber;
final MaterialStateProperty<Color> color =
    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
  if (states.contains(MaterialState.disabled) &&
      states.contains(MaterialState.selected)) {
    return disabledSelectedColor;
  }
  if (states.contains(MaterialState.disabled)) {
    return disabledColor;
  }
  if (states.contains(MaterialState.selected)) {
    return selectedColor;
  }
  return backgroundColor;
});

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        // chipTheme: ChipThemeData(color: color),
      ),
      home: const Example(),
    );
  }
}

class Example extends StatefulWidget {
  const Example({super.key});

  @override
  State<Example> createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  bool enabled = false;
  bool selected = true;

  @override
  Widget build(BuildContext context) {
    const Widget verticalSpace = SizedBox(height: 20);

    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            const SizedBox(height: 25),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                const Card(
                  elevation: 0.0,
                  color: disabledColor,
                  child: Padding(
                    padding: EdgeInsets.all(8.0),
                    child: Text('disabledColor'),
                  ),
                ),
                const Card(
                  elevation: 0.0,
                  color: backgroundColor,
                  child: Padding(
                    padding: EdgeInsets.all(8.0),
                    child: Text('backgroundColor'),
                  ),
                ),
                Card(
                  elevation: 0.0,
                  color: disabledSelectedColor,
                  child: const Padding(
                    padding: EdgeInsets.all(8.0),
                    child: Text('disabledSelectedColor'),
                  ),
                ),
                const Card(
                  elevation: 0.0,
                  color: selectedColor,
                  child: Padding(
                    padding: EdgeInsets.all(8.0),
                    child: Text('selectedColor'),
                  ),
                ),
              ],
            ),
            const Spacer(),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Column(
                  mainAxisSize: MainAxisSize.min,
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    RawChip(
                      selected: selected,
                      selectedColor: selectedColor,
                      color: color,
                      label: const Text('RawChip'),
                      isEnabled: enabled,
                      onSelected: enabled ? (bool value) {} : null,
                    ),
                    verticalSpace,
                    InputChip(
                      isEnabled: enabled,
                      selected: selected,
                      selectedColor: selectedColor,
                      color: color,
                      label: const Text('InputChip'),
                      onSelected: enabled ? (bool value) {} : null,
                    ),
                  ],
                ),
                Column(
                  mainAxisSize: MainAxisSize.min,
                  children: <Widget>[
                    FilterChip(
                      selected: selected,
                      selectedColor: selectedColor,
                      color: color,
                      label: const Text('FilterChip'),
                      onSelected: enabled ? (bool value) {} : null,
                    ),
                    verticalSpace,
                    FilterChip.elevated(
                      selected: selected,
                      selectedColor: selectedColor,
                      color: color,
                      label: const Text('FilterChip.elevated'),
                      onSelected: enabled ? (bool value) {} : null,
                    ),
                  ],
                ),
                Column(
                  mainAxisSize: MainAxisSize.min,
                  children: <Widget>[
                    ChoiceChip(
                      selected: selected,
                      selectedColor: selectedColor,
                      color: color,
                      label: const Text('ChoiceChip'),
                      onSelected: enabled ? (bool value) {} : null,
                    ),
                    verticalSpace,
                    ChoiceChip.elevated(
                      selected: selected,
                      selectedColor: selectedColor,
                      color: color,
                      label: const Text('ChoiceChip.elevated'),
                      onSelected: enabled ? (bool value) {} : null,
                    ),
                  ],
                ),
              ],
            ),
            const Spacer(),
            Row(
              children: <Widget>[
                Flexible(
                  child: SwitchListTile(
                    title: const Text('Enabled'),
                    value: enabled,
                    onChanged: (bool value) {
                      setState(() => enabled = value);
                    },
                  ),
                ),
                Flexible(
                  child: SwitchListTile(
                    title: const Text('Selected'),
                    value: selected,
                    onChanged: (bool value) {
                      setState(() => selected = value);
                    },
                  ),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

``` 
	
</details>

### Before (not possible to customize disabled and selected chips)

![Screenshot 2023-06-13 at 16 27 13](https://github.com/flutter/flutter/assets/48603081/633f09f7-16a1-469e-b326-b9cc0ed59242)

### After (using disabled and selected chips using the new  MaterialState `color` property)

![Screenshot 2023-06-13 at 16 26 53](https://github.com/flutter/flutter/assets/48603081/7f5dffb7-4074-4268-87c0-c059c2da67a8)
parent 62a1c8ee
......@@ -41,7 +41,15 @@ class _${blockName}DefaultsM3 extends ChipThemeData {
TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")};
@override
Color? get backgroundColor => ${componentColor("$tokenGroup$flatVariant.container")};
MaterialStateProperty<Color?>? get color =>
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return _chipVariant == _ChipVariant.flat
? ${componentColor("$tokenGroup$flatVariant.disabled.container")}
: ${componentColor("$tokenGroup$elevatedVariant.disabled.container")};
}
return ${componentColor("$tokenGroup$flatVariant.container")};
});
@override
Color? get shadowColor => _chipVariant == _ChipVariant.flat
......@@ -51,17 +59,9 @@ class _${blockName}DefaultsM3 extends ChipThemeData {
@override
Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")};
@override
Color? get selectedColor => ${componentColor("$tokenGroup$flatVariant.selected.container")};
@override
Color? get checkmarkColor => ${color("$tokenGroup.with-icon.selected.icon.color")};
@override
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")};
......
......@@ -32,7 +32,7 @@ class _${blockName}DefaultsM3 extends ChipThemeData {
TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")};
@override
Color? get backgroundColor => ${componentColor("$tokenGroup$variant.container")};
MaterialStateProperty<Color?>? get color => null; // Subclasses override this getter
@override
Color? get shadowColor => ${colorOrTransparent("$tokenGroup.container.shadow-color")};
......@@ -40,15 +40,9 @@ class _${blockName}DefaultsM3 extends ChipThemeData {
@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")};
......
......@@ -46,7 +46,27 @@ class _${blockName}DefaultsM3 extends ChipThemeData {
TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")};
@override
Color? get backgroundColor => ${componentColor("$tokenGroup$flatVariant.container")};
MaterialStateProperty<Color?>? get color =>
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected) && states.contains(MaterialState.disabled)) {
return _chipVariant == _ChipVariant.flat
? ${componentColor("$tokenGroup$flatVariant.disabled.selected.container")}
: ${componentColor("$tokenGroup$elevatedVariant.disabled.container")};
}
if (states.contains(MaterialState.disabled)) {
return _chipVariant == _ChipVariant.flat
? ${componentColor("$tokenGroup$flatVariant.disabled.unselected.container")}
: ${componentColor("$tokenGroup$elevatedVariant.disabled.container")};
}
if (states.contains(MaterialState.selected)) {
return _chipVariant == _ChipVariant.flat
? ${componentColor("$tokenGroup$flatVariant.selected.container")}
: ${componentColor("$tokenGroup$elevatedVariant.selected.container")};
}
return _chipVariant == _ChipVariant.flat
? ${componentColor("$tokenGroup$flatVariant.container")}
: ${componentColor("$tokenGroup$elevatedVariant.container")};
});
@override
Color? get shadowColor => _chipVariant == _ChipVariant.flat
......@@ -56,25 +76,9 @@ class _${blockName}DefaultsM3 extends ChipThemeData {
@override
Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")};
@override
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 => _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")};
......
......@@ -33,7 +33,19 @@ class _${blockName}DefaultsM3 extends ChipThemeData {
TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")};
@override
Color? get backgroundColor => ${componentColor("$tokenGroup$variant.container")};
MaterialStateProperty<Color?>? get color =>
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected) && states.contains(MaterialState.disabled)) {
return ${componentColor("$tokenGroup$variant.disabled.selected.container")};
}
if (states.contains(MaterialState.disabled)) {
return ${componentColor("$tokenGroup$variant.disabled.container")};
}
if (states.contains(MaterialState.selected)) {
return ${componentColor("$tokenGroup$variant.selected.container")};
}
return ${componentColor("$tokenGroup$variant.container")};
});
@override
Color? get shadowColor => ${colorOrTransparent("$tokenGroup.container.shadow-color")};
......@@ -41,17 +53,9 @@ class _${blockName}DefaultsM3 extends ChipThemeData {
@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")};
@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-trailing-icon.selected.trailing-icon.color")};
......
......@@ -10,6 +10,7 @@ import 'chip_theme.dart';
import 'color_scheme.dart';
import 'colors.dart';
import 'debug.dart';
import 'material_state.dart';
import 'text_theme.dart';
import 'theme.dart';
import 'theme_data.dart';
......@@ -81,6 +82,7 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.color,
this.backgroundColor,
this.disabledColor,
this.padding,
......@@ -113,6 +115,7 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.color,
this.backgroundColor,
this.disabledColor,
this.padding,
......@@ -151,6 +154,8 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
@override
final bool autofocus;
@override
final MaterialStateProperty<Color?>? color;
@override
final Color? backgroundColor;
@override
final Color? disabledColor;
......@@ -188,6 +193,7 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
pressElevation: pressElevation,
tooltip: tooltip,
labelStyle: labelStyle,
color: color,
backgroundColor: backgroundColor,
side: side,
shape: shape,
......@@ -239,7 +245,15 @@ class _ActionChipDefaultsM3 extends ChipThemeData {
TextStyle? get labelStyle => _textTheme.labelLarge;
@override
Color? get backgroundColor => null;
MaterialStateProperty<Color?>? get color =>
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return _chipVariant == _ChipVariant.flat
? null
: _colors.onSurface.withOpacity(0.12);
}
return null;
});
@override
Color? get shadowColor => _chipVariant == _ChipVariant.flat
......@@ -249,17 +263,9 @@ class _ActionChipDefaultsM3 extends ChipThemeData {
@override
Color? get surfaceTintColor => _colors.surfaceTint;
@override
Color? get selectedColor => null;
@override
Color? get checkmarkColor => null;
@override
Color? get disabledColor => _chipVariant == _ChipVariant.flat
? null
: _colors.onSurface.withOpacity(0.12);
@override
Color? get deleteIconColor => null;
......
......@@ -137,6 +137,13 @@ abstract interface class ChipAttributes {
/// {@macro flutter.widgets.Focus.autofocus}
bool get autofocus;
/// The color that fills the chip, in all [MaterialState]s.
///
/// Resolves in the following states:
/// * [MaterialState.selected].
/// * [MaterialState.disabled].
MaterialStateProperty<Color?>? get color;
/// Color to be used for the unselected, enabled chip's background.
///
/// The default is light grey.
......@@ -561,6 +568,7 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.color,
this.backgroundColor,
this.padding,
this.visualDensity,
......@@ -595,6 +603,8 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri
@override
final bool autofocus;
@override
final MaterialStateProperty<Color?>? color;
@override
final Color? backgroundColor;
@override
final EdgeInsetsGeometry? padding;
......@@ -644,6 +654,7 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri
clipBehavior: clipBehavior,
focusNode: focusNode,
autofocus: autofocus,
color: color,
backgroundColor: backgroundColor,
padding: padding,
visualDensity: visualDensity,
......@@ -729,6 +740,7 @@ class RawChip extends StatefulWidget
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.color,
this.backgroundColor,
this.materialTapTargetSize,
this.elevation,
......@@ -798,6 +810,8 @@ class RawChip extends StatefulWidget
@override
final bool autofocus;
@override
final MaterialStateProperty<Color?>? color;
@override
final Color? backgroundColor;
@override
final EdgeInsetsGeometry? padding;
......@@ -987,23 +1001,47 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
return resolvedShape.copyWith(side: resolvedSide);
}
Color? resolveColor({
MaterialStateProperty<Color?>? color,
Color? selectedColor,
Color? backgroundColor,
Color? disabledColor,
MaterialStateProperty<Color?>? defaultColor,
}) {
return _IndividualOverrides(
color: color,
selectedColor: selectedColor,
backgroundColor: backgroundColor,
disabledColor: disabledColor,
).resolve(materialStates) ?? defaultColor?.resolve(materialStates);
}
/// Picks between three different colors, depending upon the state of two
/// different animations.
Color? _getBackgroundColor(ThemeData theme, ChipThemeData chipTheme, ChipThemeData chipDefaults) {
if (theme.useMaterial3) {
final Color? disabledColor = resolveColor(
color: widget.color ?? chipTheme.color,
disabledColor: widget.disabledColor ?? chipTheme.disabledColor,
defaultColor: chipDefaults.color,
);
final Color? backgroundColor = resolveColor(
color: widget.color ?? chipTheme.color,
backgroundColor: widget.backgroundColor ?? chipTheme.backgroundColor,
defaultColor: chipDefaults.color,
);
final Color? selectedColor = resolveColor(
color: widget.color ?? chipTheme.color,
selectedColor: widget.selectedColor ?? chipTheme.selectedColor,
defaultColor: chipDefaults.color,
);
final ColorTween backgroundTween = ColorTween(
begin: widget.disabledColor
?? chipTheme.disabledColor
?? chipDefaults.disabledColor,
end: widget.backgroundColor
?? chipTheme.backgroundColor
?? chipDefaults.backgroundColor,
begin: disabledColor,
end: backgroundColor,
);
final ColorTween selectTween = ColorTween(
begin: backgroundTween.evaluate(enableController),
end: widget.selectedColor
?? chipTheme.selectedColor
?? chipDefaults.selectedColor,
end: selectedColor,
);
return selectTween.evaluate(selectionFade);
} else {
......@@ -1295,6 +1333,37 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
}
}
class _IndividualOverrides extends MaterialStateProperty<Color?> {
_IndividualOverrides({
this.color,
this.backgroundColor,
this.selectedColor,
this.disabledColor,
});
final MaterialStateProperty<Color?>? color;
final Color? backgroundColor;
final Color? selectedColor;
final Color? disabledColor;
@override
Color? resolve(Set<MaterialState> states) {
if (color != null) {
return color!.resolve(states);
}
if (states.contains(MaterialState.selected) && states.contains(MaterialState.disabled)) {
return selectedColor;
}
if (states.contains(MaterialState.disabled)) {
return disabledColor;
}
if (states.contains(MaterialState.selected)) {
return selectedColor;
}
return backgroundColor;
}
}
/// Redirects the [buttonRect.dy] passed to [RenderBox.hitTest] to the vertical
/// center of the widget.
///
......@@ -2176,7 +2245,7 @@ class _ChipDefaultsM3 extends ChipThemeData {
TextStyle? get labelStyle => _textTheme.labelLarge;
@override
Color? get backgroundColor => null;
MaterialStateProperty<Color?>? get color => null; // Subclasses override this getter
@override
Color? get shadowColor => Colors.transparent;
......@@ -2184,15 +2253,9 @@ class _ChipDefaultsM3 extends ChipThemeData {
@override
Color? get surfaceTintColor => _colors.surfaceTint;
@override
Color? get selectedColor => null;
@override
Color? get checkmarkColor => null;
@override
Color? get disabledColor => null;
@override
Color? get deleteIconColor => null;
......
......@@ -9,6 +9,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'material_state.dart';
import 'theme.dart';
/// Applies a chip theme to descendant [RawChip]-based widgets, like [Chip],
......@@ -178,6 +179,7 @@ class ChipThemeData with Diagnosticable {
/// This will rarely be used directly. It is used by [lerp] to
/// create intermediate themes based on two themes.
const ChipThemeData({
this.color,
this.backgroundColor,
this.deleteIconColor,
this.disabledColor,
......@@ -268,6 +270,12 @@ class ChipThemeData with Diagnosticable {
);
}
/// Overrides the default for [ChipAttributes.color].
///
/// This property applies to [ActionChip], [Chip], [ChoiceChip],
/// [FilterChip], [InputChip], [RawChip].
final MaterialStateProperty<Color?>? color;
/// Overrides the default for [ChipAttributes.backgroundColor]
/// which is used for unselected, enabled chip backgrounds.
///
......@@ -433,6 +441,7 @@ class ChipThemeData with Diagnosticable {
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
ChipThemeData copyWith({
MaterialStateProperty<Color?>? color,
Color? backgroundColor,
Color? deleteIconColor,
Color? disabledColor,
......@@ -455,6 +464,7 @@ class ChipThemeData with Diagnosticable {
IconThemeData? iconTheme,
}) {
return ChipThemeData(
color: color ?? this.color,
backgroundColor: backgroundColor ?? this.backgroundColor,
deleteIconColor: deleteIconColor ?? this.deleteIconColor,
disabledColor: disabledColor ?? this.disabledColor,
......@@ -488,6 +498,7 @@ class ChipThemeData with Diagnosticable {
return a;
}
return ChipThemeData(
color: MaterialStateProperty.lerp<Color?>(a?.color, b?.color, t, Color.lerp),
backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
deleteIconColor: Color.lerp(a?.deleteIconColor, b?.deleteIconColor, t),
disabledColor: Color.lerp(a?.disabledColor, b?.disabledColor, t),
......@@ -537,6 +548,7 @@ class ChipThemeData with Diagnosticable {
@override
int get hashCode => Object.hashAll(<Object?>[
color,
backgroundColor,
deleteIconColor,
disabledColor,
......@@ -568,6 +580,7 @@ class ChipThemeData with Diagnosticable {
return false;
}
return other is ChipThemeData
&& other.color == color
&& other.backgroundColor == backgroundColor
&& other.deleteIconColor == deleteIconColor
&& other.disabledColor == disabledColor
......@@ -593,6 +606,7 @@ class ChipThemeData with Diagnosticable {
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('color', color, defaultValue: null));
properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
properties.add(ColorProperty('deleteIconColor', deleteIconColor, defaultValue: null));
properties.add(ColorProperty('disabledColor', disabledColor, defaultValue: null));
......
......@@ -10,6 +10,7 @@ import 'chip_theme.dart';
import 'color_scheme.dart';
import 'colors.dart';
import 'debug.dart';
import 'material_state.dart';
import 'text_theme.dart';
import 'theme.dart';
import 'theme_data.dart';
......@@ -78,6 +79,7 @@ class ChoiceChip extends StatelessWidget
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.color,
this.backgroundColor,
this.padding,
this.visualDensity,
......@@ -116,6 +118,7 @@ class ChoiceChip extends StatelessWidget
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.color,
this.backgroundColor,
this.padding,
this.visualDensity,
......@@ -163,6 +166,8 @@ class ChoiceChip extends StatelessWidget
@override
final bool autofocus;
@override
final MaterialStateProperty<Color?>? color;
@override
final Color? backgroundColor;
@override
final EdgeInsetsGeometry? padding;
......@@ -218,6 +223,7 @@ class ChoiceChip extends StatelessWidget
autofocus: autofocus,
disabledColor: disabledColor,
selectedColor: selectedColor ?? chipTheme.secondarySelectedColor,
color: color,
backgroundColor: backgroundColor,
padding: padding,
visualDensity: visualDensity,
......@@ -270,7 +276,27 @@ class _ChoiceChipDefaultsM3 extends ChipThemeData {
TextStyle? get labelStyle => _textTheme.labelLarge;
@override
Color? get backgroundColor => null;
MaterialStateProperty<Color?>? get color =>
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected) && states.contains(MaterialState.disabled)) {
return _chipVariant == _ChipVariant.flat
? _colors.onSurface.withOpacity(0.12)
: _colors.onSurface.withOpacity(0.12);
}
if (states.contains(MaterialState.disabled)) {
return _chipVariant == _ChipVariant.flat
? null
: _colors.onSurface.withOpacity(0.12);
}
if (states.contains(MaterialState.selected)) {
return _chipVariant == _ChipVariant.flat
? _colors.secondaryContainer
: _colors.secondaryContainer;
}
return _chipVariant == _ChipVariant.flat
? null
: null;
});
@override
Color? get shadowColor => _chipVariant == _ChipVariant.flat
......@@ -280,25 +306,9 @@ class _ChoiceChipDefaultsM3 extends ChipThemeData {
@override
Color? get surfaceTintColor => _colors.surfaceTint;
@override
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 => _chipVariant == _ChipVariant.flat
? isSelected
? _colors.onSurface.withOpacity(0.12)
: null
: _colors.onSurface.withOpacity(0.12);
@override
Color? get deleteIconColor => _colors.onSecondaryContainer;
......
......@@ -10,6 +10,7 @@ import 'chip_theme.dart';
import 'color_scheme.dart';
import 'colors.dart';
import 'debug.dart';
import 'material_state.dart';
import 'text_theme.dart';
import 'theme.dart';
import 'theme_data.dart';
......@@ -80,6 +81,7 @@ class FilterChip extends StatelessWidget
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.color,
this.backgroundColor,
this.padding,
this.visualDensity,
......@@ -118,6 +120,7 @@ class FilterChip extends StatelessWidget
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.color,
this.backgroundColor,
this.padding,
this.visualDensity,
......@@ -165,6 +168,8 @@ class FilterChip extends StatelessWidget
@override
final bool autofocus;
@override
final MaterialStateProperty<Color?>? color;
@override
final Color? backgroundColor;
@override
final EdgeInsetsGeometry? padding;
......@@ -215,6 +220,7 @@ class FilterChip extends StatelessWidget
clipBehavior: clipBehavior,
focusNode: focusNode,
autofocus: autofocus,
color: color,
backgroundColor: backgroundColor,
disabledColor: disabledColor,
selectedColor: selectedColor,
......@@ -270,7 +276,27 @@ class _FilterChipDefaultsM3 extends ChipThemeData {
TextStyle? get labelStyle => _textTheme.labelLarge;
@override
Color? get backgroundColor => null;
MaterialStateProperty<Color?>? get color =>
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected) && states.contains(MaterialState.disabled)) {
return _chipVariant == _ChipVariant.flat
? _colors.onSurface.withOpacity(0.12)
: _colors.onSurface.withOpacity(0.12);
}
if (states.contains(MaterialState.disabled)) {
return _chipVariant == _ChipVariant.flat
? null
: _colors.onSurface.withOpacity(0.12);
}
if (states.contains(MaterialState.selected)) {
return _chipVariant == _ChipVariant.flat
? _colors.secondaryContainer
: _colors.secondaryContainer;
}
return _chipVariant == _ChipVariant.flat
? null
: null;
});
@override
Color? get shadowColor => _chipVariant == _ChipVariant.flat
......@@ -280,25 +306,9 @@ class _FilterChipDefaultsM3 extends ChipThemeData {
@override
Color? get surfaceTintColor => _colors.surfaceTint;
@override
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 => _chipVariant == _ChipVariant.flat
? isSelected
? _colors.onSurface.withOpacity(0.12)
: null
: _colors.onSurface.withOpacity(0.12);
@override
Color? get deleteIconColor => _colors.onSecondaryContainer;
......
......@@ -11,6 +11,7 @@ import 'color_scheme.dart';
import 'colors.dart';
import 'debug.dart';
import 'icons.dart';
import 'material_state.dart';
import 'text_theme.dart';
import 'theme.dart';
import 'theme_data.dart';
......@@ -99,6 +100,7 @@ class InputChip extends StatelessWidget
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.color,
this.backgroundColor,
this.padding,
this.visualDensity,
......@@ -162,6 +164,8 @@ class InputChip extends StatelessWidget
@override
final bool autofocus;
@override
final MaterialStateProperty<Color?>? color;
@override
final Color? backgroundColor;
@override
final EdgeInsetsGeometry? padding;
......@@ -223,6 +227,7 @@ class InputChip extends StatelessWidget
clipBehavior: clipBehavior,
focusNode: focusNode,
autofocus: autofocus,
color: color,
backgroundColor: backgroundColor,
padding: padding,
visualDensity: visualDensity,
......@@ -264,7 +269,19 @@ class _InputChipDefaultsM3 extends ChipThemeData {
TextStyle? get labelStyle => _textTheme.labelLarge;
@override
Color? get backgroundColor => null;
MaterialStateProperty<Color?>? get color =>
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected) && states.contains(MaterialState.disabled)) {
return _colors.onSurface.withOpacity(0.12);
}
if (states.contains(MaterialState.disabled)) {
return null;
}
if (states.contains(MaterialState.selected)) {
return _colors.secondaryContainer;
}
return null;
});
@override
Color? get shadowColor => Colors.transparent;
......@@ -272,17 +289,9 @@ class _InputChipDefaultsM3 extends ChipThemeData {
@override
Color? get surfaceTintColor => Colors.transparent;
@override
Color? get selectedColor => isEnabled
? _colors.secondaryContainer
: _colors.onSurface.withOpacity(0.12);
@override
Color? get checkmarkColor => null;
@override
Color? get disabledColor => null;
@override
Color? get deleteIconColor => _colors.onSecondaryContainer;
......
......@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '../foundation/leak_tracking.dart';
import '../rendering/mock_canvas.dart';
/// Adds the basic requirements for a Chip.
Widget wrapForChip({
......@@ -13,9 +14,10 @@ Widget wrapForChip({
TextDirection textDirection = TextDirection.ltr,
double textScaleFactor = 1.0,
Brightness brightness = Brightness.light,
bool? useMaterial3,
}) {
return MaterialApp(
theme: ThemeData(brightness: brightness),
theme: ThemeData(brightness: brightness, useMaterial3: useMaterial3),
home: Directionality(
textDirection: textDirection,
child: MediaQuery(
......@@ -26,6 +28,15 @@ Widget wrapForChip({
);
}
RenderBox getMaterialBox(WidgetTester tester, Finder type) {
return tester.firstRenderObject<RenderBox>(
find.descendant(
of: type,
matching: find.byType(CustomPaint),
),
);
}
Material getMaterial(WidgetTester tester) {
return tester.widget<Material>(
find.descendant(
......@@ -196,6 +207,120 @@ void main() {
expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12));
});
testWidgets('ActionChip.color resolves material states', (WidgetTester tester) async {
const Color disabledColor = Color(0xff00ff00);
const Color backgroundColor = Color(0xff0000ff);
final MaterialStateProperty<Color?> color = MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return disabledColor;
}
return backgroundColor;
});
Widget buildApp({ required bool enabled, required bool selected }) {
return wrapForChip(
useMaterial3: true,
child: Column(
children: <Widget>[
ActionChip(
onPressed: enabled ? () { } : null,
color: color,
label: const Text('ActionChip'),
),
ActionChip.elevated(
onPressed: enabled ? () { } : null,
color: color,
label: const Text('ActionChip.elevated'),
),
],
),
);
}
// Test enabled state.
await tester.pumpWidget(buildApp(enabled: true, selected: false));
// Enabled ActionChip should have the provided backgroundColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: backgroundColor),
);
// Enabled elevated ActionChip should have the provided backgroundColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: backgroundColor),
);
// Test disabled state.
await tester.pumpWidget(buildApp(enabled: false, selected: false));
await tester.pumpAndSettle();
// Disabled ActionChip should have the provided disabledColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: disabledColor),
);
// Disabled elevated ActionChip should have the provided disabledColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: disabledColor),
);
});
testWidgets('ActionChip uses provided state color properties', (WidgetTester tester) async {
const Color disabledColor = Color(0xff00ff00);
const Color backgroundColor = Color(0xff0000ff);
Widget buildApp({ required bool enabled, required bool selected }) {
return wrapForChip(
useMaterial3: true,
child: Column(
children: <Widget>[
ActionChip(
onPressed: enabled ? () { } : null,
disabledColor: disabledColor,
backgroundColor: backgroundColor,
label: const Text('ActionChip'),
),
ActionChip.elevated(
onPressed: enabled ? () { } : null,
disabledColor: disabledColor,
backgroundColor: backgroundColor,
label: const Text('ActionChip.elevated'),
),
],
),
);
}
// Test enabled state.
await tester.pumpWidget(buildApp(enabled: true, selected: false));
// Enabled ActionChip should have the provided backgroundColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: backgroundColor),
);
// Enabled elevated ActionChip should have the provided backgroundColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: backgroundColor),
);
// Test disabled state.
await tester.pumpWidget(buildApp(enabled: false, selected: false));
await tester.pumpAndSettle();
// Disabled ActionChip should have the provided disabledColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: disabledColor),
);
// Disabled elevated ActionChip should have the provided disabledColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: disabledColor),
);
});
testWidgetsWithLeakTracking('ActionChip can be tapped', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
......
......@@ -50,6 +50,7 @@ void main() {
test('ChipThemeData defaults', () {
const ChipThemeData themeData = ChipThemeData();
expect(themeData.color, null);
expect(themeData.backgroundColor, null);
expect(themeData.deleteIconColor, null);
expect(themeData.disabledColor, null);
......@@ -86,16 +87,17 @@ void main() {
testWidgets('ChipThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const ChipThemeData(
backgroundColor: Color(0xfffffff0),
deleteIconColor: Color(0xfffffff1),
disabledColor: Color(0xfffffff2),
selectedColor: Color(0xfffffff3),
secondarySelectedColor: Color(0xfffffff4),
shadowColor: Color(0xfffffff5),
surfaceTintColor: Color(0xfffffff8),
selectedShadowColor: Color(0xfffffff6),
color: MaterialStatePropertyAll<Color>(Color(0xfffffff0)),
backgroundColor: Color(0xfffffff1),
deleteIconColor: Color(0xfffffff2),
disabledColor: Color(0xfffffff3),
selectedColor: Color(0xfffffff4),
secondarySelectedColor: Color(0xfffffff5),
shadowColor: Color(0xfffffff6),
surfaceTintColor: Color(0xfffffff7),
selectedShadowColor: Color(0xfffffff8),
showCheckmark: true,
checkmarkColor: Color(0xfffffff7),
checkmarkColor: Color(0xfffffff9),
labelPadding: EdgeInsets.all(1),
padding: EdgeInsets.all(2),
side: BorderSide(width: 10),
......@@ -113,16 +115,17 @@ void main() {
.toList();
expect(description, <String>[
'backgroundColor: Color(0xfffffff0)',
'deleteIconColor: Color(0xfffffff1)',
'disabledColor: Color(0xfffffff2)',
'selectedColor: Color(0xfffffff3)',
'secondarySelectedColor: Color(0xfffffff4)',
'shadowColor: Color(0xfffffff5)',
'surfaceTintColor: Color(0xfffffff8)',
'selectedShadowColor: Color(0xfffffff6)',
'color: MaterialStatePropertyAll(Color(0xfffffff0))',
'backgroundColor: Color(0xfffffff1)',
'deleteIconColor: Color(0xfffffff2)',
'disabledColor: Color(0xfffffff3)',
'selectedColor: Color(0xfffffff4)',
'secondarySelectedColor: Color(0xfffffff5)',
'shadowColor: Color(0xfffffff6)',
'surfaceTintColor: Color(0xfffffff7)',
'selectedShadowColor: Color(0xfffffff8)',
'showCheckmark: true',
'checkMarkColor: Color(0xfffffff7)',
'checkMarkColor: Color(0xfffffff9)',
'labelPadding: EdgeInsets.all(1.0)',
'padding: EdgeInsets.all(2.0)',
'side: BorderSide(width: 10.0)',
......@@ -330,7 +333,6 @@ void main() {
expect(chipTheme.pressElevation, 8.0);
});
testWidgets('ChipThemeData generates correct opacities for defaults', (WidgetTester tester) async {
const Color customColor1 = Color(0xcafefeed);
const Color customColor2 = Color(0xdeadbeef);
......@@ -760,6 +762,111 @@ void main() {
await tester.pumpWidget(chipWidget(selected: true));
expect(getMaterial(tester).shape, isA<RoundedRectangleBorder>());
});
testWidgets('RawChip uses material state color from ChipTheme', (WidgetTester tester) async {
const Color disabledSelectedColor = Color(0xffffff00);
const Color disabledColor = Color(0xff00ff00);
const Color backgroundColor = Color(0xff0000ff);
const Color selectedColor = Color(0xffff0000);
Widget buildApp({ required bool enabled, required bool selected }) {
return MaterialApp(
theme: ThemeData(
chipTheme: ChipThemeData(
color: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)
&& states.contains(MaterialState.selected)) {
return disabledSelectedColor;
}
if (states.contains(MaterialState.disabled)) {
return disabledColor;
}
if (states.contains(MaterialState.selected)) {
return selectedColor;
}
return backgroundColor;
}),
),
useMaterial3: true,
),
home: Material(
child: RawChip(
isEnabled: enabled,
selected: selected,
label: const Text('RawChip'),
),
),
);
}
// Check theme color for enabled chip.
await tester.pumpWidget(buildApp(enabled: true, selected: false));
await tester.pumpAndSettle();
// Enabled chip should have the provided backgroundColor.
expect(getMaterialBox(tester), paints..rrect(color: backgroundColor));
// Check theme color for disabled chip.
await tester.pumpWidget(buildApp(enabled: false, selected: false));
await tester.pumpAndSettle();
// Disabled chip should have the provided disabledColor.
expect(getMaterialBox(tester),paints..rrect(color: disabledColor));
// Check theme color for enabled and selected chip.
await tester.pumpWidget(buildApp(enabled: true, selected: true));
await tester.pumpAndSettle();
// Enabled & selected chip should have the provided selectedColor.
expect(getMaterialBox(tester), paints..rrect(color: selectedColor));
// Check theme color for disabled & selected chip.
await tester.pumpWidget(buildApp(enabled: false, selected: true));
await tester.pumpAndSettle();
// Disabled & selected chip should have the provided disabledSelectedColor.
expect(getMaterialBox(tester), paints..rrect(color: disabledSelectedColor));
});
testWidgets('RawChip uses state colors from ChipTheme', (WidgetTester tester) async {
const ChipThemeData chipTheme = ChipThemeData(
disabledColor: Color(0xadfefafe),
backgroundColor: Color(0xcafefeed),
selectedColor: Color(0xbeefcafe),
);
Widget buildApp({ required bool enabled, required bool selected }) {
return MaterialApp(
theme: ThemeData(chipTheme: chipTheme, useMaterial3: true),
home: Material(
child: RawChip(
isEnabled: enabled,
selected: selected,
label: const Text('RawChip'),
),
),
);
}
// Check theme color for enabled chip.
await tester.pumpWidget(buildApp(enabled: true, selected: false));
await tester.pumpAndSettle();
// Enabled chip should have the provided backgroundColor.
expect(getMaterialBox(tester), paints..rrect(color: chipTheme.backgroundColor));
// Check theme color for disabled chip.
await tester.pumpWidget(buildApp(enabled: false, selected: false));
await tester.pumpAndSettle();
// Disabled chip should have the provided disabledColor.
expect(getMaterialBox(tester),paints..rrect(color: chipTheme.disabledColor));
// Check theme color for enabled and selected chip.
await tester.pumpWidget(buildApp(enabled: true, selected: true));
await tester.pumpAndSettle();
// Enabled & selected chip should have the provided selectedColor.
expect(getMaterialBox(tester), paints..rrect(color: chipTheme.selectedColor));
});
}
class _MaterialStateOutlinedBorder extends StadiumBorder implements MaterialStateOutlinedBorder {
......
......@@ -7,10 +7,10 @@ import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
RenderBox getMaterialBox(WidgetTester tester) {
RenderBox getMaterialBox(WidgetTester tester, Finder type) {
return tester.firstRenderObject<RenderBox>(
find.descendant(
of: find.byType(RawChip),
of: type,
matching: find.byType(CustomPaint),
),
);
......@@ -19,7 +19,7 @@ RenderBox getMaterialBox(WidgetTester tester) {
Material getMaterial(WidgetTester tester) {
return tester.widget<Material>(
find.descendant(
of: find.byType(RawChip),
of: find.byType(ChoiceChip),
matching: find.byType(Material),
),
);
......@@ -40,9 +40,10 @@ Widget wrapForChip({
TextDirection textDirection = TextDirection.ltr,
double textScaleFactor = 1.0,
Brightness brightness = Brightness.light,
bool? useMaterial3,
}) {
return MaterialApp(
theme: ThemeData(brightness: brightness),
theme: ThemeData(brightness: brightness, useMaterial3: useMaterial3),
home: Directionality(
textDirection: textDirection,
child: MediaQuery(
......@@ -327,6 +328,180 @@ void main() {
expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12));
});
testWidgets('ChoiceChip.color resolves material states', (WidgetTester tester) async {
const Color disabledSelectedColor = Color(0xffffff00);
const Color disabledColor = Color(0xff00ff00);
const Color backgroundColor = Color(0xff0000ff);
const Color selectedColor = Color(0xffff0000);
final MaterialStateProperty<Color?> color = MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled) && states.contains(MaterialState.selected)) {
return disabledSelectedColor;
}
if (states.contains(MaterialState.disabled)) {
return disabledColor;
}
if (states.contains(MaterialState.selected)) {
return selectedColor;
}
return backgroundColor;
});
Widget buildApp({ required bool enabled, required bool selected }) {
return wrapForChip(
useMaterial3: true,
child: Column(
children: <Widget>[
ChoiceChip(
onSelected: enabled ? (bool value) { } : null,
selected: selected,
color: color,
label: const Text('ChoiceChip'),
),
ChoiceChip.elevated(
onSelected: enabled ? (bool value) { } : null,
selected: selected,
color: color,
label: const Text('ChoiceChip.elevated'),
),
],
),
);
}
// Test enabled state.
await tester.pumpWidget(buildApp(enabled: true, selected: false));
// Enabled ChoiceChip should have the provided backgroundColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: backgroundColor),
);
// Enabled elevated ChoiceChip should have the provided backgroundColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: backgroundColor),
);
// Test disabled state.
await tester.pumpWidget(buildApp(enabled: false, selected: false));
await tester.pumpAndSettle();
// Disabled ChoiceChip should have the provided disabledColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: disabledColor),
);
// Disabled elevated ChoiceChip should have the provided disabledColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: disabledColor),
);
// Test enabled & selected state.
await tester.pumpWidget(buildApp(enabled: true, selected: true));
await tester.pumpAndSettle();
// Enabled & selected ChoiceChip should have the provided selectedColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: selectedColor),
);
// Enabled & selected elevated ChoiceChip should have the provided selectedColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: selectedColor),
);
// Test disabled & selected state.
await tester.pumpWidget(buildApp(enabled: false, selected: true));
await tester.pumpAndSettle();
// Disabled & selected ChoiceChip should have the provided disabledSelectedColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: disabledSelectedColor),
);
// Disabled & selected elevated ChoiceChip should have the provided disabledSelectedColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: disabledSelectedColor),
);
});
testWidgets('ChoiceChip uses provided state color properties', (WidgetTester tester) async {
const Color disabledColor = Color(0xff00ff00);
const Color backgroundColor = Color(0xff0000ff);
const Color selectedColor = Color(0xffff0000);
Widget buildApp({ required bool enabled, required bool selected }) {
return wrapForChip(
useMaterial3: true,
child: Column(
children: <Widget>[
ChoiceChip(
onSelected: enabled ? (bool value) { } : null,
selected: selected,
disabledColor: disabledColor,
backgroundColor: backgroundColor,
selectedColor: selectedColor,
label: const Text('ChoiceChip'),
),
ChoiceChip.elevated(
onSelected: enabled ? (bool value) { } : null,
selected: selected,
disabledColor: disabledColor,
backgroundColor: backgroundColor,
selectedColor: selectedColor,
label: const Text('ChoiceChip.elevated'),
),
],
),
);
}
// Test enabled chips.
await tester.pumpWidget(buildApp(enabled: true, selected: false));
// Enabled ChoiceChip should have the provided backgroundColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: backgroundColor),
);
// Enabled elevated ChoiceChip should have the provided backgroundColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: backgroundColor),
);
// Test disabled chips.
await tester.pumpWidget(buildApp(enabled: false, selected: false));
await tester.pumpAndSettle();
// Disabled ChoiceChip should have the provided disabledColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: disabledColor),
);
// Disabled elevated ChoiceChip should have the provided disabledColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: disabledColor),
);
// Test enabled & selected chips.
await tester.pumpWidget(buildApp(enabled: true, selected: true));
await tester.pumpAndSettle();
// Enabled & selected ChoiceChip should have the provided selectedColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: selectedColor),
);
// Enabled & selected elevated ChoiceChip should have the provided selectedColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: selectedColor),
);
});
testWidgets('ChoiceChip can be tapped', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
......@@ -416,7 +591,7 @@ void main() {
}
await tester.pumpWidget(buildFrame(Brightness.light));
expect(getMaterialBox(tester), paints..rrect(color: const Color(0x3d000000)));
expect(getMaterialBox(tester, find.byType(RawChip)), 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);
......@@ -425,7 +600,7 @@ void main() {
await tester.pumpWidget(buildFrame(Brightness.dark));
await tester.pumpAndSettle(); // Theme transition animation
expect(getMaterialBox(tester), paints..rrect(color: const Color(0x3dffffff)));
expect(getMaterialBox(tester, find.byType(RawChip)), 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);
......
......@@ -78,6 +78,15 @@ void expectCheckmarkColor(Finder finder, Color color) {
);
}
RenderBox getMaterialBox(WidgetTester tester, Finder type) {
return tester.firstRenderObject<RenderBox>(
find.descendant(
of: type,
matching: find.byType(CustomPaint),
),
);
}
void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) {
final Iterable<Material> materials = tester.widgetList<Material>(find.byType(Material));
// There should be two Material widgets, first Material is from the "_wrapForChip" and
......@@ -370,6 +379,181 @@ void main() {
expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12));
});
testWidgets('FilterChip.color resolves material states', (WidgetTester tester) async {
const Color disabledSelectedColor = Color(0xffffff00);
const Color disabledColor = Color(0xff00ff00);
const Color backgroundColor = Color(0xff0000ff);
const Color selectedColor = Color(0xffff0000);
final MaterialStateProperty<Color?> color = MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled) && states.contains(MaterialState.selected)) {
return disabledSelectedColor;
}
if (states.contains(MaterialState.disabled)) {
return disabledColor;
}
if (states.contains(MaterialState.selected)) {
return selectedColor;
}
return backgroundColor;
});
Widget buildApp({ required bool enabled, required bool selected }) {
return wrapForChip(
useMaterial3: true,
child: Column(
children: <Widget>[
FilterChip(
onSelected: enabled ? (bool value) { } : null,
selected: selected,
color: color,
label: const Text('FilterChip'),
),
FilterChip.elevated(
onSelected: enabled ? (bool value) { } : null,
selected: selected,
color: color,
label: const Text('FilterChip.elevated'),
),
],
),
);
}
// Test enabled state.
await tester.pumpWidget(buildApp(enabled: true, selected: false));
// Enabled FilterChip should have the provided backgroundColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: backgroundColor),
);
// Enabled elevated FilterChip should have the provided backgroundColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: backgroundColor),
);
// Test disabled state.
await tester.pumpWidget(buildApp(enabled: false, selected: false));
await tester.pumpAndSettle();
// Disabled FilterChip should have the provided disabledColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: disabledColor),
);
// Disabled elevated FilterChip should have the provided disabledColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: disabledColor),
);
// Test enabled & selected state.
await tester.pumpWidget(buildApp(enabled: true, selected: true));
await tester.pumpAndSettle();
// Enabled & selected FilterChip should have the provided selectedColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: selectedColor),
);
// Enabled & selected elevated FilterChip should have the provided selectedColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: selectedColor),
);
// Test disabled & selected state.
await tester.pumpWidget(buildApp(enabled: false, selected: true));
await tester.pumpAndSettle();
// Disabled & selected FilterChip should have the provided disabledSelectedColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: disabledSelectedColor),
);
// Disabled & selected elevated FilterChip should have the
// provided disabledSelectedColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: disabledSelectedColor),
);
});
testWidgets('FilterChip uses provided state color properties', (WidgetTester tester) async {
const Color disabledColor = Color(0xff00ff00);
const Color backgroundColor = Color(0xff0000ff);
const Color selectedColor = Color(0xffff0000);
Widget buildApp({ required bool enabled, required bool selected }) {
return wrapForChip(
useMaterial3: true,
child: Column(
children: <Widget>[
FilterChip(
onSelected: enabled ? (bool value) { } : null,
selected: selected,
disabledColor: disabledColor,
backgroundColor: backgroundColor,
selectedColor: selectedColor,
label: const Text('FilterChip'),
),
FilterChip.elevated(
onSelected: enabled ? (bool value) { } : null,
selected: selected,
disabledColor: disabledColor,
backgroundColor: backgroundColor,
selectedColor: selectedColor,
label: const Text('FilterChip.elevated'),
),
],
),
);
}
// Test enabled state.
await tester.pumpWidget(buildApp(enabled: true, selected: false));
// Enabled FilterChip should have the provided backgroundColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: backgroundColor),
);
// Enabled elevated FilterChip should have the provided backgroundColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: backgroundColor),
);
// Test disabled state.
await tester.pumpWidget(buildApp(enabled: false, selected: false));
await tester.pumpAndSettle();
// Disabled FilterChip should have the provided disabledColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: disabledColor),
);
// Disabled elevated FilterChip should have the provided disabledColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: disabledColor),
);
// Test enabled & selected state.
await tester.pumpWidget(buildApp(enabled: true, selected: true));
await tester.pumpAndSettle();
// Enabled & selected FilterChip should have the provided selectedColor.
expect(
getMaterialBox(tester, find.byType(RawChip).first),
paints..rrect(color: selectedColor),
);
// Enabled & selected elevated FilterChip should have the provided selectedColor.
expect(
getMaterialBox(tester, find.byType(RawChip).last),
paints..rrect(color: selectedColor),
);
});
testWidgets('FilterChip can be tapped', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
......
......@@ -13,7 +13,7 @@ Widget wrapForChip({
TextDirection textDirection = TextDirection.ltr,
double textScaleFactor = 1.0,
Brightness brightness = Brightness.light,
bool useMaterial3 = false,
bool? useMaterial3,
}) {
return MaterialApp(
theme: ThemeData(brightness: brightness, useMaterial3: useMaterial3),
......@@ -101,6 +101,101 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) {
}
void main() {
testWidgets('InputChip.color resolves material states', (WidgetTester tester) async {
const Color disabledSelectedColor = Color(0xffffff00);
const Color disabledColor = Color(0xff00ff00);
const Color backgroundColor = Color(0xff0000ff);
const Color selectedColor = Color(0xffff0000);
Widget buildApp({ required bool enabled, required bool selected }) {
return wrapForChip(
useMaterial3: true,
child: InputChip(
onSelected: enabled ? (bool value) { } : null,
selected: selected,
color: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled) && states.contains(MaterialState.selected)) {
return disabledSelectedColor;
}
if (states.contains(MaterialState.disabled)) {
return disabledColor;
}
if (states.contains(MaterialState.selected)) {
return selectedColor;
}
return backgroundColor;
}),
label: const Text('InputChip'),
),
);
}
// Test enabled chip.
await tester.pumpWidget(buildApp(enabled: true, selected: false));
// Enabled chip should have the provided backgroundColor.
expect(getMaterialBox(tester), paints..rrect(color: backgroundColor));
// Test disabled chip.
await tester.pumpWidget(buildApp(enabled: false, selected: false));
await tester.pumpAndSettle();
// Disabled chip should have the provided disabledColor.
expect(getMaterialBox(tester), paints..rrect(color: disabledColor));
// Test enabled & selected chip.
await tester.pumpWidget(buildApp(enabled: true, selected: true));
await tester.pumpAndSettle();
// Enabled & selected chip should have the provided selectedColor.
expect(getMaterialBox(tester), paints..rrect(color: selectedColor));
// Test disabled & selected chip.
await tester.pumpWidget(buildApp(enabled: false, selected: true));
await tester.pumpAndSettle();
// Disabled & selected chip should have the provided disabledSelectedColor.
expect(getMaterialBox(tester), paints..rrect(color: disabledSelectedColor));
});
testWidgets('InputChip uses provided state color properties', (WidgetTester tester) async {
const Color disabledColor = Color(0xff00ff00);
const Color backgroundColor = Color(0xff0000ff);
const Color selectedColor = Color(0xffff0000);
Widget buildApp({ required bool enabled, required bool selected }) {
return wrapForChip(
useMaterial3: true,
child: InputChip(
onSelected: enabled ? (bool value) { } : null,
selected: selected,
disabledColor: disabledColor,
backgroundColor: backgroundColor,
selectedColor: selectedColor,
label: const Text('InputChip'),
),
);
}
// Test enabled chip.
await tester.pumpWidget(buildApp(enabled: true, selected: false));
// Enabled chip should have the provided backgroundColor.
expect(getMaterialBox(tester), paints..rrect(color: backgroundColor));
// Test disabled chip.
await tester.pumpWidget(buildApp(enabled: false, selected: false));
await tester.pumpAndSettle();
// Disabled chip should have the provided disabledColor.
expect(getMaterialBox(tester), paints..rrect(color: disabledColor));
// Test enabled & selected chip.
await tester.pumpWidget(buildApp(enabled: true, selected: true));
await tester.pumpAndSettle();
// Enabled & selected chip should have the provided selectedColor.
expect(getMaterialBox(tester), paints..rrect(color: selectedColor));
});
testWidgets('InputChip can be tapped', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
......
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