mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
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:
parent
76dccbe2fb
commit
47a9ddc803
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue