Refactor chip class and move independent chips into separate classes (#101507)

This commit is contained in:
Taha Tesser 2022-04-12 17:24:11 +03:00 committed by GitHub
parent 0675471207
commit 0f2b1a3baf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1467 additions and 1203 deletions

View File

@ -45,6 +45,10 @@ export 'src/material/checkbox.dart';
export 'src/material/checkbox_list_tile.dart';
export 'src/material/checkbox_theme.dart';
export 'src/material/chip.dart';
export 'src/material/chip_action.dart';
export 'src/material/chip_choice.dart';
export 'src/material/chip_filter.dart';
export 'src/material/chip_input.dart';
export 'src/material/chip_theme.dart';
export 'src/material/circle_avatar.dart';
export 'src/material/color_scheme.dart';

View File

@ -659,777 +659,6 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri
}
}
/// A material design input chip.
///
/// Input chips represent a complex piece of information, such as an entity
/// (person, place, or thing) or conversational text, in a compact form.
///
/// Input chips can be made selectable by setting [onSelected], deletable by
/// setting [onDeleted], and pressable like a button with [onPressed]. They have
/// a [label], and they can have a leading icon (see [avatar]) and a trailing
/// icon ([deleteIcon]). Colors and padding can be customized.
///
/// Requires one of its ancestors to be a [Material] widget.
///
/// Input chips work together with other UI elements. They can appear:
///
/// * In a [Wrap] widget.
/// * In a horizontally scrollable list, like a [ListView] whose
/// scrollDirection is [Axis.horizontal].
///
/// {@tool snippet}
///
/// ```dart
/// InputChip(
/// avatar: CircleAvatar(
/// backgroundColor: Colors.grey.shade800,
/// child: const Text('AB'),
/// ),
/// label: const Text('Aaron Burr'),
/// onPressed: () {
/// print('I am the one thing in life.');
/// }
/// )
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [Chip], a chip that displays information and can be deleted.
/// * [ChoiceChip], allows a single selection from a set of options. Choice
/// chips contain related descriptive text or categories.
/// * [FilterChip], uses tags or descriptive words as a way to filter content.
/// * [ActionChip], represents an action related to primary content.
/// * [CircleAvatar], which shows images or initials of people.
/// * [Wrap], A widget that displays its children in multiple horizontal or
/// vertical runs.
/// * <https://material.io/design/components/chips.html>
class InputChip extends StatelessWidget
implements
ChipAttributes,
DeletableChipAttributes,
SelectableChipAttributes,
CheckmarkableChipAttributes,
DisabledChipAttributes,
TappableChipAttributes {
/// Creates an [InputChip].
///
/// The [onPressed] and [onSelected] callbacks must not both be specified at
/// the same time.
///
/// The [label], [isEnabled], [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 InputChip({
Key? key,
this.avatar,
required this.label,
this.labelStyle,
this.labelPadding,
this.selected = false,
this.isEnabled = true,
this.onSelected,
this.deleteIcon,
this.onDeleted,
this.deleteIconColor,
this.deleteButtonTooltipMessage,
this.onPressed,
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.selectedShadowColor,
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
@Deprecated(
'Migrate to deleteButtonTooltipMessage. '
'This feature was deprecated after v2.10.0-0.3.pre.'
)
this.useDeleteButtonTooltip = true,
}) : assert(selected != null),
assert(isEnabled != null),
assert(label != null),
assert(clipBehavior != null),
assert(autofocus != null),
assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
super(key: key);
@override
final Widget? avatar;
@override
final Widget label;
@override
final TextStyle? labelStyle;
@override
final EdgeInsetsGeometry? labelPadding;
@override
final bool selected;
@override
final bool isEnabled;
@override
final ValueChanged<bool>? onSelected;
@override
final Widget? deleteIcon;
@override
final VoidCallback? onDeleted;
@override
final Color? deleteIconColor;
@override
final String? deleteButtonTooltipMessage;
@override
final VoidCallback? onPressed;
@override
final double? pressElevation;
@override
final Color? disabledColor;
@override
final Color? selectedColor;
@override
final String? tooltip;
@override
final BorderSide? side;
@override
final OutlinedBorder? shape;
@override
final Clip clipBehavior;
@override
final FocusNode? focusNode;
@override
final bool autofocus;
@override
final Color? backgroundColor;
@override
final EdgeInsetsGeometry? padding;
@override
final VisualDensity? visualDensity;
@override
final MaterialTapTargetSize? materialTapTargetSize;
@override
final double? elevation;
@override
final Color? shadowColor;
@override
final Color? selectedShadowColor;
@override
final bool? showCheckmark;
@override
final Color? checkmarkColor;
@override
final ShapeBorder avatarBorder;
@override
@Deprecated(
'Migrate to deleteButtonTooltipMessage. '
'This feature was deprecated after v2.10.0-0.3.pre.'
)
final bool useDeleteButtonTooltip;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
return RawChip(
avatar: avatar,
label: label,
labelStyle: labelStyle,
labelPadding: labelPadding,
deleteIcon: deleteIcon,
onDeleted: onDeleted,
deleteIconColor: deleteIconColor,
useDeleteButtonTooltip: useDeleteButtonTooltip,
deleteButtonTooltipMessage: deleteButtonTooltipMessage,
onSelected: onSelected,
onPressed: onPressed,
pressElevation: pressElevation,
selected: selected,
disabledColor: disabledColor,
selectedColor: selectedColor,
tooltip: tooltip,
side: side,
shape: shape,
clipBehavior: clipBehavior,
focusNode: focusNode,
autofocus: autofocus,
backgroundColor: backgroundColor,
padding: padding,
visualDensity: visualDensity,
materialTapTargetSize: materialTapTargetSize,
elevation: elevation,
shadowColor: shadowColor,
selectedShadowColor: selectedShadowColor,
showCheckmark: showCheckmark,
checkmarkColor: checkmarkColor,
isEnabled: isEnabled && (onSelected != null || onDeleted != null || onPressed != null),
avatarBorder: avatarBorder,
);
}
}
/// A material design choice chip.
///
/// [ChoiceChip]s represent a single choice from a set. Choice chips contain
/// related descriptive text or categories.
///
/// Requires one of its ancestors to be a [Material] widget. The [selected] and
/// [label] arguments must not be null.
///
/// {@tool snippet}
///
/// ```dart
/// class MyThreeOptions extends StatefulWidget {
/// const MyThreeOptions({Key? key}) : super(key: key);
///
/// @override
/// State<MyThreeOptions> createState() => _MyThreeOptionsState();
/// }
///
/// class _MyThreeOptionsState extends State<MyThreeOptions> {
/// int? _value = 1;
///
/// @override
/// Widget build(BuildContext context) {
/// return Wrap(
/// children: List<Widget>.generate(
/// 3,
/// (int index) {
/// return ChoiceChip(
/// label: Text('Item $index'),
/// selected: _value == index,
/// onSelected: (bool selected) {
/// setState(() {
/// _value = selected ? index : null;
/// });
/// },
/// );
/// },
/// ).toList(),
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [Chip], a chip that displays information and can be deleted.
/// * [InputChip], a chip that represents a complex piece of information, such
/// as an entity (person, place, or thing) or conversational text, in a
/// compact form.
/// * [FilterChip], uses tags or descriptive words as a way to filter content.
/// * [ActionChip], represents an action related to primary content.
/// * [CircleAvatar], which shows images or initials of people.
/// * [Wrap], A widget that displays its children in multiple horizontal or
/// vertical runs.
/// * <https://material.io/design/components/chips.html>
class ChoiceChip extends StatelessWidget
implements
ChipAttributes,
SelectableChipAttributes,
DisabledChipAttributes {
/// Create a 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({
Key? 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.selectedShadowColor,
this.avatarBorder = const CircleBorder(),
}) : assert(selected != null),
assert(label != null),
assert(clipBehavior != null),
assert(autofocus != null),
assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
super(key: key);
@override
final Widget? avatar;
@override
final Widget label;
@override
final TextStyle? labelStyle;
@override
final EdgeInsetsGeometry? labelPadding;
@override
final ValueChanged<bool>? onSelected;
@override
final double? pressElevation;
@override
final bool selected;
@override
final Color? disabledColor;
@override
final Color? selectedColor;
@override
final String? tooltip;
@override
final BorderSide? side;
@override
final OutlinedBorder? shape;
@override
final Clip clipBehavior;
@override
final FocusNode? focusNode;
@override
final bool autofocus;
@override
final Color? backgroundColor;
@override
final EdgeInsetsGeometry? padding;
@override
final VisualDensity? visualDensity;
@override
final MaterialTapTargetSize? materialTapTargetSize;
@override
final double? elevation;
@override
final Color? shadowColor;
@override
final Color? selectedShadowColor;
@override
final ShapeBorder avatarBorder;
@override
bool get isEnabled => onSelected != null;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
final ChipThemeData chipTheme = ChipTheme.of(context);
return RawChip(
avatar: avatar,
label: label,
labelStyle: labelStyle ?? (selected ? chipTheme.secondaryLabelStyle : null),
labelPadding: labelPadding,
onSelected: onSelected,
pressElevation: pressElevation,
selected: selected,
showCheckmark: false,
tooltip: tooltip,
side: side,
shape: shape,
clipBehavior: clipBehavior,
focusNode: focusNode,
autofocus: autofocus,
disabledColor: disabledColor,
selectedColor: selectedColor ?? chipTheme.secondarySelectedColor,
backgroundColor: backgroundColor,
padding: padding,
visualDensity: visualDensity,
isEnabled: isEnabled,
materialTapTargetSize: materialTapTargetSize,
elevation: elevation,
shadowColor: shadowColor,
selectedShadowColor: selectedShadowColor,
avatarBorder: avatarBorder,
);
}
}
/// A material design filter chip.
///
/// Filter chips use tags or descriptive words as a way to filter content.
///
/// Filter chips are a good alternative to [Checkbox] or [Switch] widgets.
/// Unlike these alternatives, filter chips allow for clearly delineated and
/// exposed options in a compact area.
///
/// Requires one of its ancestors to be a [Material] widget.
///
/// {@tool snippet}
///
/// ```dart
/// class ActorFilterEntry {
/// const ActorFilterEntry(this.name, this.initials);
/// final String name;
/// final String initials;
/// }
///
/// class CastFilter extends StatefulWidget {
/// const CastFilter({Key? key}) : super(key: key);
///
/// @override
/// State createState() => CastFilterState();
/// }
///
/// class CastFilterState extends State<CastFilter> {
/// final List<ActorFilterEntry> _cast = <ActorFilterEntry>[
/// const ActorFilterEntry('Aaron Burr', 'AB'),
/// const ActorFilterEntry('Alexander Hamilton', 'AH'),
/// const ActorFilterEntry('Eliza Hamilton', 'EH'),
/// const ActorFilterEntry('James Madison', 'JM'),
/// ];
/// final List<String> _filters = <String>[];
///
/// Iterable<Widget> get actorWidgets {
/// return _cast.map((ActorFilterEntry actor) {
/// return Padding(
/// padding: const EdgeInsets.all(4.0),
/// child: FilterChip(
/// avatar: CircleAvatar(child: Text(actor.initials)),
/// label: Text(actor.name),
/// selected: _filters.contains(actor.name),
/// onSelected: (bool value) {
/// setState(() {
/// if (value) {
/// _filters.add(actor.name);
/// } else {
/// _filters.removeWhere((String name) {
/// return name == actor.name;
/// });
/// }
/// });
/// },
/// ),
/// );
/// });
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Column(
/// mainAxisAlignment: MainAxisAlignment.center,
/// children: <Widget>[
/// Wrap(
/// children: actorWidgets.toList(),
/// ),
/// Text('Look for: ${_filters.join(', ')}'),
/// ],
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [Chip], a chip that displays information and can be deleted.
/// * [InputChip], a chip that represents a complex piece of information, such
/// as an entity (person, place, or thing) or conversational text, in a
/// compact form.
/// * [ChoiceChip], allows a single selection from a set of options. Choice
/// chips contain related descriptive text or categories.
/// * [ActionChip], represents an action related to primary content.
/// * [CircleAvatar], which shows images or initials of people.
/// * [Wrap], A widget that displays its children in multiple horizontal or
/// vertical runs.
/// * <https://material.io/design/components/chips.html>
class FilterChip extends StatelessWidget
implements
ChipAttributes,
SelectableChipAttributes,
CheckmarkableChipAttributes,
DisabledChipAttributes {
/// Create a 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({
Key? 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.selectedShadowColor,
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
}) : assert(selected != null),
assert(label != null),
assert(clipBehavior != null),
assert(autofocus != null),
assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
super(key: key);
@override
final Widget? avatar;
@override
final Widget label;
@override
final TextStyle? labelStyle;
@override
final EdgeInsetsGeometry? labelPadding;
@override
final bool selected;
@override
final ValueChanged<bool>? onSelected;
@override
final double? pressElevation;
@override
final Color? disabledColor;
@override
final Color? selectedColor;
@override
final String? tooltip;
@override
final BorderSide? side;
@override
final OutlinedBorder? shape;
@override
final Clip clipBehavior;
@override
final FocusNode? focusNode;
@override
final bool autofocus;
@override
final Color? backgroundColor;
@override
final EdgeInsetsGeometry? padding;
@override
final VisualDensity? visualDensity;
@override
final MaterialTapTargetSize? materialTapTargetSize;
@override
final double? elevation;
@override
final Color? shadowColor;
@override
final Color? selectedShadowColor;
@override
final bool? showCheckmark;
@override
final Color? checkmarkColor;
@override
final ShapeBorder avatarBorder;
@override
bool get isEnabled => onSelected != null;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
return RawChip(
avatar: avatar,
label: label,
labelStyle: labelStyle,
labelPadding: labelPadding,
onSelected: onSelected,
pressElevation: pressElevation,
selected: selected,
tooltip: tooltip,
side: side,
shape: shape,
clipBehavior: clipBehavior,
focusNode: focusNode,
autofocus: autofocus,
backgroundColor: backgroundColor,
disabledColor: disabledColor,
selectedColor: selectedColor,
padding: padding,
visualDensity: visualDensity,
isEnabled: isEnabled,
materialTapTargetSize: materialTapTargetSize,
elevation: elevation,
shadowColor: shadowColor,
selectedShadowColor: selectedShadowColor,
showCheckmark: showCheckmark,
checkmarkColor: checkmarkColor,
avatarBorder: avatarBorder,
);
}
}
/// A material design action chip.
///
/// Action chips are a set of options which trigger an action related to primary
/// content. Action chips should appear dynamically and contextually in a UI.
///
/// Action chips can be tapped to trigger an action or show progress and
/// confirmation. They cannot be disabled; if the action is not applicable, the
/// chip should not be included in the interface. (This contrasts with buttons,
/// where unavailable choices are usually represented as disabled controls.)
///
/// Action chips are displayed after primary content, such as below a card or
/// persistently at the bottom of a screen.
///
/// The material button widgets, [ElevatedButton], [TextButton], and
/// [OutlinedButton], are an alternative to action chips, which should appear
/// statically and consistently in a UI.
///
/// Requires one of its ancestors to be a [Material] widget.
///
/// {@tool snippet}
///
/// ```dart
/// ActionChip(
/// avatar: CircleAvatar(
/// backgroundColor: Colors.grey.shade800,
/// child: const Text('AB'),
/// ),
/// label: const Text('Aaron Burr'),
/// onPressed: () {
/// print('If you stand for nothing, Burr, whatll you fall for?');
/// }
/// )
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [Chip], a chip that displays information and can be deleted.
/// * [InputChip], a chip that represents a complex piece of information, such
/// as an entity (person, place, or thing) or conversational text, in a
/// compact form.
/// * [ChoiceChip], allows a single selection from a set of options. Choice
/// chips contain related descriptive text or categories.
/// * [CircleAvatar], which shows images or initials of people.
/// * [Wrap], A widget that displays its children in multiple horizontal or
/// vertical runs.
/// * <https://material.io/design/components/chips.html>
class ActionChip extends StatelessWidget implements ChipAttributes, TappableChipAttributes {
/// Create a 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({
Key? key,
this.avatar,
required this.label,
this.labelStyle,
this.labelPadding,
required this.onPressed,
this.pressElevation,
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,
}) : assert(label != null),
assert(clipBehavior != null),
assert(autofocus != null),
assert(
onPressed != null,
'Rather than disabling an ActionChip by setting onPressed to null, '
'remove it from the interface entirely.',
),
assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
super(key: key);
@override
final Widget? avatar;
@override
final Widget label;
@override
final TextStyle? labelStyle;
@override
final EdgeInsetsGeometry? labelPadding;
@override
final VoidCallback onPressed;
@override
final double? pressElevation;
@override
final String? tooltip;
@override
final BorderSide? side;
@override
final OutlinedBorder? shape;
@override
final Clip clipBehavior;
@override
final FocusNode? focusNode;
@override
final bool autofocus;
@override
final Color? backgroundColor;
@override
final EdgeInsetsGeometry? padding;
@override
final VisualDensity? visualDensity;
@override
final MaterialTapTargetSize? materialTapTargetSize;
@override
final double? elevation;
@override
final Color? shadowColor;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
return RawChip(
avatar: avatar,
label: label,
onPressed: onPressed,
pressElevation: pressElevation,
tooltip: tooltip,
labelStyle: labelStyle,
backgroundColor: backgroundColor,
side: side,
shape: shape,
clipBehavior: clipBehavior,
focusNode: focusNode,
autofocus: autofocus,
padding: padding,
visualDensity: visualDensity,
labelPadding: labelPadding,
materialTapTargetSize: materialTapTargetSize,
elevation: elevation,
shadowColor: shadowColor,
);
}
}
/// A raw material design chip.
///
/// This serves as the basis for all of the chip widget types to aggregate.

