From b7f7a3d2edab9c4076d2504f362757fc453a071a Mon Sep 17 00:00:00 2001 From: AK <144495202+AKnassa@users.noreply.github.com> Date: Mon, 27 Apr 2026 01:52:02 -0400 Subject: [PATCH] fix(fullscreen): consume FullscreenProvider, remove per-instance state useFullscreen is now a thin useContext consumer of FullscreenProvider, so all callers share a single fullscreen state owned by the app root. Why this fixes the desync bug: stremio-router keeps multiple route layers mounted at once, and each top-level route (Board, Discover, Library, Calendar, Addons, Settings, Search) renders its own MainNavBars -> HorizontalNavBar -> useFullscreen. The previous hook held local useState plus its own listeners, so each route had an independent boolean. Entering fullscreen, then navigating to another tab, mounted a fresh hook initialized to false; the icon flipped back to "enter fullscreen" and clicking it re-requested fullscreen on top of the existing one, leaving the UI unresponsive until a route remount happened to coincide with reality. With one provider above the router, state outlives route remounts and listeners are attached exactly once. The hook's return tuple shape ([fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen]) is preserved, so all three call sites (HorizontalNavBar, NavMenuContent, Player) keep working with no API change. Also removes the legacy src/common/useFullscreen.ts and routes its imports through stremio/common/Fullscreen (and the stremio/common barrel for App.js / Player). Note: MainNavBars is still rendered per-route. Lifting it to a single app-level layout above the router is a worthwhile follow-up (eliminates 6+ duplicate mounts) but carries non-trivial CSS / useRouteFocused / stacked-route risk and is out of scope for this PR; tracking separately. Made-with: Cursor --- src/App/App.js | 3 +- src/common/index.js | 3 +- src/common/useFullscreen.ts | 86 ------------------- .../HorizontalNavBar/HorizontalNavBar.js | 2 +- .../NavMenu/NavMenuContent.js | 2 +- 5 files changed, 5 insertions(+), 91 deletions(-) delete mode 100644 src/common/useFullscreen.ts diff --git a/src/App/App.js b/src/App/App.js index ea226895f..c27cc2901 100644 --- a/src/App/App.js +++ b/src/App/App.js @@ -6,8 +6,7 @@ const { useTranslation } = require('react-i18next'); const { Router } = require('stremio-router'); const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider } = require('stremio/services'); const { NotFound } = require('stremio/routes'); -const { FileDropProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, withCoreSuspender, useShell, useBinaryState } = require('stremio/common'); -const { FullscreenProvider } = require('stremio/common/Fullscreen'); +const { FileDropProvider, FullscreenProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, withCoreSuspender, useShell, useBinaryState } = require('stremio/common'); const ServicesToaster = require('./ServicesToaster'); const DeepLinkHandler = require('./DeepLinkHandler'); const SearchParamsHandler = require('./SearchParamsHandler'); diff --git a/src/common/index.js b/src/common/index.js index e608e7e23..1963e8995 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const { FileDropProvider, onFileDrop } = require('./FileDrop'); +const { FullscreenProvider, useFullscreen } = require('./Fullscreen'); const { PlatformProvider, usePlatform } = require('./Platform'); const { ToastProvider, useToast } = require('./Toast'); const { TooltipProvider, Tooltip } = require('./Tooltips'); @@ -14,7 +15,6 @@ const languages = require('./languages'); const routesRegexp = require('./routesRegexp'); const useAnimationFrame = require('./useAnimationFrame'); const useBinaryState = require('./useBinaryState'); -const { default: useFullscreen } = require('./useFullscreen'); const { default: useInterval } = require('./useInterval'); const useLiveRef = require('./useLiveRef'); const useModelState = require('./useModelState'); @@ -34,6 +34,7 @@ const { default: useLanguageSorting } = require('./useLanguageSorting'); module.exports = { FileDropProvider, onFileDrop, + FullscreenProvider, PlatformProvider, usePlatform, ShortcutsProvider, diff --git a/src/common/useFullscreen.ts b/src/common/useFullscreen.ts deleted file mode 100644 index 8a1692254..000000000 --- a/src/common/useFullscreen.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (C) 2017-2023 Smart code 203358507 - -import { useCallback, useEffect, useState } from 'react'; -import useShell, { type WindowVisibility } from './useShell'; -import useSettings from './useSettings'; - -const useFullscreen = () => { - const shell = useShell(); - const [settings] = useSettings(); - - const [fullscreen, setFullscreen] = useState(false); - - const requestFullscreen = useCallback(async () => { - if (shell.active) { - shell.send('win-set-visibility', { fullscreen: true }); - } else { - try { - await document.documentElement.requestFullscreen(); - } catch (err) { - console.error('Error enabling fullscreen', err); - } - } - }, []); - - const exitFullscreen = useCallback(() => { - if (shell.active) { - shell.send('win-set-visibility', { fullscreen: false }); - } else { - if (document.fullscreenElement === document.documentElement) { - document.exitFullscreen(); - } - } - }, []); - - const toggleFullscreen = useCallback(() => { - fullscreen ? exitFullscreen() : requestFullscreen(); - }, [fullscreen]); - - useEffect(() => { - const onWindowVisibilityChanged = (state: WindowVisibility) => { - setFullscreen(state.isFullscreen === true); - }; - - const onFullscreenChange = () => { - setFullscreen(document.fullscreenElement === document.documentElement); - }; - - const onKeyDown = (event: KeyboardEvent) => { - - const activeElement = document.activeElement as HTMLElement; - - const inputFocused = - activeElement && - (activeElement.tagName === 'INPUT' || - activeElement.tagName === 'TEXTAREA' || - activeElement.tagName === 'SELECT' || - activeElement.isContentEditable); - - if (event.code === 'Escape' && settings.escExitFullscreen) { - exitFullscreen(); - } - - if (event.code === 'KeyF' && !inputFocused) { - toggleFullscreen(); - } - - if (event.code === 'F11' && shell.active) { - toggleFullscreen(); - } - }; - - shell.on('win-visibility-changed', onWindowVisibilityChanged); - document.addEventListener('keydown', onKeyDown); - document.addEventListener('fullscreenchange', onFullscreenChange); - - return () => { - shell.off('win-visibility-changed', onWindowVisibilityChanged); - document.removeEventListener('keydown', onKeyDown); - document.removeEventListener('fullscreenchange', onFullscreenChange); - }; - }, [settings.escExitFullscreen, toggleFullscreen]); - - return [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen]; -}; - -export default useFullscreen; diff --git a/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js b/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js index 6fed91c8a..4b0655918 100644 --- a/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js +++ b/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js @@ -5,7 +5,7 @@ const PropTypes = require('prop-types'); const classnames = require('classnames'); const { default: Icon } = require('@stremio/stremio-icons/react'); const { Button, Image } = require('stremio/components'); -const { default: useFullscreen } = require('stremio/common/useFullscreen'); +const { useFullscreen } = require('stremio/common/Fullscreen'); const usePWA = require('stremio/common/usePWA'); const SearchBar = require('./SearchBar'); const NavMenu = require('./NavMenu'); diff --git a/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js b/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js index b6309a44c..6615e5b76 100644 --- a/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js +++ b/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js @@ -7,7 +7,7 @@ const { useTranslation } = require('react-i18next'); const { default: Icon } = require('@stremio/stremio-icons/react'); const { useServices } = require('stremio/services'); const { Button } = require('stremio/components'); -const { default: useFullscreen } = require('stremio/common/useFullscreen'); +const { useFullscreen } = require('stremio/common/Fullscreen'); const useProfile = require('stremio/common/useProfile'); const usePWA = require('stremio/common/usePWA'); const { default: usePlayUrl } = require('stremio/common/usePlayUrl');