stremio-web/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js
AK b7f7a3d2ed 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
2026-04-27 01:52:02 -04:00

99 lines
4 KiB
JavaScript

// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
const { Button, Image } = require('stremio/components');
const { useFullscreen } = require('stremio/common/Fullscreen');
const usePWA = require('stremio/common/usePWA');
const SearchBar = require('./SearchBar');
const NavMenu = require('./NavMenu');
const styles = require('./styles');
const { t } = require('i18next');
const HorizontalNavBar = React.memo(({ className, route, query, title, backButton, searchBar, fullscreenButton, navMenu, hdrInfo, ...props }) => {
const backButtonOnClick = React.useCallback(() => {
window.history.back();
}, []);
const [fullscreen, requestFullscreen, exitFullscreen] = useFullscreen();
const [isIOSPWA] = usePWA();
const renderNavMenuLabel = React.useCallback(({ ref, className, onClick, children, }) => (
<Button ref={ref} className={classnames(className, styles['button-container'], styles['menu-button-container'])} tabIndex={-1} onClick={onClick}>
<Icon className={styles['icon']} name={'person-outline'} />
{children}
</Button>
), []);
return (
<nav {...props} className={classnames(className, styles['horizontal-nav-bar-container'])}>
{
backButton ?
<Button className={classnames(styles['button-container'], styles['back-button-container'])} tabIndex={-1} onClick={backButtonOnClick}>
<Icon className={styles['icon']} name={'chevron-back'} />
</Button>
:
<div className={styles['logo-container']}>
<Image
className={styles['logo']}
src={require('/assets/images/stremio_symbol.png')}
alt={' '}
/>
</div>
}
{
typeof title === 'string' && title.length > 0 ?
<h2 className={styles['title']}>{title}</h2>
:
null
}
{
searchBar && route !== 'addons' ?
<SearchBar className={styles['search-bar']} query={query} active={route === 'search'} />
:
null
}
<div className={styles['buttons-container']}>
{
hdrInfo && (hdrInfo.gamma === 'pq' || hdrInfo.gamma === 'hlg') ?
<div className={styles['hdr-indicator']} title={hdrInfo.gamma === 'pq' ? 'HDR10' : 'HLG'}>
<Icon className={styles['icon']} name={'hdr'} />
</div>
:
null
}
{
!isIOSPWA && fullscreenButton ?
<Button className={styles['button-container']} title={fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')} tabIndex={-1} onClick={fullscreen ? exitFullscreen : requestFullscreen}>
<Icon className={styles['icon']} name={fullscreen ? 'minimize' : 'maximize'} />
</Button>
:
null
}
{
navMenu ?
<NavMenu renderLabel={renderNavMenuLabel} />
:
null
}
</div>
</nav>
);
});
HorizontalNavBar.displayName = 'HorizontalNavBar';
HorizontalNavBar.propTypes = {
className: PropTypes.string,
route: PropTypes.string,
query: PropTypes.string,
title: PropTypes.string,
backButton: PropTypes.bool,
searchBar: PropTypes.bool,
fullscreenButton: PropTypes.bool,
navMenu: PropTypes.bool,
hdrInfo: PropTypes.shape({
gamma: PropTypes.string,
}),
};
module.exports = HorizontalNavBar;