View File

@ -0,0 +1,157 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
import 'chip.dart';
import 'debug.dart';
import 'theme_data.dart';
/// A material design action chip.
///
/// Action chips are a set of options which trigger an action related to primary
/// content. Action chips should appear dynamically and contextually in a UI.
///
/// Action chips can be tapped to trigger an action or show progress and
/// confirmation. They cannot be disabled; if the action is not applicable, the
/// chip should not be included in the interface. (This contrasts with buttons,
/// where unavailable choices are usually represented as disabled controls.)
///
/// Action chips are displayed after primary content, such as below a card or
/// persistently at the bottom of a screen.
///
/// The material button widgets, [ElevatedButton], [TextButton], and
/// [OutlinedButton], are an alternative to action chips, which should appear
/// statically and consistently in a UI.
///
/// Requires one of its ancestors to be a [Material] widget.
///
/// {@tool snippet}
///
/// ```dart
/// ActionChip(
/// avatar: CircleAvatar(
/// backgroundColor: Colors.grey.shade800,
/// child: const Text('AB'),
/// ),
/// label: const Text('Aaron Burr'),
/// onPressed: () {
/// print('If you stand for nothing, Burr, whatll you fall for?');
/// }
/// )
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [Chip], a chip that displays information and can be deleted.
/// * [InputChip], a chip that represents a complex piece of information, such
/// as an entity (person, place, or thing) or conversational text, in a
/// compact form.
/// * [ChoiceChip], allows a single selection from a set of options. Choice
/// chips contain related descriptive text or categories.
/// * [CircleAvatar], which shows images or initials of people.
/// * [Wrap], A widget that displays its children in multiple horizontal or
/// vertical runs.
/// * <https://material.io/design/components/chips.html>
class ActionChip extends StatelessWidget implements ChipAttributes, TappableChipAttributes {
/// Create a 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({
Key? key,
this.avatar,
required this.label,
this.labelStyle,
this.labelPadding,
required this.onPressed,
this.pressElevation,
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,
}) : assert(label != null),
assert(clipBehavior != null),
assert(autofocus != null),
assert(
onPressed != null,
'Rather than disabling an ActionChip by setting onPressed to null, '
'remove it from the interface entirely.',
),
assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
super(key: key);
@override
final Widget? avatar;
@override
final Widget label;
@override
final TextStyle? labelStyle;
@override
final EdgeInsetsGeometry? labelPadding;
@override
final VoidCallback onPressed;
@override
final double? pressElevation;
@override
final String? tooltip;
@override
final BorderSide? side;
@override
final OutlinedBorder? shape;
@override
final Clip clipBehavior;
@override
final FocusNode? focusNode;
@override
final bool autofocus;
@override
final Color? backgroundColor;
@override
final EdgeInsetsGeometry? padding;
@override
final VisualDensity? visualDensity;
@override
final MaterialTapTargetSize? materialTapTargetSize;
@override
final double? elevation;
@override
final Color? shadowColor;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
return RawChip(
avatar: avatar,
label: label,
onPressed: onPressed,
pressElevation: pressElevation,
tooltip: tooltip,
labelStyle: labelStyle,
backgroundColor: backgroundColor,
side: side,
shape: shape,
clipBehavior: clipBehavior,
focusNode: focusNode,
autofocus: autofocus,
padding: padding,
visualDensity: visualDensity,
labelPadding: labelPadding,
materialTapTargetSize: materialTapTargetSize,
elevation: elevation,
shadowColor: shadowColor,
);
}
}

