diff --git a/app/_layout.tsx b/app/_layout.tsx
index 55652930..dddfe2a1 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -10,10 +10,6 @@ import {
} from "@/providers/JellyfinProvider";
import { JobQueueProvider } from "@/providers/JobQueueProvider";
import { PlaySettingsProvider } from "@/providers/PlaySettingsProvider";
-import {
- SplashScreenProvider,
- useSplashScreenLoading,
-} from "@/providers/SplashScreenProvider";
import { WebSocketProvider } from "@/providers/WebSocketProvider";
import { Settings, useSettings } from "@/utils/atoms/settings";
import { BACKGROUND_FETCH_TASK } from "@/utils/background-tasks";
@@ -36,6 +32,7 @@ import { useFonts } from "expo-font";
import { useKeepAwake } from "expo-keep-awake";
const Notifications = !Platform.isTV ? require("expo-notifications") : null;
import { router, Stack } from "expo-router";
+import * as SplashScreen from "expo-splash-screen";
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
const TaskManager = !Platform.isTV ? require("expo-task-manager") : null;
import { getLocales } from "expo-localization";
@@ -58,6 +55,15 @@ if (!Platform.isTV) {
});
}
+// Keep the splash screen visible while we fetch resources
+SplashScreen.preventAutoHideAsync();
+
+// Set the animation options. This is optional.
+SplashScreen.setOptions({
+ duration: 500,
+ fade: true,
+});
+
function useNotificationObserver() {
if (Platform.isTV) return;
@@ -224,17 +230,15 @@ export default function RootLayout() {
Appearance.setColorScheme("dark");
return (
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
}
@@ -303,16 +307,6 @@ function Layout() {
}, []);
}
- const [loaded] = useFonts({
- SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
- });
-
- useSplashScreenLoading(!loaded);
-
- if (!loaded) {
- return null;
- }
-
return (
@@ -324,7 +318,7 @@ function Layout() {
-
+
{
setCredentials({ ...credentials, username: text })
}
value={credentials.username}
- autoFocus
secureTextEntry={false}
keyboardType="default"
returnKeyType="done"
diff --git a/bun.lock b/bun.lock
index 25e70592..33b28594 100644
--- a/bun.lock
+++ b/bun.lock
@@ -44,7 +44,7 @@
"expo-router": "~4.0.17",
"expo-screen-orientation": "~8.0.4",
"expo-sensors": "~14.0.2",
- "expo-splash-screen": "~0.29.21",
+ "expo-splash-screen": "~0.29.22",
"expo-status-bar": "~2.0.1",
"expo-system-ui": "~4.0.8",
"expo-task-manager": "~12.0.5",
diff --git a/components/settings/SettingsIndex.tsx b/components/settings/SettingsIndex.tsx
index e9213952..964bee31 100644
--- a/components/settings/SettingsIndex.tsx
+++ b/components/settings/SettingsIndex.tsx
@@ -8,10 +8,6 @@ import { Colors } from "@/constants/Colors";
import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
import { useDownload } from "@/providers/DownloadProvider";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
-import {
- useSplashScreenLoading,
- useSplashScreenVisible,
-} from "@/providers/SplashScreenProvider";
import { useSettings } from "@/utils/atoms/settings";
import { Feather, Ionicons } from "@expo/vector-icons";
import { Api } from "@jellyfin/sdk";
@@ -156,10 +152,6 @@ export const SettingsIndex = () => {
staleTime: 60 * 1000,
});
- // show splash screen until query loaded
- useSplashScreenLoading(l1);
- const splashScreenVisible = useSplashScreenVisible();
-
const userViews = useMemo(
() => data?.filter((l) => !settings?.hiddenLibraries?.includes(l.Id!)),
[data, settings?.hiddenLibraries]
@@ -414,9 +406,7 @@ export const SettingsIndex = () => {
);
- // this spinner should only show up, when user navigates here
- // on launch the splash screen is used for loading
- if (l1 && !splashScreenVisible)
+ if (l1)
return (
diff --git a/components/settings/SettingsIndex.tv.tsx b/components/settings/SettingsIndex.tv.tsx
index 905baad3..afde6937 100644
--- a/components/settings/SettingsIndex.tv.tsx
+++ b/components/settings/SettingsIndex.tv.tsx
@@ -6,10 +6,6 @@ import { Loader } from "@/components/Loader";
import { MediaListSection } from "@/components/medialists/MediaListSection";
import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
-import {
- useSplashScreenLoading,
- useSplashScreenVisible,
-} from "@/providers/SplashScreenProvider";
import { useSettings } from "@/utils/atoms/settings";
import { Ionicons } from "@expo/vector-icons";
import { Api } from "@jellyfin/sdk";
@@ -124,10 +120,6 @@ export const SettingsIndex = () => {
staleTime: 60 * 1000,
});
- // show splash screen until query loaded
- useSplashScreenLoading(l1);
- const splashScreenVisible = useSplashScreenVisible();
-
const userViews = useMemo(
() => data?.filter((l) => !settings?.hiddenLibraries?.includes(l.Id!)),
[data, settings?.hiddenLibraries]
@@ -382,9 +374,7 @@ export const SettingsIndex = () => {
);
- // this spinner should only show up, when user navigates here
- // on launch the splash screen is used for loading
- if (l1 && !splashScreenVisible)
+ if (l1)
return (
diff --git a/package.json b/package.json
index ee7d0bba..a04b6b4b 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
"expo-router": "~4.0.17",
"expo-screen-orientation": "~8.0.4",
"expo-sensors": "~14.0.2",
- "expo-splash-screen": "~0.29.21",
+ "expo-splash-screen": "~0.29.22",
"expo-status-bar": "~2.0.1",
"expo-system-ui": "~4.0.8",
"expo-task-manager": "~12.0.5",
diff --git a/providers/.JellyfinProvider.tsx.swp b/providers/.JellyfinProvider.tsx.swp
new file mode 100644
index 00000000..18d4a7ed
Binary files /dev/null and b/providers/.JellyfinProvider.tsx.swp differ
diff --git a/providers/JellyfinProvider.tsx b/providers/JellyfinProvider.tsx
index 66d1966e..463cc3f6 100644
--- a/providers/JellyfinProvider.tsx
+++ b/providers/JellyfinProvider.tsx
@@ -1,5 +1,7 @@
import "@/augmentations";
import { useInterval } from "@/hooks/useInterval";
+import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr";
+import { useSettings } from "@/utils/atoms/settings";
import { storage } from "@/utils/mmkv";
import { Api, Jellyfin } from "@jellyfin/sdk";
import { UserDto } from "@jellyfin/sdk/lib/generated-client/models";
@@ -7,6 +9,7 @@ import { getUserApi } from "@jellyfin/sdk/lib/utils/api";
import { useMutation, useQuery } from "@tanstack/react-query";
import axios, { AxiosError } from "axios";
import { router, useSegments } from "expo-router";
+import * as SplashScreen from "expo-splash-screen";
import { atom, useAtom } from "jotai";
import React, {
createContext,
@@ -17,16 +20,10 @@ import React, {
useMemo,
useState,
} from "react";
-import { Platform } from "react-native";
-import uuid from "react-native-uuid";
-import { getDeviceName } from "react-native-device-info";
import { useTranslation } from "react-i18next";
-import { useSettings } from "@/utils/atoms/settings";
-import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr";
-import {
- useSplashScreenLoading,
- useSplashScreenVisible,
-} from "./SplashScreenProvider";
+import { Platform } from "react-native";
+import { getDeviceName } from "react-native-device-info";
+import uuid from "react-native-uuid";
interface Server {
address: string;
@@ -88,22 +85,6 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
] = useSettings();
const { clearAllJellyseerData, setJellyseerrUser } = useJellyseerr();
- useQuery({
- queryKey: ["user", api],
- queryFn: async () => {
- if (!api) return null;
- const response = await getUserApi(api).getCurrentUser();
- if (response.data) setUser(response.data);
- return user;
- },
- enabled: !!api,
- refetchOnWindowFocus: true,
- refetchInterval: 1000 * 60,
- refetchIntervalInBackground: true,
- refetchOnMount: true,
- refetchOnReconnect: true,
- });
-
const headers = useMemo(() => {
if (!deviceId) return {};
return {
@@ -179,14 +160,13 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
}
}, [api, secret, headers]);
- useInterval(pollQuickConnect, isPolling ? 1000 : null);
-
useEffect(() => {
(async () => {
await refreshStreamyfinPluginSettings();
})();
}, []);
+ useInterval(pollQuickConnect, isPolling ? 1000 : null);
useInterval(refreshStreamyfinPluginSettings, 60 * 5 * 1000); // 5 min
const discoverServers = async (url: string): Promise => {
@@ -312,33 +292,44 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
},
});
- const { isLoading, isFetching } = useQuery({
- queryKey: [
- "initializeJellyfin",
- user?.Id,
- api?.basePath,
- jellyfin?.clientInfo,
- ],
- queryFn: async () => {
+ const [loaded, setLoaded] = useState(false);
+ const [initialLoaded, setInitialLoaded] = useState(false);
+
+ useEffect(() => {
+ if (initialLoaded) {
+ setLoaded(true);
+ }
+ }, [initialLoaded]);
+
+ useEffect(() => {
+ const initializeJellyfin = async () => {
+ if (!jellyfin) return;
+
try {
const token = getTokenFromStorage();
const serverUrl = getServerUrlFromStorage();
- const user = getUserFromStorage();
- if (serverUrl && token && user?.Id && jellyfin) {
+ const storedUser = getUserFromStorage();
+
+ if (serverUrl && token) {
const apiInstance = jellyfin.createApi(serverUrl, token);
setApi(apiInstance);
- setUser(user);
- }
- return true;
+ if (storedUser?.Id) {
+ setUser(storedUser);
+ }
+
+ const response = await getUserApi(apiInstance).getCurrentUser();
+ setUser(response.data);
+ }
} catch (e) {
console.error(e);
- return false;
+ } finally {
+ setInitialLoaded(true);
}
- },
- staleTime: 0,
- enabled: !user?.Id || !api || !jellyfin,
- });
+ };
+
+ initializeJellyfin();
+ }, [jellyfin]);
const contextValue: JellyfinContextValue = {
discoverServers,
@@ -350,17 +341,17 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
initiateQuickConnect,
};
- let isLoadingOrFetching = isLoading || isFetching;
- useProtectedRoute(user, isLoadingOrFetching);
+ useEffect(() => {
+ if (loaded) {
+ SplashScreen.hideAsync();
+ }
+ }, [loaded]);
- // show splash screen until everything loaded
- useSplashScreenLoading(isLoadingOrFetching);
- const splashScreenVisible = useSplashScreenVisible();
+ useProtectedRoute(user, loaded);
return (
- {/* don't render login page when loading and splash screen visible */}
- {isLoadingOrFetching && splashScreenVisible ? undefined : children}
+ {children}
);
};
@@ -372,20 +363,24 @@ export const useJellyfin = (): JellyfinContextValue => {
return context;
};
-function useProtectedRoute(user: UserDto | null, loading = false) {
+function useProtectedRoute(user: UserDto | null, loaded = false) {
const segments = useSegments();
useEffect(() => {
- if (loading) return;
+ if (loaded === false) return;
+
+ console.log("Loaded", user);
const inAuthGroup = segments[0] === "(auth)";
if (!user?.Id && inAuthGroup) {
+ console.log("Redirected to login");
router.replace("/login");
} else if (user?.Id && !inAuthGroup) {
+ console.log("Redirected to home");
router.replace("/(auth)/(tabs)/(home)/");
}
- }, [user, segments, loading]);
+ }, [user, segments, loaded]);
}
export function getTokenFromStorage(): string | null {
diff --git a/providers/SplashScreenProvider.tsx b/providers/SplashScreenProvider.tsx
deleted file mode 100644
index 2265dd00..00000000
--- a/providers/SplashScreenProvider.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import {
- createContext,
- ReactNode,
- useContext,
- useEffect,
- useState,
- useRef,
-} from "react";
-import * as SplashScreen from "expo-splash-screen";
-
-type SplashScreenContextValue = {
- registerLoadingComponent: () => () => void;
- splashScreenVisible: boolean;
-};
-
-const SplashScreenContext = createContext(
- undefined
-);
-
-// Prevent splash screen from auto-hiding
-void SplashScreen.preventAutoHideAsync();
-
-export const SplashScreenProvider: React.FC<{ children: ReactNode }> = ({
- children,
-}) => {
- const [splashScreenVisible, setSplashScreenVisible] = useState(true);
- const loadingComponentsCount = useRef(0);
- const isHidingRef = useRef(false);
-
- const hideScreenIfNoLoadingComponents = async () => {
- if (loadingComponentsCount.current === 0 && !isHidingRef.current) {
- try {
- isHidingRef.current = true;
- await SplashScreen.hideAsync();
- setSplashScreenVisible(false);
- } catch (error) {
- console.warn("Failed to hide splash screen:", error);
- } finally {
- isHidingRef.current = false;
- }
- }
- };
-
- const registerLoadingComponent = () => {
- loadingComponentsCount.current += 1;
-
- return () => {
- loadingComponentsCount.current -= 1;
- void hideScreenIfNoLoadingComponents();
- };
- };
-
- const contextValue: SplashScreenContextValue = {
- registerLoadingComponent,
- splashScreenVisible,
- };
-
- return (
-
- {children}
-
- );
-};
-
-/**
- * Show the Splash Screen until component is ready to be displayed.
- *
- * @param isLoading The loading state of the component
- *
- * ## Usage
- * ```
- * const isLoading = loadSomething()
- * useSplashScreenLoading(isLoading) // splash screen visible until isLoading is false
- * ```
- */
-export function useSplashScreenLoading(isLoading: boolean) {
- const context = useContext(SplashScreenContext);
- if (!context) {
- throw new Error(
- "useSplashScreenLoading must be used within a SplashScreenProvider"
- );
- }
-
- useEffect(() => {
- if (isLoading) {
- return context.registerLoadingComponent();
- }
- }, [isLoading]);
-}
-
-/**
- * Get the visibility of the Splash Screen.
- * @returns the visibility of the Splash Screen
- */
-export function useSplashScreenVisible() {
- const context = useContext(SplashScreenContext);
- if (!context) {
- throw new Error(
- "useSplashScreenVisible must be used within a SplashScreenProvider"
- );
- }
- return context.splashScreenVisible;
-}
diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts
index 35688e03..52ccf719 100644
--- a/utils/atoms/settings.ts
+++ b/utils/atoms/settings.ts
@@ -268,6 +268,8 @@ export const useSettings = () => {
const newSettings = { ..._settings, ...update };
setSettings(newSettings);
+
+ // @ts-expect-error
saveSettings(newSettings);
}
};