Skip to content

Commit 510ecaa

Browse files
authored
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 flutter/flutter#132377. ## Tests Adds one test.
1 parent f0b682b commit 510ecaa

File tree

2 files changed

+62
-2
lines changed

2 files changed

+62
-2
lines changed

packages/flutter/lib/src/material/ink_well.dart

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:async';
56
import 'dart:collection';
67

78
import 'package:flutter/foundation.dart';
@@ -809,15 +810,18 @@ class _InkResponseState extends State<_InkResponseStateWidget>
809810
bool _hovering = false;
810811
final Map<_HighlightType, InkHighlight?> _highlights = <_HighlightType, InkHighlight?>{};
811812
late final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{
812-
ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: simulateTap),
813-
ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(onInvoke: simulateTap),
813+
ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: activateOnIntent),
814+
ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(onInvoke: activateOnIntent),
814815
};
815816
MaterialStatesController? internalStatesController;
816817

817818
bool get highlightsExist => _highlights.values.where((InkHighlight? highlight) => highlight != null).isNotEmpty;
818819

819820
final ObserverList<_ParentInkResponseState> _activeChildren = ObserverList<_ParentInkResponseState>();
820821

822+
static const Duration _activationDuration = Duration(milliseconds: 100);
823+
Timer? _activationTimer;
824+
821825
@override
822826
void markChildInkResponsePressed(_ParentInkResponseState childState, bool value) {
823827
final bool lastAnyPressed = _anyChildInkResponsePressed;
@@ -833,6 +837,25 @@ class _InkResponseState extends State<_InkResponseStateWidget>
833837
}
834838
bool get _anyChildInkResponsePressed => _activeChildren.isNotEmpty;
835839

840+
void activateOnIntent(Intent? intent) {
841+
_activationTimer?.cancel();
842+
_activationTimer = null;
843+
_startNewSplash(context: context);
844+
_currentSplash?.confirm();
845+
_currentSplash = null;
846+
if (widget.onTap != null) {
847+
if (widget.enableFeedback) {
848+
Feedback.forTap(context);
849+
}
850+
widget.onTap?.call();
851+
}
852+
// Delay the call to `updateHighlight` to simulate a pressed delay
853+
// and give MaterialStatesController listeners a chance to react.
854+
_activationTimer = Timer(_activationDuration, () {
855+
updateHighlight(_HighlightType.pressed, value: false);
856+
});
857+
}
858+
836859
void simulateTap([Intent? intent]) {
837860
_startNewSplash(context: context);
838861
handleTap();
@@ -917,6 +940,8 @@ class _InkResponseState extends State<_InkResponseStateWidget>
917940
FocusManager.instance.removeHighlightModeListener(handleFocusHighlightModeChange);
918941
statesController.removeListener(handleStatesControllerChange);
919942
internalStatesController?.dispose();
943+
_activationTimer?.cancel();
944+
_activationTimer = null;
920945
super.dispose();
921946
}
922947

packages/flutter/test/material/ink_well_test.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2253,4 +2253,39 @@ testWidgetsWithLeakTracking('InkResponse radius can be updated', (WidgetTester t
22532253
expect(log, equals(<String>['tap']));
22542254
log.clear();
22552255
});
2256+
2257+
testWidgetsWithLeakTracking('InkWell activation action does not end immediately', (WidgetTester tester) async {
2258+
// Regression test for https://github.com/flutter/flutter/issues/132377.
2259+
final MaterialStatesController controller = MaterialStatesController();
2260+
2261+
await tester.pumpWidget(Directionality(
2262+
textDirection: TextDirection.ltr,
2263+
child: Shortcuts(
2264+
shortcuts: const <ShortcutActivator, Intent>{
2265+
SingleActivator(LogicalKeyboardKey.enter): ButtonActivateIntent(),
2266+
},
2267+
child: Material(
2268+
child: Center(
2269+
child: InkWell(
2270+
autofocus: true,
2271+
onTap: () {},
2272+
statesController: controller,
2273+
),
2274+
),
2275+
),
2276+
),
2277+
));
2278+
2279+
// Invoke the InkWell activation action.
2280+
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
2281+
2282+
// The InkWell is in pressed state.
2283+
await tester.pump(const Duration(milliseconds: 99));
2284+
expect(controller.value.contains(MaterialState.pressed), isTrue);
2285+
2286+
await tester.pumpAndSettle();
2287+
expect(controller.value.contains(MaterialState.pressed), isFalse);
2288+
2289+
controller.dispose();
2290+
});
22562291
}

0 commit comments

Comments
 (0)