Fix arrow keys for production web builds.

Handle all arrows in Focus.onKeyEvent with a HardwareKeyboard fallback, and remove invalid CallbackShortcuts repeat parameter.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
nathanAndrinoid 2026-06-04 19:46:40 +00:00
parent b840db0cff
commit 60b14fb49e

View File

@ -111,13 +111,15 @@ class _SwipeQuestionTileState extends State<SwipeQuestionTile>
AnimationController(vsync: this, duration: _holdEdgeDuration) AnimationController(vsync: this, duration: _holdEdgeDuration)
..addListener(_syncHoldDeferDrag) ..addListener(_syncHoldDeferDrag)
..addStatusListener(_onHoldDeferStatus); ..addStatusListener(_onHoldDeferStatus);
HardwareKeyboard.instance.addHandler(_handleHardwareKey);
WidgetsBinding.instance.addPostFrameCallback((_) => _requestTileFocus()); WidgetsBinding.instance.addPostFrameCallback((_) => _requestTileFocus());
} }
@override @override
void didUpdateWidget(SwipeQuestionTile oldWidget) { void didUpdateWidget(SwipeQuestionTile oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (oldWidget.questionId != widget.questionId) { if (oldWidget.questionId != widget.questionId ||
(oldWidget.busy && !widget.busy)) {
WidgetsBinding.instance.addPostFrameCallback((_) => _requestTileFocus()); WidgetsBinding.instance.addPostFrameCallback((_) => _requestTileFocus());
} }
} }
@ -131,6 +133,7 @@ class _SwipeQuestionTileState extends State<SwipeQuestionTile>
@override @override
void dispose() { void dispose() {
HardwareKeyboard.instance.removeHandler(_handleHardwareKey);
_focusNode.dispose(); _focusNode.dispose();
_snapController.dispose(); _snapController.dispose();
_holdSaveController.dispose(); _holdSaveController.dispose();
@ -484,7 +487,15 @@ class _SwipeQuestionTileState extends State<SwipeQuestionTile>
_stepSliderBy(scrollDy > 0 ? -1 : 1); _stepSliderBy(scrollDy > 0 ? -1 : 1);
} }
/// Arrow left/right (held): defer / save swipe. Up/down use [CallbackShortcuts]. /// Web/production fallback when [Focus.onKeyEvent] does not receive arrows.
bool _handleHardwareKey(KeyEvent event) {
if (!_focusNode.hasFocus) {
return false;
}
return _onKeyEvent(_focusNode, event) == KeyEventResult.handled;
}
/// Arrow up/down step value; hold left/right to defer / save (edge buttons).
KeyEventResult _onKeyEvent(FocusNode node, KeyEvent event) { KeyEventResult _onKeyEvent(FocusNode node, KeyEvent event) {
if (widget.busy || _acting) { if (widget.busy || _acting) {
return KeyEventResult.ignored; return KeyEventResult.ignored;
@ -493,6 +504,18 @@ class _SwipeQuestionTileState extends State<SwipeQuestionTile>
final LogicalKeyboardKey key = event.logicalKey; final LogicalKeyboardKey key = event.logicalKey;
const _HoldInputSource kb = _HoldInputSource.keyboard; const _HoldInputSource kb = _HoldInputSource.keyboard;
if (key == LogicalKeyboardKey.arrowUp ||
key == LogicalKeyboardKey.arrowDown) {
if (event is KeyUpEvent) {
return KeyEventResult.handled;
}
if (event is KeyDownEvent || event is KeyRepeatEvent) {
_stepSliderBy(key == LogicalKeyboardKey.arrowUp ? 1 : -1);
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
}
if (key == LogicalKeyboardKey.arrowRight) { if (key == LogicalKeyboardKey.arrowRight) {
if (event is KeyDownEvent) { if (event is KeyDownEvent) {
_beginHoldSave(kb); _beginHoldSave(kb);
@ -608,13 +631,6 @@ class _SwipeQuestionTileState extends State<SwipeQuestionTile>
autofocus: true, autofocus: true,
descendantsAreFocusable: false, descendantsAreFocusable: false,
onKeyEvent: _onKeyEvent, onKeyEvent: _onKeyEvent,
child: CallbackShortcuts(
bindings: <ShortcutActivator, VoidCallback>{
const SingleActivator(LogicalKeyboardKey.arrowUp, repeat: true):
() => _stepSliderBy(1),
const SingleActivator(LogicalKeyboardKey.arrowDown, repeat: true):
() => _stepSliderBy(-1),
},
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
clipBehavior: Clip.none, clipBehavior: Clip.none,
@ -770,7 +786,6 @@ class _SwipeQuestionTileState extends State<SwipeQuestionTile>
), ),
], ],
), ),
),
); );
}, },
); );