Make hover and focus not respond when buttons and fields are disabled. (#32914)

Disabled fields and buttons were responding to hover and focus changes, and they shouldn't.
This commit is contained in:
Greg Spencer 2019-05-18 14:58:38 -07:00 committed by GitHub
parent 76dccbe2fb
commit 47a9ddc803
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 196 additions and 88 deletions

View file

@ -467,6 +467,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
Set<InteractiveInkFeature> _splashes;
InteractiveInkFeature _currentSplash;
FocusNode _focusNode;
bool _hovering = false;
final Map<_HighlightType, InkHighlight> _highlights = <_HighlightType, InkHighlight>{};
bool get highlightsExist => _highlights.values.where((InkHighlight highlight) => highlight != null).isNotEmpty;
@ -479,6 +480,15 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
_focusNode?.addListener(_handleFocusUpdate);
}
@override
void didUpdateWidget(InkResponse oldWidget) {
super.didUpdateWidget(oldWidget);
if (_isWidgetEnabled(widget) != _isWidgetEnabled(oldWidget)) {
_handleHoverChange(_hovering);
_handleFocusUpdate();
}
}
@override
void dispose() {
_focusNode?.removeListener(_handleFocusUpdate);
@ -599,8 +609,8 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
}
void _handleFocusUpdate() {
final bool hasFocus = Focus.of(context).hasPrimaryFocus;
updateHighlight(_HighlightType.focus, value: hasFocus);
final bool showFocus = enabled && (Focus.of(context, nullOk: true)?.hasPrimaryFocus ?? false);
updateHighlight(_HighlightType.focus, value: showFocus);
}
void _handleTapDown(TapDownDetails details) {
@ -669,10 +679,20 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
super.deactivate();
}
bool get enabled => widget.onTap != null || widget.onDoubleTap != null || widget.onLongPress != null;
bool _isWidgetEnabled(InkResponse widget) {
return widget.onTap != null || widget.onDoubleTap != null || widget.onLongPress != null;
}
void _handlePointerEnter(PointerEnterEvent event) => updateHighlight(_HighlightType.hover, value: true);
void _handlePointerExit(PointerExitEvent event) => updateHighlight(_HighlightType.hover, value: false);
bool get enabled => _isWidgetEnabled(widget);
void _handlePointerEnter(PointerEnterEvent event) => _handleHoverChange(true);
void _handlePointerExit(PointerExitEvent event) => _handleHoverChange(false);
void _handleHoverChange(bool hovering) {
if (_hovering != hovering) {
_hovering = hovering;
updateHighlight(_HighlightType.hover, value: enabled && _hovering);
}
}
@override
Widget build(BuildContext context) {
@ -683,8 +703,8 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
}
_currentSplash?.color = widget.splashColor ?? Theme.of(context).splashColor;
return Listener(
onPointerEnter: _handlePointerEnter,
onPointerExit: _handlePointerExit,
onPointerEnter: enabled ? _handlePointerEnter : null,
onPointerExit: enabled ? _handlePointerExit : null,
behavior: HitTestBehavior.translucent,
child: GestureDetector(
onTapDown: enabled ? _handleTapDown : null,

View file

@ -1823,8 +1823,8 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
}
TextAlign get textAlign => widget.textAlign;
bool get isFocused => widget.isFocused;
bool get isHovering => widget.isHovering;
bool get isFocused => widget.isFocused && decoration.enabled;
bool get isHovering => widget.isHovering && decoration.enabled;
bool get isEmpty => widget.isEmpty;
@override
@ -1875,11 +1875,9 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
return themeData.hintColor;
}
if (isHovering) {
// TODO(gspencer): Find out the actual value here from the spec writers.
final Color hoverColor = decoration.hoverColor ?? themeData.inputDecorationTheme?.hoverColor ?? themeData.hoverColor;
return Color.alphaBlend(hoverColor.withOpacity(0.16), themeData.colorScheme.onSurface.withOpacity(0.12));
}
// TODO(gspencer): Find out the actual value here from the spec writers.
return themeData.colorScheme.onSurface.withOpacity(0.12);
}
@ -1906,13 +1904,13 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
}
Color _getFocusColor(ThemeData themeData) {
if (decoration.filled != true) // filled == null same as filled == false
if (decoration.filled == null || !decoration.filled || !decoration.enabled)
return Colors.transparent;
return decoration.focusColor ?? themeData.inputDecorationTheme?.focusColor ?? themeData.focusColor;
}
Color _getHoverColor(ThemeData themeData) {
if (isFocused || decoration.filled != true) // filled == null same as filled == false
if (decoration.filled == null || !decoration.filled || isFocused || !decoration.enabled)
return Colors.transparent;
return decoration.hoverColor ?? themeData.inputDecorationTheme?.hoverColor ?? themeData.hoverColor;
}

View file

