Unverified Commit cf89a787 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Fix various problems with Chip delete button. (#90531)

parent 6fb73814
...@@ -11,7 +11,6 @@ import 'chip_theme.dart'; ...@@ -11,7 +11,6 @@ import 'chip_theme.dart';
import 'colors.dart'; import 'colors.dart';
import 'constants.dart'; import 'constants.dart';
import 'debug.dart'; import 'debug.dart';
import 'feedback.dart';
import 'icons.dart'; import 'icons.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'material.dart'; import 'material.dart';
...@@ -1589,8 +1588,6 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid ...@@ -1589,8 +1588,6 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
late Animation<double> enableAnimation; late Animation<double> enableAnimation;
late Animation<double> selectionFade; late Animation<double> selectionFade;
final GlobalKey deleteIconKey = GlobalKey();
bool get hasDeleteButton => widget.onDeleted != null; bool get hasDeleteButton => widget.onDeleted != null;
bool get hasAvatar => widget.avatar != null; bool get hasAvatar => widget.avatar != null;
...@@ -1795,7 +1792,6 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid ...@@ -1795,7 +1792,6 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
BuildContext context, BuildContext context,
ThemeData theme, ThemeData theme,
ChipThemeData chipTheme, ChipThemeData chipTheme,
GlobalKey deleteIconKey,
) { ) {
if (!hasDeleteButton) { if (!hasDeleteButton) {
return null; return null;
...@@ -1806,15 +1802,12 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid ...@@ -1806,15 +1802,12 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
child: _wrapWithTooltip( child: _wrapWithTooltip(
widget.deleteButtonTooltipMessage ?? MaterialLocalizations.of(context).deleteButtonTooltip, widget.deleteButtonTooltipMessage ?? MaterialLocalizations.of(context).deleteButtonTooltip,
widget.onDeleted, widget.onDeleted,
GestureDetector( InkWell(
key: deleteIconKey, // Radius should be slightly less than the full size of the chip.
behavior: HitTestBehavior.opaque, radius: (_kChipHeight + (widget.padding?.vertical ?? 0.0)) * .45,
onTap: widget.isEnabled // Keeps the splash from being constrained to the icon alone.
? () { splashFactory: _UnconstrainedInkSplashFactory(Theme.of(context).splashFactory),
Feedback.forTap(context); onTap: widget.isEnabled ? widget.onDeleted : null,
widget.onDeleted!();
}
: null,
child: IconTheme( child: IconTheme(
data: theme.iconTheme.copyWith( data: theme.iconTheme.copyWith(
color: widget.deleteIconColor ?? chipTheme.deleteIconColor, color: widget.deleteIconColor ?? chipTheme.deleteIconColor,
...@@ -1878,11 +1871,6 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid ...@@ -1878,11 +1871,6 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
onTapDown: canTap ? _handleTapDown : null, onTapDown: canTap ? _handleTapDown : null,
onTapCancel: canTap ? _handleTapCancel : null, onTapCancel: canTap ? _handleTapCancel : null,
onHover: canTap ? updateMaterialState(MaterialState.hovered) : null, onHover: canTap ? updateMaterialState(MaterialState.hovered) : null,
splashFactory: _LocationAwareInkRippleFactory(
hasDeleteButton,
context,
deleteIconKey,
),
customBorder: resolvedShape, customBorder: resolvedShape,
child: AnimatedBuilder( child: AnimatedBuilder(
animation: Listenable.merge(<Listenable>[selectController, enableController]), animation: Listenable.merge(<Listenable>[selectController, enableController]),
...@@ -1916,7 +1904,7 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid ...@@ -1916,7 +1904,7 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
deleteIcon: AnimatedSwitcher( deleteIcon: AnimatedSwitcher(
duration: _kDrawerDuration, duration: _kDrawerDuration,
switchInCurve: Curves.fastOutSlowIn, switchInCurve: Curves.fastOutSlowIn,
child: _buildDeleteIcon(context, theme, chipTheme, deleteIconKey), child: _buildDeleteIcon(context, theme, chipTheme),
), ),
brightness: chipTheme.brightness, brightness: chipTheme.brightness,
padding: (widget.padding ?? chipTheme.padding).resolve(textDirection), padding: (widget.padding ?? chipTheme.padding).resolve(textDirection),
...@@ -2531,13 +2519,14 @@ class _RenderChip extends RenderBox { ...@@ -2531,13 +2519,14 @@ class _RenderChip extends RenderBox {
if (!size.contains(position)) { if (!size.contains(position)) {
return false; return false;
} }
final bool tapIsOnDeleteIcon = _tapIsOnDeleteIcon( final bool hitIsOnDeleteIcon = deleteIcon != null && _hitIsOnDeleteIcon(
hasDeleteButton: deleteIcon != null, padding: theme.padding,
tapPosition: position, tapPosition: position,
chipSize: size, chipSize: size,
deleteButtonSize: deleteIcon!.size,
textDirection: textDirection!, textDirection: textDirection!,
); );
final RenderBox? hitTestChild = tapIsOnDeleteIcon final RenderBox? hitTestChild = hitIsOnDeleteIcon
? (deleteIcon ?? label ?? avatar) ? (deleteIcon ?? label ?? avatar)
: (label ?? avatar); : (label ?? avatar);
...@@ -2924,16 +2913,10 @@ class _ChipSizes { ...@@ -2924,16 +2913,10 @@ class _ChipSizes {
final Offset densityAdjustment; final Offset densityAdjustment;
} }
class _LocationAwareInkRippleFactory extends InteractiveInkFeatureFactory { class _UnconstrainedInkSplashFactory extends InteractiveInkFeatureFactory {
const _LocationAwareInkRippleFactory( const _UnconstrainedInkSplashFactory(this.parentFactory);
this.hasDeleteButton,
this.chipContext,
this.deleteIconKey,
);
final bool hasDeleteButton; final InteractiveInkFeatureFactory parentFactory;
final BuildContext chipContext;
final GlobalKey deleteIconKey;
@override @override
InteractiveInkFeature create({ InteractiveInkFeature create({
...@@ -2949,61 +2932,55 @@ class _LocationAwareInkRippleFactory extends InteractiveInkFeatureFactory { ...@@ -2949,61 +2932,55 @@ class _LocationAwareInkRippleFactory extends InteractiveInkFeatureFactory {
double? radius, double? radius,
VoidCallback? onRemoved, VoidCallback? onRemoved,
}) { }) {
return parentFactory.create(
final bool tapIsOnDeleteIcon = _tapIsOnDeleteIcon(
hasDeleteButton: hasDeleteButton,
tapPosition: position,
chipSize: chipContext.size!,
textDirection: textDirection,
);
final BuildContext splashContext = tapIsOnDeleteIcon
? deleteIconKey.currentContext!
: chipContext;
final InteractiveInkFeatureFactory splashFactory = Theme.of(splashContext).splashFactory;
if (tapIsOnDeleteIcon) {
final RenderBox currentBox = referenceBox;
referenceBox = deleteIconKey.currentContext!.findRenderObject()! as RenderBox;
position = referenceBox.globalToLocal(currentBox.localToGlobal(position));
containedInkWell = false;
}
return splashFactory.create(
controller: controller, controller: controller,
referenceBox: referenceBox, referenceBox: referenceBox,
position: position, position: position,
color: color, color: color,
textDirection: textDirection, containedInkWell: false,
containedInkWell: containedInkWell,
rectCallback: rectCallback, rectCallback: rectCallback,
borderRadius: borderRadius, borderRadius: borderRadius,
customBorder: customBorder, customBorder: customBorder,
radius: radius, radius: radius,
onRemoved: onRemoved, onRemoved: onRemoved,
textDirection: textDirection,
); );
} }
} }
bool _tapIsOnDeleteIcon({ bool _hitIsOnDeleteIcon({
required bool hasDeleteButton, required EdgeInsetsGeometry padding,
required Offset tapPosition, required Offset tapPosition,
required Size chipSize, required Size chipSize,
required Size deleteButtonSize,
required TextDirection textDirection, required TextDirection textDirection,
}) { }) {
bool tapIsOnDeleteIcon; // The chipSize includes the padding, so we need to deflate the size and adjust the
if (!hasDeleteButton) { // tap position to account for the padding.
tapIsOnDeleteIcon = false; final EdgeInsets resolvedPadding = padding.resolve(textDirection);
} else { final Size deflatedSize = resolvedPadding.deflateSize(chipSize);
switch (textDirection) { final Offset adjustedPosition = tapPosition - Offset(resolvedPadding.left, resolvedPadding.top);
case TextDirection.ltr: // The delete button hit area should be at least the width of the delete button,
tapIsOnDeleteIcon = tapPosition.dx / chipSize.width > 0.66; // but, if there's room, up to 24 pixels from the center of the delete icon
break; // (corresponding to part of a 48x48 square that Material would prefer for touch
case TextDirection.rtl: // targets), but no more than half of the overall size of the chip when the chip is
tapIsOnDeleteIcon = tapPosition.dx / chipSize.width < 0.33; // small.
break; //
} // This isn't affected by materialTapTargetSize because it only applies to the
// width of the tappable region within the chip, not outside of the chip, which is
// handled elsewhere. Also because delete buttons aren't specified to be used on
// touch devices, only desktop devices.
final double accessibleDeleteButtonWidth = math.max(
deleteButtonSize.width,
math.min(
deflatedSize.width * 0.5,
24.0 + deleteButtonSize.width / 2.0,
),
);
switch (textDirection) {
case TextDirection.ltr:
return adjustedPosition.dx >= deflatedSize.width - accessibleDeleteButtonWidth;
case TextDirection.rtl:
return adjustedPosition.dx <= accessibleDeleteButtonWidth;
} }
return tapIsOnDeleteIcon;
} }
...@@ -1399,8 +1399,7 @@ class _SomethingPaintPredicate extends _PaintPredicate { ...@@ -1399,8 +1399,7 @@ class _SomethingPaintPredicate extends _PaintPredicate {
currentCall = call.current; currentCall = call.current;
if (!currentCall.invocation.isMethod) if (!currentCall.invocation.isMethod)
throw 'It called $currentCall, which was not a method, when the paint pattern expected a method call'; throw 'It called $currentCall, which was not a method, when the paint pattern expected a method call';
call.moveNext(); } while (call.moveNext() && !_runPredicate(currentCall.invocation.memberName, currentCall.invocation.positionalArguments));
} while (!_runPredicate(currentCall.invocation.memberName, currentCall.invocation.positionalArguments));
} }
bool _runPredicate(Symbol methodName, List<dynamic> arguments) { bool _runPredicate(Symbol methodName, List<dynamic> arguments) {
......
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