View File

@ -0,0 +1,193 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
import 'chip.dart';
import 'chip_theme.dart';
import 'debug.dart';
import 'theme_data.dart';
/// A material design choice chip.
///
/// [ChoiceChip]s represent a single choice from a set. Choice chips contain
/// related descriptive text or categories.
///
/// Requires one of its ancestors to be a [Material] widget. The [selected] and
/// [label] arguments must not be null.
///
/// {@tool snippet}
///
/// ```dart
/// class MyThreeOptions extends StatefulWidget {
/// const MyThreeOptions({Key? key}) : super(key: key);
///
/// @override
/// State<MyThreeOptions> createState() => _MyThreeOptionsState();
/// }
///
/// class _MyThreeOptionsState extends State<MyThreeOptions> {
/// int? _value = 1;
///
/// @override
/// Widget build(BuildContext context) {
/// return Wrap(
/// children: List<Widget>.generate(
/// 3,
/// (int index) {
/// return ChoiceChip(
/// label: Text('Item $index'),
/// selected: _value == index,
/// onSelected: (bool selected) {
/// setState(() {
/// _value = selected ? index : null;
/// });
/// },
/// );
/// },
/// ).toList(),
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [Chip], a chip that displays information and can be deleted.
/// * [InputChip], a chip that represents a complex piece of information, such
/// as an entity (person, place, or thing) or conversational text, in a
/// compact form.
/// * [FilterChip], uses tags or descriptive words as a way to filter content.
/// * [ActionChip], represents an action related to primary content.
/// * [CircleAvatar], which shows images or initials of people.
/// * [Wrap], A widget that displays its children in multiple horizontal or
/// vertical runs.
/// * <https://material.io/design/components/chips.html>
class ChoiceChip extends StatelessWidget
implements
ChipAttributes,
SelectableChipAttributes,
DisabledChipAttributes {
/// Create a 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({
Key? 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.selectedShadowColor,
this.avatarBorder = const CircleBorder(),
}) : assert(selected != null),
assert(label != null),
assert(clipBehavior != null),
assert(autofocus != null),
assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
super(key: key);
@override
final Widget? avatar;
@override
final Widget label;
@override
final TextStyle? labelStyle;
@override
final EdgeInsetsGeometry? labelPadding;
@override
final ValueChanged<bool>? onSelected;
@override
final double? pressElevation;
@override
final bool selected;
@override
final Color? disabledColor;
@override
final Color? selectedColor;
@override
final String? tooltip;
@override
final BorderSide? side;
@override
final OutlinedBorder? shape;
@override
final Clip clipBehavior;
@override
final FocusNode? focusNode;
@override
final bool autofocus;
@override
final Color? backgroundColor;
@override
final EdgeInsetsGeometry? padding;
@override
final VisualDensity? visualDensity;
@override
final MaterialTapTargetSize? materialTapTargetSize;
@override
final double? elevation;
@override
final Color? shadowColor;
@override
final Color? selectedShadowColor;
@override
final ShapeBorder avatarBorder;
@override
bool get isEnabled => onSelected != null;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
final ChipThemeData chipTheme = ChipTheme.of(context);
return RawChip(
avatar: avatar,
label: label,
labelStyle: labelStyle ?? (selected ? chipTheme.secondaryLabelStyle : null),
labelPadding: labelPadding,
onSelected: onSelected,
pressElevation: pressElevation,
selected: selected,
showCheckmark: false,
tooltip: tooltip,
side: side,
shape: shape,
clipBehavior: clipBehavior,
focusNode: focusNode,
autofocus: autofocus,
disabledColor: disabledColor,
selectedColor: selectedColor ?? chipTheme.secondarySelectedColor,
backgroundColor: backgroundColor,
padding: padding,
visualDensity: visualDensity,
isEnabled: isEnabled,
materialTapTargetSize: materialTapTargetSize,
elevation: elevation,
shadowColor: shadowColor,
selectedShadowColor: selectedShadowColor,
avatarBorder: avatarBorder,
);
}
}

View File

