diff --git a/lib/focus/key_event_utils.dart b/lib/focus/key_event_utils.dart index b33b4985..ef1dc5bb 100644 --- a/lib/focus/key_event_utils.dart +++ b/lib/focus/key_event_utils.dart @@ -62,10 +62,18 @@ KeyEventResult handleBackKeyAction(KeyEvent event, VoidCallback onBack) { return KeyEventResult.handled; } - if (PlatformDetector.isAppleTV() && event.isPhysicalKeyboardEvent && event is KeyDownEvent) { - BackKeyCoordinator.markHandled(); - BackKeyUpSuppressor.suppressBackUntilKeyUp(); - onBack(); + // AppleTV physical-keyboard back (Siri Remote Menu via engine-synthesized + // escape): run onBack on KeyDown only; consume KeyUp silently. The + // suppressor-based "arm-on-KeyDown, clear-on-KeyUp" pattern leaks here + // because onBack typically calls Navigator.pop, swapping the focus tree + // before the matching KeyUp is dispatched — the orphaned KeyUp then never + // reaches a consumeIfSuppressed call, pinning the suppressor armed and + // silently swallowing the next press's KeyDown. + if (PlatformDetector.isAppleTV() && event.isPhysicalKeyboardEvent) { + if (event is KeyDownEvent) { + BackKeyCoordinator.markHandled(); + onBack(); + } return KeyEventResult.handled; } @@ -174,6 +182,12 @@ FocusOnKeyEventCallback dpadKeyHandler({ class BackKeySuppressorObserver extends NavigatorObserver { @override void didPop(Route route, Route? previousRoute) { + // On AppleTV, handleBackKeyAction consumes the KeyUp silently regardless, + // so the suppressor isn't needed and arming it would pin state across the + // pop's focus-tree swap. (The atomic engine fix delivers KeyDown+KeyUp in + // a single recognizer Began callback, so didPop fires squarely inside the + // window where BackKeyPressTracker.isBackKeyDown is true.) + if (PlatformDetector.isAppleTV()) return; if (BackKeyPressTracker.isBackKeyDown) { BackKeyUpSuppressor.suppressBackUntilKeyUp(); } diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart index ac388982..57896f6e 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main_screen.dart @@ -954,14 +954,19 @@ class _MainScreenState extends State return KeyEventResult.handled; } + // AppleTV: KeyDown does the work, KeyUp is consumed silently. See the + // matching comment in handleBackKeyAction for why the suppressor pattern + // doesn't fit here. if (PlatformDetector.isAppleTV() && event is KeyDownEvent) { final result = _handleMainBack(allowTvSystemExit: true); if (result == KeyEventResult.handled) { BackKeyCoordinator.markHandled(); - BackKeyUpSuppressor.suppressBackUntilKeyUp(); } return result; } + if (PlatformDetector.isAppleTV() && event is KeyUpEvent) { + return KeyEventResult.handled; + } if (event is KeyUpEvent) { final result = _handleMainBack(allowTvSystemExit: PlatformDetector.isAppleTV()); diff --git a/tvos/engine.version b/tvos/engine.version index f375b33d..9c49820d 100644 --- a/tvos/engine.version +++ b/tvos/engine.version @@ -1 +1 @@ -3.41.6+7 +3.41.6+8