diff --git a/package.json b/package.json index f397368..00fc21d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "has", "private": true, - "version": "0.0.18", + "version": "0.0.19", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0006c16..45a3424 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "has" -version = "0.0.18" +version = "0.0.19" description = "has" authors = ["autli"] edition = "2024" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index c663303..291ddcf 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "has", - "version": "0.0.18", + "version": "0.0.19", "identifier": "com.has.tauri-app", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/App.tsx b/src/App.tsx index 37f948e..1734d6f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,8 +27,6 @@ const isTauri = () => { // Helper for Smart TV / Android TV detection const detectTvEnvironment = (): boolean => { if (typeof window === "undefined") return false; - const saved = localStorage.getItem("has_tv_mode"); - if (saved !== null) return saved === "true"; const ua = (navigator.userAgent || "").toLowerCase(); const isTvUa = ua.includes("android tv") || @@ -55,6 +53,17 @@ const detectTvEnvironment = (): boolean => { const isAndroid = ua.includes("android"); const noTouch = !("ontouchstart" in window) && navigator.maxTouchPoints === 0; if (isAndroid && noTouch) return true; + + // On pure desktop (macOS / Windows / Desktop Linux), default to desktop PC mode + const isDesktop = + (ua.includes("windows nt") || ua.includes("macintosh") || (ua.includes("linux") && !ua.includes("android"))) && + !isTvUa; + if (isDesktop) { + return localStorage.getItem("has_tv_mode_manual") === "true"; + } + + const saved = localStorage.getItem("has_tv_mode"); + if (saved !== null) return saved === "true"; return false; }; @@ -144,21 +153,6 @@ export default function App() { checkDatabase(); }, [isTvEnvironment]); - // Activate TV mode immediately upon any remote control key press - useEffect(() => { - const handleRemoteKey = (e: KeyboardEvent) => { - const isRemoteKey = - [19, 20, 21, 22, 23, 4, 82, 85, 179].includes(e.keyCode) || - ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key); - if (isRemoteKey && !isTvEnvironment) { - setIsTvEnvironment(true); - localStorage.setItem("has_tv_mode", "true"); - } - }; - window.addEventListener("keydown", handleRemoteKey); - return () => window.removeEventListener("keydown", handleRemoteKey); - }, [isTvEnvironment]); - // Countdown timer for OTP re-send in resetStage 2 useEffect(() => { if (resetStage !== 2) return; @@ -525,6 +519,7 @@ export default function App() { dbInfo={dbInfo} dbStatus={dbStatus} onLogout={handleLogout} + isTvEnvironment={isTvEnvironment} /> ); @@ -542,6 +537,7 @@ export default function App() { onSwitchToDesktop={() => { setIsTvEnvironment(false); localStorage.setItem("has_tv_mode", "false"); + localStorage.setItem("has_tv_mode_manual", "false"); }} /> @@ -812,6 +808,7 @@ export default function App() { onClick={() => { setIsTvEnvironment(true); localStorage.setItem("has_tv_mode", "true"); + localStorage.setItem("has_tv_mode_manual", "true"); }} title="Перейти в режим Android TV (выбор профилей)" style={{ diff --git a/src/components/UpdaterBanner.tsx b/src/components/UpdaterBanner.tsx index c56339d..5fd6324 100644 --- a/src/components/UpdaterBanner.tsx +++ b/src/components/UpdaterBanner.tsx @@ -22,8 +22,6 @@ interface UpdaterBannerProps { // Helper for Smart TV / Android TV detection const detectTvEnvironment = (): boolean => { if (typeof window === "undefined") return false; - const saved = localStorage.getItem("has_tv_mode"); - if (saved !== null) return saved === "true"; const ua = (navigator.userAgent || "").toLowerCase(); const isTvUa = ua.includes("android tv") || @@ -50,6 +48,17 @@ const detectTvEnvironment = (): boolean => { const isAndroid = ua.includes("android"); const noTouch = !("ontouchstart" in window) && navigator.maxTouchPoints === 0; if (isAndroid && noTouch) return true; + + // On pure desktop (macOS / Windows / Desktop Linux), default to desktop PC mode + const isDesktop = + (ua.includes("windows nt") || ua.includes("macintosh") || (ua.includes("linux") && !ua.includes("android"))) && + !isTvUa; + if (isDesktop) { + return localStorage.getItem("has_tv_mode_manual") === "true"; + } + + const saved = localStorage.getItem("has_tv_mode"); + if (saved !== null) return saved === "true"; return false; }; @@ -88,7 +97,7 @@ const isNewerSemver = (remote: string, current: string): boolean => { export function UpdaterBanner({ isTvEnvironment }: UpdaterBannerProps = {}) { const [isTv, setIsTv] = useState(() => isTvEnvironment ?? detectTvEnvironment()); - const [currentAppVersion, setCurrentAppVersion] = useState("0.0.18"); + const [currentAppVersion, setCurrentAppVersion] = useState("0.0.19"); const [updateAvailable, setUpdateAvailable] = useState(false); const [updateInfo, setUpdateInfo] = useState(null); const [downloading, setDownloading] = useState(false); diff --git a/src/pages/cinema/Cinema.tsx b/src/pages/cinema/Cinema.tsx index 756ac00..2938f61 100644 --- a/src/pages/cinema/Cinema.tsx +++ b/src/pages/cinema/Cinema.tsx @@ -493,8 +493,6 @@ const formatMediaImg = (path: string | undefined, size: "w500" | "original" = "w // Detect Android TV / Smart TV user agent or previous user preference const detectTvEnvironment = (): boolean => { if (typeof window === "undefined") return false; - const saved = localStorage.getItem("has_tv_mode"); - if (saved !== null) return saved === "true"; const ua = (navigator.userAgent || "").toLowerCase(); const isTvUa = ua.includes("android tv") || @@ -521,6 +519,17 @@ const detectTvEnvironment = (): boolean => { const isAndroid = ua.includes("android"); const noTouch = !("ontouchstart" in window) && navigator.maxTouchPoints === 0; if (isAndroid && noTouch) return true; + + // On pure desktop (macOS / Windows / Desktop Linux), default to desktop PC mode + const isDesktop = + (ua.includes("windows nt") || ua.includes("macintosh") || (ua.includes("linux") && !ua.includes("android"))) && + !isTvUa; + if (isDesktop) { + return localStorage.getItem("has_tv_mode_manual") === "true"; + } + + const saved = localStorage.getItem("has_tv_mode"); + if (saved !== null) return saved === "true"; return false; }; @@ -540,10 +549,10 @@ export default function Cinema({ const lastFocusedCardRef = useRef(null); useEffect(() => { - if (isTvEnvironment && !isTvMode) { - setIsTvMode(true); + if (isTvEnvironment !== isTvMode) { + setIsTvMode(isTvEnvironment); } - }, [isTvEnvironment, isTvMode]); + }, [isTvEnvironment]); const [tvToast, setTvToast] = useState<{ text: string; icon?: string } | null>(null); const tvToastTimerRef = useRef(null); @@ -563,6 +572,7 @@ export default function Cinema({ setIsTvMode((prev) => { const next = !prev; localStorage.setItem("has_tv_mode", String(next)); + localStorage.setItem("has_tv_mode_manual", String(next)); showTvToast(next ? "ТВ-Режим включен (10-Foot UI)" : "ПК-Режим включен", "📺", 2500); playTvBeep(next ? 750 : 400, 0.05); return next; @@ -2250,40 +2260,18 @@ export default function Cinema({ // D-Pad Spatial Navigation & TV Remote Key Handler const handleRemoteKeyDown = useCallback( (e: KeyboardEvent) => { - // Auto-activate TV mode immediately on ANY remote control button press - const isRemoteKey = - [19, 20, 21, 22, 23, 4, 82, 85, 179].includes(e.keyCode) || - ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key); - if (isRemoteKey && !isTvMode) { - setIsTvMode(true); - localStorage.setItem("has_tv_mode", "true"); - } - const isInput = document.activeElement instanceof HTMLInputElement || document.activeElement instanceof HTMLTextAreaElement; - // Android TV / Remote key mappings - const isBack = - e.key === "Escape" || - e.key === "GoBack" || - e.key === "BrowserBack" || - e.keyCode === 4 || - e.keyCode === 27; - const isOk = - e.key === "Enter" || - e.key === "Select" || - e.key === "Accept" || - e.keyCode === 13 || - e.keyCode === 23 || - e.keyCode === 66; - const isPlayPause = - e.key === "MediaPlayPause" || e.keyCode === 85 || e.keyCode === 179; - const isFastForward = e.key === "MediaFastForward" || e.keyCode === 90; - const isRewind = e.key === "MediaRewind" || e.keyCode === 89; + // Remote Menu key (ONLY in TV Mode, and NEVER for the 'R' key on PC keyboard!) + const isMenuKey = + !isInput && + (e.key === "Menu" || + e.key === "ContextMenu" || + (isTvMode && e.keyCode === 82 && e.key !== "r" && e.key !== "R" && e.code !== "KeyR")); - // Remote Menu key (Android TV remote Menu button / ContextMenu) - if (e.key === "Menu" || e.keyCode === 82 || e.key === "ContextMenu") { + if (isTvMode && isMenuKey) { e.preventDefault(); setIsTvNavOpen((prev) => { const next = !prev; @@ -2310,6 +2298,29 @@ export default function Cinema({ return; } + // Android TV / Remote key mappings + const isBack = + e.key === "Escape" || + e.key === "GoBack" || + e.key === "BrowserBack" || + (isTvMode && e.keyCode === 4) || + e.keyCode === 27; + const isOk = + e.key === "Enter" || + e.key === "Select" || + e.key === "Accept" || + e.keyCode === 13 || + (isTvMode && (e.keyCode === 23 || (e.keyCode === 66 && e.code !== "KeyB"))); + const isPlayPause = + e.key === "MediaPlayPause" || + (isTvMode && (e.keyCode === 85 || e.keyCode === 179) && e.code !== "KeyU"); + const isFastForward = + e.key === "MediaFastForward" || + (isTvMode && e.keyCode === 90 && e.code !== "KeyZ"); + const isRewind = + e.key === "MediaRewind" || + (isTvMode && e.keyCode === 89 && e.code !== "KeyY"); + // 1. In-player remote control if (playingEpisode !== null) { resetHudTimer(); diff --git a/src/pages/dashboard/Dashboard.tsx b/src/pages/dashboard/Dashboard.tsx index 38fe797..6d4e03d 100644 --- a/src/pages/dashboard/Dashboard.tsx +++ b/src/pages/dashboard/Dashboard.tsx @@ -55,6 +55,7 @@ interface DashboardProps { dbInfo: SafeDbInfo | null; dbStatus: DbStatus | null; onLogout: () => void; + isTvEnvironment?: boolean; } type NavTab = "cinema" | "home" | "users" | "settings" | "profile" | "help"; @@ -62,8 +63,6 @@ type NavTab = "cinema" | "home" | "users" | "settings" | "profile" | "help"; // Helper for Smart TV / Android TV detection const detectTvEnvironment = (): boolean => { if (typeof window === "undefined") return false; - const saved = localStorage.getItem("has_tv_mode"); - if (saved !== null) return saved === "true"; const ua = (navigator.userAgent || "").toLowerCase(); const isTvUa = ua.includes("android tv") || @@ -90,6 +89,17 @@ const detectTvEnvironment = (): boolean => { const isAndroid = ua.includes("android"); const noTouch = !("ontouchstart" in window) && navigator.maxTouchPoints === 0; if (isAndroid && noTouch) return true; + + // On pure desktop (macOS / Windows / Desktop Linux), default to desktop PC mode + const isDesktop = + (ua.includes("windows nt") || ua.includes("macintosh") || (ua.includes("linux") && !ua.includes("android"))) && + !isTvUa; + if (isDesktop) { + return localStorage.getItem("has_tv_mode_manual") === "true"; + } + + const saved = localStorage.getItem("has_tv_mode"); + if (saved !== null) return saved === "true"; return false; }; @@ -98,13 +108,23 @@ export default function Dashboard({ dbInfo, dbStatus, onLogout, + isTvEnvironment: propIsTv, }: DashboardProps) { const [currentUser, setCurrentUser] = useState(user); useEffect(() => { setCurrentUser(user); }, [user]); - const [isTvEnvironment, setIsTvEnvironment] = useState(detectTvEnvironment); + const [isTvEnvironment, setIsTvEnvironment] = useState(() => { + return propIsTv !== undefined ? propIsTv : detectTvEnvironment(); + }); + + useEffect(() => { + if (propIsTv !== undefined && propIsTv !== isTvEnvironment) { + setIsTvEnvironment(propIsTv); + } + }, [propIsTv, isTvEnvironment]); + const [activeTab, setActiveTab] = useState("cinema"); const [serverUsers, setServerUsers] = useState([]); const [loadingUsers, setLoadingUsers] = useState(false); @@ -113,21 +133,6 @@ export default function Dashboard({ }); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); - // Dynamic D-Pad Remote Control Key Listener (Auto-detects TV when user presses remote buttons) - useEffect(() => { - const handleRemoteDpadKey = (e: KeyboardEvent) => { - const isRemoteKey = - [19, 20, 21, 22, 23, 4, 82, 85, 179].includes(e.keyCode) || - ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key); - if (isRemoteKey && !isTvEnvironment) { - setIsTvEnvironment(true); - localStorage.setItem("has_tv_mode", "true"); - } - }; - window.addEventListener("keydown", handleRemoteDpadKey); - return () => window.removeEventListener("keydown", handleRemoteDpadKey); - }, [isTvEnvironment]); - const handleNavClick = (tab: NavTab) => { setActiveTab(tab); setMobileMenuOpen(false);