@ -0,0 +1,231 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
import 'chip.dart';
import 'debug.dart';
import 'theme_data.dart';
/// A material design filter chip.
///
/// Filter chips use tags or descriptive words as a way to filter content.
///
/// Filter chips are a good alternative to [Checkbox] or [Switch] widgets.
/// Unlike these alternatives, filter chips allow for clearly delineated and
/// exposed options in a compact area.
///
/// Requires one of its ancestors to be a [Material] widget.
///
/// {@tool snippet}
///
/// ```dart
/// class ActorFilterEntry {
/// const ActorFilterEntry(this.name, this.initials);
/// final String name;
/// final String initials;
/// }
///
/// class CastFilter extends StatefulWidget {
/// const CastFilter({Key? key}) : super(key: key);
///
/// @override
/// State createState() => CastFilterState();
/// }
///
/// class CastFilterState extends State<CastFilter> {
/// final List<ActorFilterEntry> _cast = <ActorFilterEntry>[
/// const ActorFilterEntry('Aaron Burr', 'AB'),
/// const ActorFilterEntry('Alexander Hamilton', 'AH'),
/// const ActorFilterEntry('Eliza Hamilton', 'EH'),
/// const ActorFilterEntry('James Madison', 'JM'),
/// ];
/// final List<String> _filters = <String>[];
///
/// Iterable<Widget> get actorWidgets {
/// return _cast.map((ActorFilterEntry actor) {
/// return Padding(
/// padding: const EdgeInsets.all(4.0),
/// child: FilterChip(
/// avatar: CircleAvatar(child: Text(actor.initials)),
/// label: Text(actor.name),
/// selected: _filters.contains(actor.name),
/// onSelected: (bool value) {
/// setState(() {
/// if (value) {
/// _filters.add(actor.name);
/// } else {
/// _filters.removeWhere((String name) {
/// return name == actor.name;
/// });
/// }
/// });
/// },
/// ),
/// );
/// });
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Column(
/// mainAxisAlignment: MainAxisAlignment.center,
/// children: <Widget>[
/// Wrap(
/// children: actorWidgets.toList(),
/// ),
/// Text('Look for: ${_filters.join(', ')}'),
/// ],
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [Chip], a chip that displays information and can be deleted.
/// * [InputChip], a chip that represents a complex piece of information, such
/// as an entity (person, place, or thing) or conversational text, in a
/// compact form.
/// * [ChoiceChip], allows a single selection from a set of options. Choice
/// chips contain related descriptive text or categories.
/// * [ActionChip], represents an action related to primary content.
/// * [CircleAvatar], which shows images or initials of people.
/// * [Wrap], A widget that displays its children in multiple horizontal or
/// vertical runs.
/// * <https://material.io/design/components/chips.html>
class FilterChip extends StatelessWidget
implements
ChipAttributes,
SelectableChipAttributes,
CheckmarkableChipAttributes,
DisabledChipAttributes {
/// Create a 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({
Key? 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.selectedShadowColor,
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
}) : assert(selected != null),
assert(label != null),
assert(clipBehavior != null),
assert(autofocus != null),
assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
super(key: key);
@override
final Widget? avatar;
@override
final Widget label;
@override
final TextStyle? labelStyle;
@override
final EdgeInsetsGeometry? labelPadding;
@override
final bool selected;
@override
final ValueChanged<bool>? onSelected;
@override
final double? pressElevation;
@override
final Color? disabledColor;
@override
final Color? selectedColor;
@override
final String? tooltip;
@override
final BorderSide? side;
@override
final OutlinedBorder? shape;
@override
final Clip clipBehavior;
@override
final FocusNode? focusNode;
@override
final bool autofocus;
@override
final Color? backgroundColor;
@override
final EdgeInsetsGeometry? padding;
@override
final VisualDensity? visualDensity;
@override
final MaterialTapTargetSize? materialTapTargetSize;
@override
final double? elevation;
@override
final Color? shadowColor;
@override
final Color? selectedShadowColor;
@override
final bool? showCheckmark;
@override
final Color? checkmarkColor;
@override
final ShapeBorder avatarBorder;
@override
bool get isEnabled => onSelected != null;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
return RawChip(
avatar: avatar,
label: label,
labelStyle: labelStyle,
labelPadding: labelPadding,
onSelected: onSelected,
pressElevation: pressElevation,
selected: selected,
tooltip: tooltip,
side: side,
shape: shape,
clipBehavior: clipBehavior,
focusNode: focusNode,
autofocus: autofocus,
backgroundColor: backgroundColor,
disabledColor: disabledColor,
selectedColor: selectedColor,
padding: padding,
visualDensity: visualDensity,
isEnabled: isEnabled,
materialTapTargetSize: materialTapTargetSize,
elevation: elevation,
shadowColor: shadowColor,
selectedShadowColor: selectedShadowColor,
showCheckmark: showCheckmark,
checkmarkColor: checkmarkColor,
avatarBorder: avatarBorder,
);
}
}

View File

@ -0,0 +1,227 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
import 'chip.dart';
import 'debug.dart';
import 'theme_data.dart';
/// A material design input chip.
///
/// Input chips represent a complex piece of information, such as an entity
/// (person, place, or thing) or conversational text, in a compact form.
///
/// Input chips can be made selectable by setting [onSelected], deletable by
/// setting [onDeleted], and pressable like a button with [onPressed]. They have
/// a [label], and they can have a leading icon (see [avatar]) and a trailing
/// icon ([deleteIcon]). Colors and padding can be customized.
///
/// Requires one of its ancestors to be a [Material] widget.
///
/// Input chips work together with other UI elements. They can appear:
///
/// * In a [Wrap] widget.
/// * In a horizontally scrollable list, like a [ListView] whose
/// scrollDirection is [Axis.horizontal].
///
/// {@tool snippet}
///
/// ```dart
/// InputChip(
/// avatar: CircleAvatar(
/// backgroundColor: Colors.grey.shade800,
/// child: const Text('AB'),
/// ),
/// label: const Text('Aaron Burr'),
/// onPressed: () {
/// print('I am the one thing in life.');
/// }
/// )
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [Chip], a chip that displays information and can be deleted.
/// * [ChoiceChip], allows a single selection from a set of options. Choice
/// chips contain related descriptive text or categories.
/// * [FilterChip], uses tags or descriptive words as a way to filter content.
/// * [ActionChip], represents an action related to primary content.
/// * [CircleAvatar], which shows images or initials of people.
/// * [Wrap], A widget that displays its children in multiple horizontal or
/// vertical runs.
/// * <https://material.io/design/components/chips.html>
class InputChip extends StatelessWidget
implements
ChipAttributes,
DeletableChipAttributes,
SelectableChipAttributes,
CheckmarkableChipAttributes,
DisabledChipAttributes,
TappableChipAttributes {
/// Creates an [InputChip].
///
/// The [onPressed] and [onSelected] callbacks must not both be specified at
/// the same time.
///
/// The [label], [isEnabled], [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 InputChip({
Key? key,
this.avatar,
required this.label,
this.labelStyle,
this.labelPadding,
this.selected = false,
this.isEnabled = true,
this.onSelected,
this.deleteIcon,
this.onDeleted,
this.deleteIconColor,
this.deleteButtonTooltipMessage,
this.onPressed,
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.selectedShadowColor,
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
@Deprecated(
'Migrate to deleteButtonTooltipMessage. '
'This feature was deprecated after v2.10.0-0.3.pre.'
)
this.useDeleteButtonTooltip = true,
}) : assert(selected != null),
assert(isEnabled != null),
assert(label != null),
assert(clipBehavior != null),
assert(autofocus != null),
assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
super(key: key);
@override
final Widget? avatar;
@override
final Widget label;
@override
final TextStyle? labelStyle;
@override
final EdgeInsetsGeometry? labelPadding;
@override
final bool selected;
@override
final bool isEnabled;
@override
final ValueChanged<bool>? onSelected;
@override
final Widget? deleteIcon;
@override
final VoidCallback? onDeleted;
@override
final Color? deleteIconColor;
@override
final String? deleteButtonTooltipMessage;
@override
final VoidCallback? onPressed;
@override
final double? pressElevation;
@override
final Color? disabledColor;
@override
final Color? selectedColor;
@override
final String? tooltip;
@override
final BorderSide? side;
@override
final OutlinedBorder? shape;
@override
final Clip clipBehavior;
@override
final FocusNode? focusNode;
@override
final bool autofocus;
@override
final Color? backgroundColor;
@override
final EdgeInsetsGeometry? padding;
@override
final VisualDensity? visualDensity;
@override
final MaterialTapTargetSize? materialTapTargetSize;
@override
final double? elevation;
@override
final Color? shadowColor;
@override
final Color? selectedShadowColor;
@override
final bool? showCheckmark;
@override
final Color? checkmarkColor;
@override
final ShapeBorder avatarBorder;
@override
@Deprecated(
'Migrate to deleteButtonTooltipMessage. '
'This feature was deprecated after v2.10.0-0.3.pre.'
)
final bool useDeleteButtonTooltip;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
return RawChip(
avatar: avatar,
label: label,
labelStyle: labelStyle,
labelPadding: labelPadding,
deleteIcon: deleteIcon,
onDeleted: onDeleted,
deleteIconColor: deleteIconColor,
useDeleteButtonTooltip: useDeleteButtonTooltip,
deleteButtonTooltipMessage: deleteButtonTooltipMessage,
onSelected: onSelected,
onPressed: onPressed,
pressElevation: pressElevation,
selected: selected,
disabledColor: disabledColor,
selectedColor: selectedColor,
tooltip: tooltip,
side: side,
shape: shape,
clipBehavior: clipBehavior,
focusNode: focusNode,
autofocus: autofocus,
backgroundColor: backgroundColor,
padding: padding,
visualDensity: visualDensity,
materialTapTargetSize: materialTapTargetSize,
elevation: elevation,
shadowColor: shadowColor,
selectedShadowColor: selectedShadowColor,
showCheckmark: showCheckmark,
checkmarkColor: checkmarkColor,
isEnabled: isEnabled && (onSelected != null || onDeleted != null || onPressed != null),
avatarBorder: avatarBorder,
);
}
}

View File

@ -0,0 +1,61 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
/// Adds the basic requirements for a Chip.
Widget wrapForChip({
required Widget child,
TextDirection textDirection = TextDirection.ltr,
double textScaleFactor = 1.0,
Brightness brightness = Brightness.light,
}) {
return MaterialApp(
theme: ThemeData(brightness: brightness),
home: Directionality(
textDirection: textDirection,
child: MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window).copyWith(textScaleFactor: textScaleFactor),
child: Material(child: child),
),
),
);
}
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
// last Material is from the "RawChip".
expect(materials.length, 2);
// The last Material from `RawChip` should have the clip behavior.
expect(materials.last.clipBehavior, clipBehavior);
}
void main() {
testWidgets('ActionChip can be tapped', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ActionChip(
onPressed: () { },
label: const Text('action chip'),
),
),
),
);
await tester.tap(find.byType(ActionChip));
expect(tester.takeException(), null);
});
testWidgets('ActionChip clipBehavior properly passes through to the Material', (WidgetTester tester) async {
const Text label = Text('label');
await tester.pumpWidget(wrapForChip(child: ActionChip(label: label, onPressed: () { })));
checkChipMaterialClipBehavior(tester, Clip.none);
await tester.pumpWidget(wrapForChip(child: ActionChip(label: label, clipBehavior: Clip.antiAlias, onPressed: () { })));
checkChipMaterialClipBehavior(tester, Clip.antiAlias);
});
}