@ -36,12 +36,12 @@ void main() {
testWidgets('Floating Action Button tooltip', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: null,
onPressed: () {},
tooltip: 'Add',
child: Icon(Icons.add),
child: const Icon(Icons.add),
),
),
),
@ -54,12 +54,12 @@ void main() {
// Regression test for: https://github.com/flutter/flutter/pull/21084
testWidgets('Floating Action Button tooltip (long press button edge)', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: null,
onPressed: () {},
tooltip: 'Add',
child: Icon(Icons.add),
child: const Icon(Icons.add),
),
),
),
@ -75,10 +75,10 @@ void main() {
// Regression test for: https://github.com/flutter/flutter/pull/21084
testWidgets('Floating Action Button tooltip (long press button edge - no child)', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: null,
onPressed: () {},
tooltip: 'Add',
),
),
@ -94,10 +94,10 @@ void main() {
testWidgets('Floating Action Button tooltip (no child)', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: null,
onPressed: () {},
tooltip: 'Add',
),
),
@ -127,6 +127,44 @@ void main() {
expect(find.text('Add'), findsOneWidget);
});
testWidgets('Floating Action Button tooltip reacts when disabled', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: null,
tooltip: 'Add',
),
),
),
);
expect(find.text('Add'), findsNothing);
// Test hover for tooltip.
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
try {
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(FloatingActionButton)));
await tester.pumpAndSettle();
expect(find.text('Add'), findsOneWidget);
await gesture.moveTo(Offset.zero);
} finally {
await gesture.removePointer();
}
await tester.pumpAndSettle();
expect(find.text('Add'), findsNothing);
// Test long press for tooltip.
await tester.longPress(find.byType(FloatingActionButton));
await tester.pumpAndSettle();
expect(find.text('Add'), findsOneWidget);
});
testWidgets('Floating Action Button elevation when highlighted - effect', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(

View file

@ -2045,103 +2045,155 @@ void main() {
);
testWidgets('InputDecorator draws and animates hoverColor', (WidgetTester tester) async {
const Color fillColor = Color(0xFF00FF00);
const Color hoverColor = Color(0xFF0000FF);
const Color fillColor = Color(0x0A000000);
const Color hoverColor = Color(0xFF00FF00);
const Color disabledColor = Color(0x05000000);
const Color enabledBorderColor = Color(0x1f000000);
await tester.pumpWidget(
buildInputDecorator(
isHovering: false,
decoration: const InputDecoration(
filled: true,
fillColor: fillColor,
hoverColor: hoverColor,
Future<void> pumpDecorator({bool hovering, bool enabled = true, bool filled = true}) async {
return await tester.pumpWidget(
buildInputDecorator(
isHovering: hovering,
decoration: InputDecoration(
enabled: enabled,
filled: filled,
hoverColor: hoverColor,
disabledBorder: const OutlineInputBorder(borderSide: BorderSide(color: disabledColor)),
border: const OutlineInputBorder(borderSide: BorderSide(color: enabledBorderColor)),
),
),
),
);
);
}
// Test filled text field.
await pumpDecorator(hovering: false);
expect(getContainerColor(tester), equals(fillColor));
await tester.pump(const Duration(seconds: 10));
expect(getContainerColor(tester), equals(fillColor));
await tester.pumpWidget(
buildInputDecorator(
isHovering: true,
decoration: const InputDecoration(
filled: true,
fillColor: fillColor,
hoverColor: hoverColor,
),
),
);
await pumpDecorator(hovering: true);
expect(getContainerColor(tester), equals(fillColor));
await tester.pump(const Duration(milliseconds: 15));
expect(getContainerColor(tester), equals(hoverColor));
await tester.pumpWidget(
buildInputDecorator(
isHovering: false,
decoration: const InputDecoration(
filled: true,
fillColor: fillColor,
hoverColor: hoverColor,
),
),
);
await pumpDecorator(hovering: false);
expect(getContainerColor(tester), equals(hoverColor));
await tester.pump(const Duration(milliseconds: 15));
expect(getContainerColor(tester), equals(fillColor));
await pumpDecorator(hovering: false, enabled: false);
expect(getContainerColor(tester), equals(disabledColor));
await tester.pump(const Duration(seconds: 10));
expect(getContainerColor(tester), equals(disabledColor));
await pumpDecorator(hovering: true, enabled: false);
expect(getContainerColor(tester), equals(disabledColor));
await tester.pump(const Duration(seconds: 10));
expect(getContainerColor(tester), equals(disabledColor));
// Test outline text field.
const Color blendedHoverColor = Color(0x43009c00);
await pumpDecorator(hovering: false, filled: false);
await tester.pumpAndSettle();
expect(getBorderColor(tester), equals(enabledBorderColor));
await tester.pump(const Duration(seconds: 10));
expect(getBorderColor(tester), equals(enabledBorderColor));
await pumpDecorator(hovering: true, filled: false);
expect(getBorderColor(tester), equals(enabledBorderColor));
await tester.pump(const Duration(milliseconds: 200));
expect(getBorderColor(tester), equals(blendedHoverColor));
await pumpDecorator(hovering: false, filled: false);
expect(getBorderColor(tester), equals(blendedHoverColor));
await tester.pump(const Duration(milliseconds: 200));
expect(getBorderColor(tester), equals(enabledBorderColor));
await pumpDecorator(hovering: false, filled: false, enabled: false);
expect(getBorderColor(tester), equals(enabledBorderColor));
await tester.pump(const Duration(milliseconds: 200));
expect(getBorderColor(tester), equals(disabledColor));
await pumpDecorator(hovering: true, filled: false, enabled: false);
expect(getBorderColor(tester), equals(disabledColor));
await tester.pump(const Duration(seconds: 10));
expect(getBorderColor(tester), equals(disabledColor));
});
testWidgets('InputDecorator draws and animates focusColor', (WidgetTester tester) async {
const Color fillColor = Color(0xFF00FF00);
const Color focusColor = Color(0xFF0000FF);
const Color fillColor = Color(0x0A000000);
const Color disabledColor = Color(0x05000000);
const Color enabledBorderColor = Color(0x1f000000);
await tester.pumpWidget(
buildInputDecorator(
isFocused: false,
decoration: const InputDecoration(
filled: true,
fillColor: fillColor,
focusColor: focusColor,
Future<void> pumpDecorator({bool focused, bool enabled = true, bool filled = true}) async {
return await tester.pumpWidget(
buildInputDecorator(
isFocused: focused,
decoration: InputDecoration(
enabled: enabled,
filled: filled,
focusColor: focusColor,
focusedBorder: const OutlineInputBorder(borderSide: BorderSide(color: focusColor)),
disabledBorder: const OutlineInputBorder(borderSide: BorderSide(color: disabledColor)),
border: const OutlineInputBorder(borderSide: BorderSide(color: enabledBorderColor)),
),
),
),
);
);
}
// Test filled text field.
await pumpDecorator(focused: false);
expect(getContainerColor(tester), equals(fillColor));
await tester.pump(const Duration(seconds: 10));
expect(getContainerColor(tester), equals(fillColor));
await tester.pumpWidget(
buildInputDecorator(
isFocused: true,
decoration: const InputDecoration(
filled: true,
fillColor: fillColor,
focusColor: focusColor,
),
),
);
await pumpDecorator(focused: true);
expect(getContainerColor(tester), equals(fillColor));
await tester.pump(const Duration(milliseconds: 45));
expect(getContainerColor(tester), equals(focusColor));
await tester.pumpWidget(
buildInputDecorator(
isFocused: false,
decoration: const InputDecoration(
filled: true,
fillColor: fillColor,
focusColor: focusColor,
),
),
);
await pumpDecorator(focused: false);
expect(getContainerColor(tester), equals(focusColor));
await tester.pump(const Duration(milliseconds: 15));
expect(getContainerColor(tester), equals(fillColor));
await pumpDecorator(focused: false, enabled: false);
expect(getContainerColor(tester), equals(disabledColor));
await tester.pump(const Duration(seconds: 10));
expect(getContainerColor(tester), equals(disabledColor));
await pumpDecorator(focused: true, enabled: false);
expect(getContainerColor(tester), equals(disabledColor));
await tester.pump(const Duration(seconds: 10));
expect(getContainerColor(tester), equals(disabledColor));
// Test outline text field.
await pumpDecorator(focused: false, filled: false);
await tester.pumpAndSettle();
expect(getBorderColor(tester), equals(enabledBorderColor));
await tester.pump(const Duration(seconds: 10));
expect(getBorderColor(tester), equals(enabledBorderColor));
await pumpDecorator(focused: true, filled: false);
expect(getBorderColor(tester), equals(enabledBorderColor));
await tester.pump(const Duration(milliseconds: 200));
expect(getBorderColor(tester), equals(focusColor));
await pumpDecorator(focused: false, filled: false);
expect(getBorderColor(tester), equals(focusColor));
await tester.pump(const Duration(milliseconds: 200));
expect(getBorderColor(tester), equals(enabledBorderColor));
await pumpDecorator(focused: false, filled: false, enabled: false);
expect(getBorderColor(tester), equals(enabledBorderColor));
await tester.pump(const Duration(milliseconds: 200));
expect(getBorderColor(tester), equals(disabledColor));
await pumpDecorator(focused: true, filled: false, enabled: false);
expect(getBorderColor(tester), equals(disabledColor));
await tester.pump(const Duration(seconds: 10));
expect(getBorderColor(tester), equals(disabledColor));
});
testWidgets('InputDecorationTheme.toString()', (WidgetTester tester) async {