From 60b14fb49ebd67f856bf8d06349a77a2f8fb328e Mon Sep 17 00:00:00 2001 From: nathanAndrinoid Date: Thu, 4 Jun 2026 19:46:40 +0000 Subject: [PATCH] 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 --- lib/widgets/swipe_question_tile.dart | 37 +++++++++++++++++++--------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/lib/widgets/swipe_question_tile.dart b/lib/widgets/swipe_question_tile.dart index 009c38d..455cf66 100644 --- a/lib/widgets/swipe_question_tile.dart +++ b/lib/widgets/swipe_question_tile.dart @@ -111,13 +111,15 @@ class _SwipeQuestionTileState extends State AnimationController(vsync: this, duration: _holdEdgeDuration) ..addListener(_syncHoldDeferDrag) ..addStatusListener(_onHoldDeferStatus); + HardwareKeyboard.instance.addHandler(_handleHardwareKey); WidgetsBinding.instance.addPostFrameCallback((_) => _requestTileFocus()); } @override void didUpdateWidget(SwipeQuestionTile oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.questionId != widget.questionId) { + if (oldWidget.questionId != widget.questionId || + (oldWidget.busy && !widget.busy)) { WidgetsBinding.instance.addPostFrameCallback((_) => _requestTileFocus()); } } @@ -131,6 +133,7 @@ class _SwipeQuestionTileState extends State @override void dispose() { + HardwareKeyboard.instance.removeHandler(_handleHardwareKey); _focusNode.dispose(); _snapController.dispose(); _holdSaveController.dispose(); @@ -484,7 +487,15 @@ class _SwipeQuestionTileState extends State _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) { if (widget.busy || _acting) { return KeyEventResult.ignored; @@ -493,6 +504,18 @@ class _SwipeQuestionTileState extends State final LogicalKeyboardKey key = event.logicalKey; 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 (event is KeyDownEvent) { _beginHoldSave(kb); @@ -608,14 +631,7 @@ class _SwipeQuestionTileState extends State autofocus: true, descendantsAreFocusable: false, onKeyEvent: _onKeyEvent, - child: CallbackShortcuts( - bindings: { - const SingleActivator(LogicalKeyboardKey.arrowUp, repeat: true): - () => _stepSliderBy(1), - const SingleActivator(LogicalKeyboardKey.arrowDown, repeat: true): - () => _stepSliderBy(-1), - }, - child: Stack( + child: Stack( alignment: Alignment.center, clipBehavior: Clip.none, children: [ @@ -769,7 +785,6 @@ class _SwipeQuestionTileState extends State ), ), ], - ), ), ); },