Fix MaterialState.pressed is missing when pressing button with keyboard (#133558)
## Description This PR fixes changes how `InkWell` reacts to keyboard activation. **Before**: the activation started a splash and immediately terminated it which did not let time for widgets that resolve material state properties to react (visually it also mean the splash does not have time to expand). **After**: the activation starts and terminates after a delay (I arbitrary choose 200ms for the moment). ## Related Issue Fixes https://github.com/flutter/flutter/issues/132377. ## Tests Adds one test.
This commit is contained in:
parent
f0b682bc05
commit
510ecaa4e7
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
@ -809,8 +810,8 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
bool _hovering = false;
|
||||
final Map<_HighlightType, InkHighlight?> _highlights = <_HighlightType, InkHighlight?>{};
|
||||
late final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{
|
||||
ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: simulateTap),
|
||||
ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(onInvoke: simulateTap),
|
||||
ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: activateOnIntent),
|
||||
ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(onInvoke: activateOnIntent),
|
||||
};
|
||||
MaterialStatesController? internalStatesController;
|
||||
|
||||
@ -818,6 +819,9 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
|
||||
final ObserverList<_ParentInkResponseState> _activeChildren = ObserverList<_ParentInkResponseState>();
|
||||
|
||||
static const Duration _activationDuration = Duration(milliseconds: 100);
|
||||
Timer? _activationTimer;
|
||||
|
||||
@override
|
||||
void markChildInkResponsePressed(_ParentInkResponseState childState, bool value) {
|
||||
final bool lastAnyPressed = _anyChildInkResponsePressed;
|
||||
@ -833,6 +837,25 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
}
|
||||
bool get _anyChildInkResponsePressed => _activeChildren.isNotEmpty;
|
||||
|
||||
void activateOnIntent(Intent? intent) {
|
||||
_activationTimer?.cancel();
|
||||
_activationTimer = null;
|
||||
_startNewSplash(context: context);
|
||||
_currentSplash?.confirm();
|
||||
_currentSplash = null;
|
||||
if (widget.onTap != null) {
|
||||
if (widget.enableFeedback) {
|
||||
Feedback.forTap(context);
|
||||
}
|
||||
widget.onTap?.call();
|
||||
}
|
||||
// Delay the call to `updateHighlight` to simulate a pressed delay
|
||||
// and give MaterialStatesController listeners a chance to react.
|
||||
_activationTimer = Timer(_activationDuration, () {
|
||||
updateHighlight(_HighlightType.pressed, value: false);
|
||||
});
|
||||
}
|
||||
|
||||
void simulateTap([Intent? intent]) {
|
||||
_startNewSplash(context: context);
|
||||
handleTap();
|
||||
@ -917,6 +940,8 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
FocusManager.instance.removeHighlightModeListener(handleFocusHighlightModeChange);
|
||||
statesController.removeListener(handleStatesControllerChange);
|
||||
internalStatesController?.dispose();
|
||||
_activationTimer?.cancel();
|
||||
_activationTimer = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -2253,4 +2253,39 @@ testWidgetsWithLeakTracking('InkResponse radius can be updated', (WidgetTester t
|
||||
expect(log, equals(<String>['tap']));
|
||||
log.clear();
|
||||
});
|
||||
|
||||
testWidgetsWithLeakTracking('InkWell activation action does not end immediately', (WidgetTester tester) async {
|
||||
// Regression test for https://github.com/flutter/flutter/issues/132377.
|
||||
final MaterialStatesController controller = MaterialStatesController();
|
||||
|
||||
await tester.pumpWidget(Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Shortcuts(
|
||||
shortcuts: const <ShortcutActivator, Intent>{
|
||||
SingleActivator(LogicalKeyboardKey.enter): ButtonActivateIntent(),
|
||||
},
|
||||
child: Material(
|
||||
child: Center(
|
||||
child: InkWell(
|
||||
autofocus: true,
|
||||
onTap: () {},
|
||||
statesController: controller,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
// Invoke the InkWell activation action.
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
||||
|
||||
// The InkWell is in pressed state.
|
||||
await tester.pump(const Duration(milliseconds: 99));
|
||||
expect(controller.value.contains(MaterialState.pressed), isTrue);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.value.contains(MaterialState.pressed), isFalse);
|
||||
|
||||
controller.dispose();
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user