Skip to content

Commit

Permalink
Fix MaterialState.pressed is missing when pressing button with keyboa…
Browse files Browse the repository at this point in the history
…rd (#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.
  • Loading branch information
bleroux authored Sep 1, 2023
1 parent f0b682b commit 510ecaa
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 2 deletions.
29 changes: 27 additions & 2 deletions packages/flutter/lib/src/material/ink_well.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -809,15 +810,18 @@ 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;

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

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;
Expand All @@ -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();
Expand Down Expand Up @@ -917,6 +940,8 @@ class _InkResponseState extends State<_InkResponseStateWidget>
FocusManager.instance.removeHighlightModeListener(handleFocusHighlightModeChange);
statesController.removeListener(handleStatesControllerChange);
internalStatesController?.dispose();
_activationTimer?.cancel();
_activationTimer = null;
super.dispose();
}

Expand Down
35 changes: 35 additions & 0 deletions packages/flutter/test/material/ink_well_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
}

0 comments on commit 510ecaa

Please sign in to comment.