View File

@ -0,0 +1,123 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
RenderBox getMaterialBox(WidgetTester tester) {
return tester.firstRenderObject<RenderBox>(
find.descendant(
of: find.byType(RawChip),
matching: find.byType(CustomPaint),
),
);
}
Material getMaterial(WidgetTester tester) {
return tester.widget<Material>(
find.descendant(
of: find.byType(RawChip),
matching: find.byType(Material),
),
);
}
DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) {
return tester.widget(
find.ancestor(
of: find.text(labelText),
matching: find.byType(DefaultTextStyle),
).first,
);
}
/// Adds the basic requirements for a Chip.
Widget wrapForChip({
required Widget child,
TextDirection textDirection = TextDirection.ltr,
double textScaleFactor = 1.0,
Brightness brightness = Brightness.light,
}) {
return MaterialApp(
theme: ThemeData(brightness: brightness),
home: Directionality(
textDirection: textDirection,
child: MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window).copyWith(textScaleFactor: textScaleFactor),
child: Material(child: child),
),
),
);
}
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
// last Material is from the "RawChip".
expect(materials.length, 2);
// The last Material from `RawChip` should have the clip behavior.
expect(materials.last.clipBehavior, clipBehavior);
}
void main() {
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..path(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..path(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);
});
testWidgets('ChoiceChip can be tapped', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: ChoiceChip(
selected: false,
label: Text('choice chip'),
),
),
),
);
await tester.tap(find.byType(ChoiceChip));
expect(tester.takeException(), null);
});
testWidgets('ChoiceChip clipBehavior properly passes through to the Material', (WidgetTester tester) async {
const Text label = Text('label');
await tester.pumpWidget(wrapForChip(child: const ChoiceChip(label: label, selected: false)));
checkChipMaterialClipBehavior(tester, Clip.none);
await tester.pumpWidget(wrapForChip(child: const ChoiceChip(label: label, selected: false, clipBehavior: Clip.antiAlias)));
checkChipMaterialClipBehavior(tester, Clip.antiAlias);
});
}

View File

@ -0,0 +1,173 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
/// Adds the basic requirements for a Chip.
Widget wrapForChip({
required Widget child,
TextDirection textDirection = TextDirection.ltr,
double textScaleFactor = 1.0,
Brightness brightness = Brightness.light,
}) {
return MaterialApp(
theme: ThemeData(brightness: brightness),
home: Directionality(
textDirection: textDirection,
child: MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window).copyWith(textScaleFactor: textScaleFactor),
child: Material(child: child),
),
),
);
}
Future<void> pumpCheckmarkChip(
WidgetTester tester, {
required Widget chip,
Color? themeColor,
Brightness brightness = Brightness.light,
}) async {
await tester.pumpWidget(
wrapForChip(
brightness: brightness,
child: Builder(
builder: (BuildContext context) {
final ChipThemeData chipTheme = ChipTheme.of(context);
return ChipTheme(
data: themeColor == null ? chipTheme : chipTheme.copyWith(
checkmarkColor: themeColor,
),
child: chip,
);
},
),
),
);
}
Widget selectedFilterChip({ Color? checkmarkColor }) {
return FilterChip(
label: const Text('InputChip'),
selected: true,
showCheckmark: true,
checkmarkColor: checkmarkColor,
onSelected: (bool _) { },
);
}
void expectCheckmarkColor(Finder finder, Color color) {
expect(
finder,
paints
// The first path that is painted is the selection overlay. We do not care
// how it is painted but it has to be added it to this pattern so that the
// check mark can be checked next.
..path()
// The second path that is painted is the check mark.
..path(color: color),
);
}
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
// last Material is from the "RawChip".
expect(materials.length, 2);
// The last Material from `RawChip` should have the clip behavior.
expect(materials.last.clipBehavior, clipBehavior);
}
void main() {
testWidgets('FilterChip can be tapped', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Material(
child: FilterChip(
onSelected: (bool valueChanged) { },
label: const Text('filter chip'),
),
),
),
);
await tester.tap(find.byType(FilterChip));
expect(tester.takeException(), null);
});
testWidgets('Filter chip check mark color is determined by platform brightness when light', (WidgetTester tester) async {
await pumpCheckmarkChip(
tester,
chip: selectedFilterChip(),
);
expectCheckmarkColor(
find.byType(FilterChip),
Colors.black.withAlpha(0xde),
);
});
testWidgets('Filter chip check mark color is determined by platform brightness when dark', (WidgetTester tester) async {
await pumpCheckmarkChip(
tester,
chip: selectedFilterChip(),
brightness: Brightness.dark,
);
expectCheckmarkColor(
find.byType(FilterChip),
Colors.white.withAlpha(0xde),
);
});
testWidgets('Filter chip check mark color can be set by the chip theme', (WidgetTester tester) async {
await pumpCheckmarkChip(
tester,
chip: selectedFilterChip(),
themeColor: const Color(0xff00ff00),
);
expectCheckmarkColor(
find.byType(FilterChip),
const Color(0xff00ff00),
);
});
testWidgets('Filter chip check mark color can be set by the chip constructor', (WidgetTester tester) async {
await pumpCheckmarkChip(
tester,
chip: selectedFilterChip(checkmarkColor: const Color(0xff00ff00)),
);
expectCheckmarkColor(
find.byType(FilterChip),
const Color(0xff00ff00),
);
});
testWidgets('Filter chip check mark color is set by chip constructor even when a theme color is specified', (WidgetTester tester) async {
await pumpCheckmarkChip(
tester,
chip: selectedFilterChip(checkmarkColor: const Color(0xffff0000)),
themeColor: const Color(0xff00ff00),
);
expectCheckmarkColor(
find.byType(FilterChip),
const Color(0xffff0000),
);
});
testWidgets('FilterChip clipBehavior properly passes through to the Material', (WidgetTester tester) async {
const Text label = Text('label');
await tester.pumpWidget(wrapForChip(child: FilterChip(label: label, onSelected: (bool b) { })));
checkChipMaterialClipBehavior(tester, Clip.none);
await tester.pumpWidget(wrapForChip(child: FilterChip(label: label, onSelected: (bool b) { }, clipBehavior: Clip.antiAlias)));
checkChipMaterialClipBehavior(tester, Clip.antiAlias);
});
}

View File

@ -0,0 +1,237 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
/// Adds the basic requirements for a Chip.
Widget wrapForChip({
required Widget child,
TextDirection textDirection = TextDirection.ltr,
double textScaleFactor = 1.0,
Brightness brightness = Brightness.light,
}) {
return MaterialApp(
theme: ThemeData(brightness: brightness),
home: Directionality(
textDirection: textDirection,
child: MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window).copyWith(textScaleFactor: textScaleFactor),
child: Material(child: child),
),
),
);
}
Widget selectedInputChip({ Color? checkmarkColor }) {
return InputChip(
label: const Text('InputChip'),
selected: true,
showCheckmark: true,
checkmarkColor: checkmarkColor,
);
}
Future<void> pumpCheckmarkChip(
WidgetTester tester, {
required Widget chip,
Color? themeColor,
Brightness brightness = Brightness.light,
}) async {
await tester.pumpWidget(
wrapForChip(
brightness: brightness,
child: Builder(
builder: (BuildContext context) {
final ChipThemeData chipTheme = ChipTheme.of(context);
return ChipTheme(
data: themeColor == null ? chipTheme : chipTheme.copyWith(
checkmarkColor: themeColor,
),
child: chip,
);
},
),
),
);
}
void expectCheckmarkColor(Finder finder, Color color) {
expect(
finder,
paints
// The first path that is painted is the selection overlay. We do not care
// how it is painted but it has to be added it to this pattern so that the
// check mark can be checked next.
..path()
// The second path that is painted is the check mark.
..path(color: color),
);
}
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
// last Material is from the "RawChip".
expect(materials.length, 2);
// The last Material from `RawChip` should have the clip behavior.
expect(materials.last.clipBehavior, clipBehavior);
}
void main() {
testWidgets('InputChip can be tapped', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: InputChip(
label: Text('input chip'),
),
),
),
);
await tester.tap(find.byType(InputChip));
expect(tester.takeException(), null);
});
testWidgets('loses focus when disabled', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'InputChip');
await tester.pumpWidget(
wrapForChip(
child: InputChip(
focusNode: focusNode,
autofocus: true,
shape: const RoundedRectangleBorder(),
avatar: const CircleAvatar(child: Text('A')),
label: const Text('Chip A'),
onPressed: () { },
),
),
);
await tester.pump();
expect(focusNode.hasPrimaryFocus, isTrue);
await tester.pumpWidget(
wrapForChip(
child: InputChip(
focusNode: focusNode,
autofocus: true,
shape: const RoundedRectangleBorder(),
avatar: const CircleAvatar(child: Text('A')),
label: const Text('Chip A'),
),
),
);
await tester.pump();
expect(focusNode.hasPrimaryFocus, isFalse);
});
testWidgets('cannot be traversed to when disabled', (WidgetTester tester) async {
final FocusNode focusNode1 = FocusNode(debugLabel: 'InputChip 1');
final FocusNode focusNode2 = FocusNode(debugLabel: 'InputChip 2');
await tester.pumpWidget(
wrapForChip(
child: Column(
children: <Widget>[
InputChip(
focusNode: focusNode1,
autofocus: true,
label: const Text('Chip A'),
onPressed: () { },
),
InputChip(
focusNode: focusNode2,
autofocus: true,
label: const Text('Chip B'),
),
],
),
),
);
await tester.pump();
expect(focusNode1.hasPrimaryFocus, isTrue);
expect(focusNode2.hasPrimaryFocus, isFalse);
expect(focusNode1.nextFocus(), isTrue);
await tester.pump();
expect(focusNode1.hasPrimaryFocus, isTrue);
expect(focusNode2.hasPrimaryFocus, isFalse);
});
testWidgets('Input chip check mark color is determined by platform brightness when light', (WidgetTester tester) async {
await pumpCheckmarkChip(
tester,
chip: selectedInputChip(),
);
expectCheckmarkColor(
find.byType(InputChip),
Colors.black.withAlpha(0xde),
);
});
testWidgets('Input chip check mark color is determined by platform brightness when dark', (WidgetTester tester) async {
await pumpCheckmarkChip(
tester,
chip: selectedInputChip(),
brightness: Brightness.dark,
);
expectCheckmarkColor(
find.byType(InputChip),
Colors.white.withAlpha(0xde),
);
});
testWidgets('Input chip check mark color can be set by the chip theme', (WidgetTester tester) async {
await pumpCheckmarkChip(
tester,
chip: selectedInputChip(),
themeColor: const Color(0xff00ff00),
);
expectCheckmarkColor(
find.byType(InputChip),
const Color(0xff00ff00),
);
});
testWidgets('Input chip check mark color can be set by the chip constructor', (WidgetTester tester) async {
await pumpCheckmarkChip(
tester,
chip: selectedInputChip(checkmarkColor: const Color(0xff00ff00)),
);
expectCheckmarkColor(
find.byType(InputChip),
const Color(0xff00ff00),
);
});
testWidgets('Input chip check mark color is set by chip constructor even when a theme color is specified', (WidgetTester tester) async {
await pumpCheckmarkChip(
tester,
chip: selectedInputChip(checkmarkColor: const Color(0xffff0000)),
themeColor: const Color(0xff00ff00),
);
expectCheckmarkColor(
find.byType(InputChip),
const Color(0xffff0000),
);
});
testWidgets('InputChip clipBehavior properly passes through to the Material', (WidgetTester tester) async {
const Text label = Text('label');
await tester.pumpWidget(wrapForChip(child: const InputChip(label: label)));
checkChipMaterialClipBehavior(tester, Clip.none);
await tester.pumpWidget(wrapForChip(child: const InputChip(label: label, clipBehavior: Clip.antiAlias)));
checkChipMaterialClipBehavior(tester, Clip.antiAlias);
});
}

