diff --git a/bun.lock b/bun.lock index afbf5d03..c511875f 100644 --- a/bun.lock +++ b/bun.lock @@ -65,7 +65,7 @@ "react-native-ios-utilities": "5.1.8", "react-native-mmkv": "2.12.2", "react-native-pager-view": "^6.9.1", - "react-native-reanimated": "~3.17.4", + "react-native-reanimated": "~3.19.1", "react-native-reanimated-carousel": "4.0.2", "react-native-safe-area-context": "5.4.0", "react-native-screens": "~4.11.1", @@ -1637,7 +1637,7 @@ "react-native-pager-view": ["react-native-pager-view@6.9.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-uUT0MMMbNtoSbxe9pRvdJJKEi9snjuJ3fXlZhG8F2vVMOBJVt/AFtqMPUHu9yMflmqOr08PewKzj9EPl/Yj+Gw=="], - "react-native-reanimated": ["react-native-reanimated@3.17.5", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", "@babel/plugin-transform-classes": "^7.0.0-0", "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", "@babel/plugin-transform-optional-chaining": "^7.0.0-0", "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", "@babel/plugin-transform-template-literals": "^7.0.0-0", "@babel/plugin-transform-unicode-regex": "^7.0.0-0", "@babel/preset-typescript": "^7.16.7", "convert-source-map": "^2.0.0", "invariant": "^2.2.4", "react-native-is-edge-to-edge": "1.1.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*" } }, "sha512-SxBK7wQfJ4UoWoJqQnmIC7ZjuNgVb9rcY5Xc67upXAFKftWg0rnkknTw6vgwnjRcvYThrjzUVti66XoZdDJGtw=="], + "react-native-reanimated": ["react-native-reanimated@3.19.1", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", "@babel/plugin-transform-classes": "^7.0.0-0", "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", "@babel/plugin-transform-optional-chaining": "^7.0.0-0", "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", "@babel/plugin-transform-template-literals": "^7.0.0-0", "@babel/plugin-transform-unicode-regex": "^7.0.0-0", "@babel/preset-typescript": "^7.16.7", "convert-source-map": "^2.0.0", "invariant": "^2.2.4", "react-native-is-edge-to-edge": "1.1.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*" } }, "sha512-ILL0FSNzSVIg6WuawrsMBvNxk2yJFiTUcahimXDAeNiE/09eagVUlHhYWXAAmH0umvAOafBaGjO7YfBhUrf5ZQ=="], "react-native-reanimated-carousel": ["react-native-reanimated-carousel@4.0.2", "", { "peerDependencies": { "react": ">=18.0.0", "react-native": ">=0.70.3", "react-native-gesture-handler": ">=2.9.0", "react-native-reanimated": ">=3.0.0" } }, "sha512-vNpCfPlFoOVKHd+oB7B0luoJswp+nyz0NdJD8+LCrf25JiNQXfM22RSJhLaksBHqk3fm8R4fKWPNcfy5w7wL1Q=="], diff --git a/components/settings/HomeIndex.tsx b/components/settings/HomeIndex.tsx index 57aac02c..77a46c1b 100644 --- a/components/settings/HomeIndex.tsx +++ b/components/settings/HomeIndex.tsx @@ -74,7 +74,12 @@ export const HomeIndex = () => { const { getDownloadedItems, cleanCacheDirectory } = useDownload(); const prevIsConnected = useRef(false); - const { isConnected, loading: retryLoading, retryCheck } = useNetworkStatus(); + const { + isConnected, + serverConnected, + loading: retryLoading, + retryCheck, + } = useNetworkStatus(); const invalidateCache = useInvalidatePlaybackProgressCache(); useEffect(() => { // Only invalidate cache when transitioning from offline to online @@ -358,13 +363,28 @@ export const HomeIndex = () => { const sections = settings?.home?.sections ? customSections : defaultSections; - if (isConnected === false) { + if (!isConnected || serverConnected !== true) { + let title: string; + let subtitle: string; + + if (!isConnected) { + // No network connection + title = t("home.no_internet"); + subtitle = t("home.no_internet_message"); + } else if (serverConnected === null) { + // Network is up, but server is being checked + title = t("home.checking_server_connection"); + subtitle = t("home.checking_server_connection_message"); + } else if (!serverConnected) { + // Network is up, but server is unreachable + title = t("home.server_unreachable"); + subtitle = t("home.server_unreachable_message"); + } return ( - {t("home.no_internet")} - - {t("home.no_internet_message")} - + {title} + {subtitle} + {!Platform.isTV && ( )} + diff --git a/hooks/useNetworkStatus.ts b/hooks/useNetworkStatus.ts index aac70ee4..5acee27e 100644 --- a/hooks/useNetworkStatus.ts +++ b/hooks/useNetworkStatus.ts @@ -1,30 +1,58 @@ import NetInfo from "@react-native-community/netinfo"; +import { useAtom } from "jotai"; import { useCallback, useEffect, useState } from "react"; +import { apiAtom } from "@/providers/JellyfinProvider"; + +async function checkApiReachable(basePath?: string): Promise { + if (!basePath) return false; + try { + const response = await fetch(basePath, { method: "HEAD" }); + return response.ok; + } catch { + return false; + } +} export function useNetworkStatus() { const [isConnected, setIsConnected] = useState(false); + const [serverConnected, setServerConnected] = useState(null); const [loading, setLoading] = useState(false); + const [api] = useAtom(apiAtom); + + const validateConnection = useCallback(async () => { + if (!api?.basePath) return false; + const reachable = await checkApiReachable(api.basePath); + setServerConnected(reachable); + return reachable; + }, [api?.basePath]); - // Manual check (optional) const retryCheck = useCallback(async () => { setLoading(true); - const state = await NetInfo.fetch(); - setIsConnected(!!state.isConnected && !!state.isInternetReachable); + await validateConnection(); setLoading(false); - }, []); + }, [validateConnection]); useEffect(() => { - const unsubscribe = NetInfo.addEventListener((state) => { - setIsConnected(!!state.isConnected && !!state.isInternetReachable); + const unsubscribe = NetInfo.addEventListener(async (state) => { + setIsConnected(!!state.isConnected); + if (state.isConnected) { + await validateConnection(); + } else { + setServerConnected(false); + } }); - // Initial state + // Initial check: wait for NetInfo first NetInfo.fetch().then((state) => { - setIsConnected(!!state.isConnected && !!state.isInternetReachable); + if (state.isConnected) { + validateConnection(); + } else { + setServerConnected(false); + } }); return () => unsubscribe(); - }, []); + }, [validateConnection]); - return { isConnected, loading, retryCheck }; + return { isConnected, serverConnected, loading, retryCheck }; } diff --git a/package.json b/package.json index 091ca20a..22b710d1 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "react-native-ios-utilities": "5.1.8", "react-native-mmkv": "2.12.2", "react-native-pager-view": "^6.9.1", - "react-native-reanimated": "~3.17.4", + "react-native-reanimated": "~3.19.1", "react-native-reanimated-carousel": "4.0.2", "react-native-safe-area-context": "5.4.0", "react-native-screens": "~4.11.1", diff --git a/providers/JellyfinProvider.tsx b/providers/JellyfinProvider.tsx index 4c99e0be..6340bcce 100644 --- a/providers/JellyfinProvider.tsx +++ b/providers/JellyfinProvider.tsx @@ -374,7 +374,7 @@ function useProtectedRoute(user: UserDto | null, loaded = false) { useEffect(() => { if (loaded === false) return; - const inAuthGroup = segments[0] === "(auth)"; + const inAuthGroup = segments.length > 1 && segments[0] === "(auth)"; if (!user?.Id && inAuthGroup) { console.log("Redirected to login"); diff --git a/translations/en.json b/translations/en.json index b1ccbb80..6373fc79 100644 --- a/translations/en.json +++ b/translations/en.json @@ -35,10 +35,15 @@ "servers": "Servers" }, "home": { + "checking_server_connection": "Checking server connection...", "no_internet": "No Internet", "no_items": "No items", "no_internet_message": "No worries, you can still watch\ndownloaded content.", + "checking_server_connection_message": "Checking connection to server", "go_to_downloads": "Go to downloads", + "retry": "Retry", + "server_unreachable": "Server Unreachable", + "server_unreachable_message": "Could not reach the server.\nPlease check your network connection.", "oops": "Oops!", "error_message": "Something went wrong.\nPlease log out and in again.", "continue_watching": "Continue Watching",