View File

@ -71,7 +71,7 @@ double getDeleteDrawerProgress(WidgetTester tester) => getRenderChip(tester)?.de
double getEnableProgress(WidgetTester tester) => getRenderChip(tester)?.enableAnimation?.value as double;
/// Adds the basic requirements for a Chip.
Widget _wrapForChip({
Widget wrapForChip({
required Widget child,
TextDirection textDirection = TextDirection.ltr,
double textScaleFactor = 1.0,
@ -93,7 +93,7 @@ Widget _wrapForChip({
/// further constraining the size of its child, the label widget.
/// Optionally, adding an avatar or delete icon to the chip should not
/// cause the chip or label to exceed its constrained height.
Future<void> _testConstrainedLabel(
Future<void> testConstrainedLabel(
WidgetTester tester, {
CircleAvatar? avatar,
VoidCallback? onDeleted,
@ -105,7 +105,7 @@ Future<void> _testConstrainedLabel(
final Key labelKey = UniqueKey();
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Center(
child: SizedBox(
width: chipParentWidth,
@ -133,65 +133,9 @@ Future<void> _testConstrainedLabel(
expect(chipSize.height, chipParentHeight);
}
Widget _selectedInputChip({ Color? checkmarkColor }) {
return InputChip(
label: const Text('InputChip'),
selected: true,
showCheckmark: true,
checkmarkColor: checkmarkColor,
);
}
void doNothing() {}
Widget _selectedFilterChip({ Color? checkmarkColor }) {
return FilterChip(
label: const Text('InputChip'),
selected: true,
showCheckmark: true,
checkmarkColor: checkmarkColor,
onSelected: (bool _) { },
);
}
Future<void> _pumpCheckmarkChip(
WidgetTester tester, {
required Widget chip,
Color? themeColor,
Brightness brightness = Brightness.light,
}) async {
await tester.pumpWidget(
_wrapForChip(
brightness: brightness,
child: Builder(
builder: (BuildContext context) {
final ChipThemeData chipTheme = ChipTheme.of(context);
return ChipTheme(
data: themeColor == null ? chipTheme : chipTheme.copyWith(
checkmarkColor: themeColor,
),
child: chip,
);
},
),
),
);
}
void _expectCheckmarkColor(Finder finder, Color color) {
expect(
finder,
paints
// The first path that is painted is the selection overlay. We do not care
// how it is painted but it has to be added it to this pattern so that the
// check mark can be checked next.
..path()
// The second path that is painted is the check mark.
..path(color: color),
);
}
void _doNothing() {}
Widget _chipWithOptionalDeleteButton({
Widget chipWithOptionalDeleteButton({
Key? deleteButtonKey,
Key? labelKey,
required bool deletable,
@ -199,16 +143,16 @@ Widget _chipWithOptionalDeleteButton({
bool useDeleteButtonTooltip = true,
String? chipTooltip,
String? deleteButtonTooltipMessage,
VoidCallback? onPressed = _doNothing,
VoidCallback? onPressed = doNothing,
}) {
return _wrapForChip(
return wrapForChip(
textDirection: textDirection,
child: Wrap(
children: <Widget>[
RawChip(
tooltip: chipTooltip,
onPressed: onPressed,
onDeleted: deletable ? _doNothing : null,
onDeleted: deletable ? doNothing : null,
deleteIcon: Icon(Icons.close, key: deleteButtonKey),
useDeleteButtonTooltip: useDeleteButtonTooltip,
deleteButtonTooltipMessage: deleteButtonTooltipMessage,
@ -347,44 +291,11 @@ void main() {
expect(labelStyle.wordSpacing, textTheme.bodyText1?.wordSpacing);
});
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..path(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..path(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);
});
testWidgets('Chip control test', (WidgetTester tester) async {
final FeedbackTester feedback = FeedbackTester();
final List<String> deletedChipLabels = <String>[];
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Column(
children: <Widget>[
Chip(
@ -438,7 +349,7 @@ void main() {
final Key labelKey = UniqueKey();
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Center(
child: SizedBox(
width: 500.0,
@ -469,7 +380,7 @@ void main() {
'Chip constrains the size of the label widget when it exceeds the '
'available space',
(WidgetTester tester) async {
await _testConstrainedLabel(tester);
await testConstrainedLabel(tester);
},
);
@ -477,7 +388,7 @@ void main() {
'Chip constrains the size of the label widget when it exceeds the '
'available space and the avatar is present',
(WidgetTester tester) async {
await _testConstrainedLabel(
await testConstrainedLabel(
tester,
avatar: const CircleAvatar(child: Text('A')),
);
@ -488,7 +399,7 @@ void main() {
'Chip constrains the size of the label widget when it exceeds the '
'available space and the delete icon is present',
(WidgetTester tester) async {
await _testConstrainedLabel(
await testConstrainedLabel(
tester,
onDeleted: () { },
);
@ -499,7 +410,7 @@ void main() {
'Chip constrains the size of the label widget when it exceeds the '
'available space and both avatar and delete icons are present',
(WidgetTester tester) async {
await _testConstrainedLabel(
await testConstrainedLabel(
tester,
avatar: const CircleAvatar(child: Text('A')),
onDeleted: () { },
@ -585,7 +496,7 @@ void main() {
testWidgets('Chip in row works ok', (WidgetTester tester) async {
const TextStyle style = TextStyle(fontFamily: 'Ahem', fontSize: 10.0);
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Row(
children: const <Widget>[
Chip(label: Text('Test'), labelStyle: style),
@ -596,7 +507,7 @@ void main() {
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
expect(tester.getSize(find.byType(Chip)), const Size(64.0, 48.0));
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Row(
children: const <Widget>[
Flexible(child: Chip(label: Text('Test'), labelStyle: style)),
@ -607,7 +518,7 @@ void main() {
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
expect(tester.getSize(find.byType(Chip)), const Size(64.0, 48.0));
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Row(
children: const <Widget>[
Expanded(child: Chip(label: Text('Test'), labelStyle: style)),
@ -621,7 +532,7 @@ void main() {
testWidgets('Chip responds to materialTapTargetSize', (WidgetTester tester) async {
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Column(
children: const <Widget>[
Chip(
@ -645,7 +556,7 @@ void main() {
final UniqueKey deleteKey = UniqueKey();
bool calledDelete = false;
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Column(
children: <Widget>[
Chip(
@ -670,7 +581,7 @@ void main() {
calledDelete = false;
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Column(
children: <Widget>[
Chip(
@ -719,7 +630,7 @@ void main() {
);
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: test,
textDirection: TextDirection.rtl,
),
@ -727,7 +638,7 @@ void main() {
await tester.pumpAndSettle(const Duration(milliseconds: 500));
expect(tester.getCenter(find.text('ABC')).dx, greaterThan(tester.getCenter(find.byKey(iconKey)).dx));
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: test,
),
);
@ -737,7 +648,7 @@ void main() {
testWidgets('Chip responds to textScaleFactor', (WidgetTester tester) async {
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Column(
children: const <Widget>[
Chip(
@ -767,7 +678,7 @@ void main() {
expect(tester.getSize(find.byType(Chip).last), anyOf(const Size(132.0, 48.0), const Size(131.0, 48.0)));
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
textScaleFactor: 3.0,
child: Column(
children: const <Widget>[
@ -795,7 +706,7 @@ void main() {
// Check that individual text scales are taken into account.
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Column(
children: const <Widget>[
Chip(
@ -824,7 +735,7 @@ void main() {
final Key keyA = GlobalKey();
final Key keyB = GlobalKey();
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Column(
children: <Widget>[
Chip(
@ -857,7 +768,7 @@ void main() {
testWidgets('Avatars can be non-circle avatar widgets', (WidgetTester tester) async {
final Key keyA = GlobalKey();
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Column(
children: <Widget>[
Chip(
@ -875,7 +786,7 @@ void main() {
testWidgets('Delete icons can be non-icon widgets', (WidgetTester tester) async {
final Key keyA = GlobalKey();
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Column(
children: <Widget>[
Chip(
@ -895,7 +806,7 @@ void main() {
final GlobalKey keyA = GlobalKey();
final GlobalKey keyB = GlobalKey();
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Overlay(
initialEntries: <OverlayEntry>[
OverlayEntry(
@ -931,7 +842,7 @@ void main() {
final GlobalKey keyA = GlobalKey();
final GlobalKey keyB = GlobalKey();
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
textDirection: TextDirection.rtl,
child: Overlay(
initialEntries: <OverlayEntry>[
@ -969,7 +880,7 @@ void main() {
final GlobalKey labelKey = GlobalKey();
Future<void> pushChip({ Widget? avatar }) async {
return tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Wrap(
children: <Widget>[
RawChip(
@ -1083,7 +994,7 @@ void main() {
bool wasDeleted = false;
Future<void> pushChip({ bool deletable = false }) async {
return tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Wrap(
children: <Widget>[
StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
@ -1199,7 +1110,7 @@ void main() {
bool deletePressed = false;
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Wrap(
children: <Widget>[
RawChip(
@ -1234,7 +1145,7 @@ void main() {
final UniqueKey deleteButtonKey = UniqueKey();
await tester.pumpWidget(
_chipWithOptionalDeleteButton(
chipWithOptionalDeleteButton(
labelKey: labelKey,
deleteButtonKey: deleteButtonKey,
deletable: true,
@ -1283,7 +1194,7 @@ void main() {
final GlobalKey deleteButtonKey = GlobalKey();
await tester.pumpWidget(
_chipWithOptionalDeleteButton(
chipWithOptionalDeleteButton(
labelKey: labelKey,
deleteButtonKey: deleteButtonKey,
deletable: true,
@ -1316,7 +1227,7 @@ void main() {
final UniqueKey deleteButtonKey = UniqueKey();
await tester.pumpWidget(
_chipWithOptionalDeleteButton(
chipWithOptionalDeleteButton(
labelKey: labelKey,
deleteButtonKey: deleteButtonKey,
deletable: true,
@ -1369,7 +1280,7 @@ void main() {
final UniqueKey deleteButtonKey = UniqueKey();
await tester.pumpWidget(
_chipWithOptionalDeleteButton(
chipWithOptionalDeleteButton(
labelKey: labelKey,
onPressed: null,
deleteButtonKey: deleteButtonKey,
@ -1424,7 +1335,7 @@ void main() {
final UniqueKey deleteButtonKey = UniqueKey();
await tester.pumpWidget(
_chipWithOptionalDeleteButton(
chipWithOptionalDeleteButton(
labelKey: labelKey,
deleteButtonKey: deleteButtonKey,
deletable: true,
@ -1453,7 +1364,7 @@ void main() {
final UniqueKey labelKey = UniqueKey();
await tester.pumpWidget(
_chipWithOptionalDeleteButton(
chipWithOptionalDeleteButton(
labelKey: labelKey,
deletable: false,
),
@ -1507,7 +1418,7 @@ void main() {
final UniqueKey labelKey = UniqueKey();
Future<void> pushChip({ Widget? avatar, bool selectable = false }) async {
return tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Wrap(
children: <Widget>[
StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
@ -1587,7 +1498,7 @@ void main() {
final UniqueKey labelKey = UniqueKey();
Future<void> pushChip({ bool selectable = false }) async {
return tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Wrap(
children: <Widget>[
StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
@ -1660,7 +1571,7 @@ void main() {
final UniqueKey labelKey = UniqueKey();
Future<void> pushChip({ Widget? avatar, bool selectable = false }) async {
return tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Wrap(
children: <Widget>[
StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
@ -1719,7 +1630,7 @@ void main() {
final ChipThemeData chipTheme = theme.chipTheme;
Widget buildChip(ChipThemeData data) {
return _wrapForChip(
return wrapForChip(
child: Theme(
data: theme,
child: const InputChip(
@ -1748,7 +1659,7 @@ void main() {
);
Widget buildChip() {
return _wrapForChip(
return wrapForChip(
child: Theme(
data: theme,
child: const Chip(
@ -1769,7 +1680,7 @@ void main() {
testWidgets('ChipTheme labelStyle with inherit:true', (WidgetTester tester) async {
Widget buildChip() {
return _wrapForChip(
return wrapForChip(
child: Theme(
data: ThemeData.light().copyWith(
chipTheme: const ChipThemeData(
@ -1789,7 +1700,7 @@ void main() {
testWidgets('Chip does not merge inherit:false label style with the theme label style', (WidgetTester tester) async {
Widget buildChip() {
return _wrapForChip(
return wrapForChip(
child: Theme(
data: ThemeData(fontFamily: 'MyFont'),
child: const DefaultTextStyle(
@ -1814,7 +1725,7 @@ void main() {
testWidgets('Chip size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
final Key key1 = UniqueKey();
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Theme(
data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
child: Center(
@ -1831,7 +1742,7 @@ void main() {
final Key key2 = UniqueKey();
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Theme(
data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
child: Center(
@ -1868,7 +1779,7 @@ void main() {
bool showCheckmark = true,
}) {
chipTheme ??= defaultChipTheme;
return _wrapForChip(
return wrapForChip(
child: Theme(
data: themeData,
child: ChipTheme(
@ -2450,7 +2361,7 @@ void main() {
testWidgets('can be tapped outside of chip delete icon', (WidgetTester tester) async {
bool deleted = false;
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Row(
children: <Widget>[
Chip(
@ -2474,20 +2385,6 @@ void main() {
});
testWidgets('Chips can be tapped', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: ChoiceChip(
selected: false,
label: Text('choice chip'),
),
),
),
);
await tester.tap(find.byType(ChoiceChip));
expect(tester.takeException(), null);
await tester.pumpWidget(
const MaterialApp(
home: Material(
@ -2500,47 +2397,6 @@ void main() {
await tester.tap(find.byType(RawChip));
expect(tester.takeException(), null);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ActionChip(
onPressed: () { },
label: const Text('action chip'),
),
),
),
);
await tester.tap(find.byType(ActionChip));
expect(tester.takeException(), null);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: FilterChip(
onSelected: (bool valueChanged) { },
label: const Text('filter chip'),
),
),
),
);
await tester.tap(find.byType(FilterChip));
expect(tester.takeException(), null);
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: InputChip(
label: Text('input chip'),
),
),
),
);
await tester.tap(find.byType(InputChip));
expect(tester.takeException(), null);
});
testWidgets('Chip elevation and shadow color work correctly', (WidgetTester tester) async {
@ -2554,7 +2410,7 @@ void main() {
InputChip inputChip = const InputChip(label: Text('Label'));
Widget buildChip(ChipThemeData data) {
return _wrapForChip(
return wrapForChip(
child: Theme(
data: theme,
child: inputChip,
@ -2596,7 +2452,7 @@ void main() {
testWidgets('can be tapped outside of chip body', (WidgetTester tester) async {
bool pressed = false;
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: Row(
children: <Widget>[
InputChip(
@ -2620,7 +2476,7 @@ void main() {
testWidgets('is hitTestable', (WidgetTester tester) async {
await tester.pumpWidget(
_wrapForChip(
wrapForChip(
child: InputChip(
shape: const RoundedRectangleBorder(),
avatar: const CircleAvatar(child: Text('A')),
@ -2641,51 +2497,15 @@ void main() {
testWidgets('Chip clipBehavior properly passes through to the Material', (WidgetTester tester) async {
const Text label = Text('label');
await tester.pumpWidget(_wrapForChip(child: const Chip(label: label)));
await tester.pumpWidget(wrapForChip(child: const Chip(label: label)));
checkChipMaterialClipBehavior(tester, Clip.none);
await tester.pumpWidget(_wrapForChip(child: const Chip(label: label, clipBehavior: Clip.antiAlias)));
checkChipMaterialClipBehavior(tester, Clip.antiAlias);
});
testWidgets('ChoiceChip clipBehavior properly passes through to the Material', (WidgetTester tester) async {
const Text label = Text('label');
await tester.pumpWidget(_wrapForChip(child: const ChoiceChip(label: label, selected: false)));
checkChipMaterialClipBehavior(tester, Clip.none);
await tester.pumpWidget(_wrapForChip(child: const ChoiceChip(label: label, selected: false, clipBehavior: Clip.antiAlias)));
checkChipMaterialClipBehavior(tester, Clip.antiAlias);
});
testWidgets('FilterChip clipBehavior properly passes through to the Material', (WidgetTester tester) async {
const Text label = Text('label');
await tester.pumpWidget(_wrapForChip(child: FilterChip(label: label, onSelected: (bool b) { })));
checkChipMaterialClipBehavior(tester, Clip.none);
await tester.pumpWidget(_wrapForChip(child: FilterChip(label: label, onSelected: (bool b) { }, clipBehavior: Clip.antiAlias)));
checkChipMaterialClipBehavior(tester, Clip.antiAlias);
});
testWidgets('ActionChip clipBehavior properly passes through to the Material', (WidgetTester tester) async {
const Text label = Text('label');
await tester.pumpWidget(_wrapForChip(child: ActionChip(label: label, onPressed: () { })));
checkChipMaterialClipBehavior(tester, Clip.none);
await tester.pumpWidget(_wrapForChip(child: ActionChip(label: label, clipBehavior: Clip.antiAlias, onPressed: () { })));
checkChipMaterialClipBehavior(tester, Clip.antiAlias);
});
testWidgets('InputChip clipBehavior properly passes through to the Material', (WidgetTester tester) async {
const Text label = Text('label');
await tester.pumpWidget(_wrapForChip(child: const InputChip(label: label)));
checkChipMaterialClipBehavior(tester, Clip.none);
await tester.pumpWidget(_wrapForChip(child: const InputChip(label: label, clipBehavior: Clip.antiAlias)));
await tester.pumpWidget(wrapForChip(child: const Chip(label: label, clipBehavior: Clip.antiAlias)));
checkChipMaterialClipBehavior(tester, Clip.antiAlias);
});
testWidgets('selected chip and avatar draw darkened layer within avatar circle', (WidgetTester tester) async {
await tester.pumpWidget(_wrapForChip(child: const FilterChip(
await tester.pumpWidget(wrapForChip(child: const FilterChip(
avatar: CircleAvatar(child: Text('t')),
label: Text('test'),
selected: true,
@ -3198,71 +3018,6 @@ void main() {
expect(find.byType(RawChip), paints..drrect(color: selectedBorderSide.color));
});
testWidgets('loses focus when disabled', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'InputChip');
await tester.pumpWidget(
_wrapForChip(
child: InputChip(
focusNode: focusNode,
autofocus: true,
shape: const RoundedRectangleBorder(),
avatar: const CircleAvatar(child: Text('A')),
label: const Text('Chip A'),
onPressed: () { },
),
),
);
await tester.pump();
expect(focusNode.hasPrimaryFocus, isTrue);
await tester.pumpWidget(
_wrapForChip(
child: InputChip(
focusNode: focusNode,
autofocus: true,
shape: const RoundedRectangleBorder(),
avatar: const CircleAvatar(child: Text('A')),
label: const Text('Chip A'),
),
),
);
await tester.pump();
expect(focusNode.hasPrimaryFocus, isFalse);
});
testWidgets('cannot be traversed to when disabled', (WidgetTester tester) async {
final FocusNode focusNode1 = FocusNode(debugLabel: 'InputChip 1');
final FocusNode focusNode2 = FocusNode(debugLabel: 'InputChip 2');
await tester.pumpWidget(
_wrapForChip(
child: Column(
children: <Widget>[
InputChip(
focusNode: focusNode1,
autofocus: true,
label: const Text('Chip A'),
onPressed: () { },
),
InputChip(
focusNode: focusNode2,
autofocus: true,
label: const Text('Chip B'),
),
],
),
),
);
await tester.pump();
expect(focusNode1.hasPrimaryFocus, isTrue);
expect(focusNode2.hasPrimaryFocus, isFalse);
expect(focusNode1.nextFocus(), isTrue);
await tester.pump();
expect(focusNode1.hasPrimaryFocus, isTrue);
expect(focusNode2.hasPrimaryFocus, isFalse);
});
testWidgets('Chip responds to density changes.', (WidgetTester tester) async {
const Key key = Key('test');
const Key textKey = Key('test text');
@ -3368,135 +3123,9 @@ void main() {
expect(box.size, equals(const Size(128, 24.0 + 16.0)));
});
testWidgets('Input chip check mark color is determined by platform brightness when light', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedInputChip(),
);
_expectCheckmarkColor(
find.byType(InputChip),
Colors.black.withAlpha(0xde),
);
});
testWidgets('Filter chip check mark color is determined by platform brightness when light', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedFilterChip(),
);
_expectCheckmarkColor(
find.byType(FilterChip),
Colors.black.withAlpha(0xde),
);
});
testWidgets('Input chip check mark color is determined by platform brightness when dark', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedInputChip(),
brightness: Brightness.dark,
);
_expectCheckmarkColor(
find.byType(InputChip),
Colors.white.withAlpha(0xde),
);
});
testWidgets('Filter chip check mark color is determined by platform brightness when dark', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedFilterChip(),
brightness: Brightness.dark,
);
_expectCheckmarkColor(
find.byType(FilterChip),
Colors.white.withAlpha(0xde),
);
});
testWidgets('Input chip check mark color can be set by the chip theme', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedInputChip(),
themeColor: const Color(0xff00ff00),
);
_expectCheckmarkColor(
find.byType(InputChip),
const Color(0xff00ff00),
);
});
testWidgets('Filter chip check mark color can be set by the chip theme', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedFilterChip(),
themeColor: const Color(0xff00ff00),
);
_expectCheckmarkColor(
find.byType(FilterChip),
const Color(0xff00ff00),
);
});
testWidgets('Input chip check mark color can be set by the chip constructor', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedInputChip(checkmarkColor: const Color(0xff00ff00)),
);
_expectCheckmarkColor(
find.byType(InputChip),
const Color(0xff00ff00),
);
});
testWidgets('Filter chip check mark color can be set by the chip constructor', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedFilterChip(checkmarkColor: const Color(0xff00ff00)),
);
_expectCheckmarkColor(
find.byType(FilterChip),
const Color(0xff00ff00),
);
});
testWidgets('Input chip check mark color is set by chip constructor even when a theme color is specified', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedInputChip(checkmarkColor: const Color(0xffff0000)),
themeColor: const Color(0xff00ff00),
);
_expectCheckmarkColor(
find.byType(InputChip),
const Color(0xffff0000),
);
});
testWidgets('Filter chip check mark color is set by chip constructor even when a theme color is specified', (WidgetTester tester) async {
await _pumpCheckmarkChip(
tester,
chip: _selectedFilterChip(checkmarkColor: const Color(0xffff0000)),
themeColor: const Color(0xff00ff00),
);
_expectCheckmarkColor(
find.byType(FilterChip),
const Color(0xffff0000),
);
});
testWidgets('Chip delete button tooltip can be disabled using useDeleteButtonTooltip', (WidgetTester tester) async {
await tester.pumpWidget(
_chipWithOptionalDeleteButton(
chipWithOptionalDeleteButton(
deletable: true,
useDeleteButtonTooltip: false,
),
@ -3522,7 +3151,7 @@ void main() {
testWidgets('Chip delete button tooltip is disabled if deleteButtonTooltipMessage is empty', (WidgetTester tester) async {
final UniqueKey deleteButtonKey = UniqueKey();
await tester.pumpWidget(
_chipWithOptionalDeleteButton(
chipWithOptionalDeleteButton(
deleteButtonKey: deleteButtonKey,
deletable: true,
deleteButtonTooltipMessage: '',
@ -3547,7 +3176,7 @@ void main() {
testWidgets('Disabling delete button tooltip does not disable chip tooltip', (WidgetTester tester) async {
final UniqueKey deleteButtonKey = UniqueKey();
await tester.pumpWidget(
_chipWithOptionalDeleteButton(
chipWithOptionalDeleteButton(
deleteButtonKey: deleteButtonKey,
deletable: true,
deleteButtonTooltipMessage: '',
@ -3575,7 +3204,7 @@ void main() {
testWidgets('Triggering delete button tooltip does not trigger Chip tooltip', (WidgetTester tester) async {
final UniqueKey deleteButtonKey = UniqueKey();
await tester.pumpWidget(
_chipWithOptionalDeleteButton(
chipWithOptionalDeleteButton(
deleteButtonKey: deleteButtonKey,
deletable: true,
chipTooltip: 'Chip Tooltip',
@ -3601,7 +3230,7 @@ void main() {
testWidgets('intrinsicHeight implementation meets constraints', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/49478.
await tester.pumpWidget(_wrapForChip(
await tester.pumpWidget(wrapForChip(
child: const Chip(
label: Text('text'),
padding: EdgeInsets.symmetric(horizontal: 20),