fix: remove splashscreen provider and handle loading in jellyfinprovider

This commit is contained in:
Fredrik Burmester
2025-02-19 14:44:39 +01:00
parent 77aef8877e
commit a2f03908f6
10 changed files with 76 additions and 209 deletions

View File

@@ -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,7 +230,6 @@ export default function RootLayout() {
Appearance.setColorScheme("dark");
return (
<SplashScreenProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
<JotaiProvider>
<ActionSheetProvider>
@@ -234,7 +239,6 @@ export default function RootLayout() {
</ActionSheetProvider>
</JotaiProvider>
</GestureHandlerRootView>
</SplashScreenProvider>
);
}
@@ -303,16 +307,6 @@ function Layout() {
}, []);
}
const [loaded] = useFonts({
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
});
useSplashScreenLoading(!loaded);
if (!loaded) {
return null;
}
return (
<QueryClientProvider client={queryClient}>
<JobQueueProvider>
@@ -324,7 +318,7 @@ function Layout() {
<BottomSheetModalProvider>
<SystemBars style="light" hidden={false} />
<ThemeProvider value={DarkTheme}>
<Stack>
<Stack initialRouteName="(auth)/(tabs)">
<Stack.Screen
name="(auth)/(tabs)"
options={{

View File

@@ -237,7 +237,6 @@ const Login: React.FC = () => {
setCredentials({ ...credentials, username: text })
}
value={credentials.username}
autoFocus
secureTextEntry={false}
keyboardType="default"
returnKeyType="done"

View File

@@ -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",

View File

@@ -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 = () => {
</View>
);
// 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 (
<View className="justify-center items-center h-full">
<Loader />

View File

@@ -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 = () => {
</View>
);
// 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 (
<View className="justify-center items-center h-full">
<Loader />

View File

@@ -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",

Binary file not shown.

View File

@@ -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<Server[]> => {
@@ -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);
if (storedUser?.Id) {
setUser(storedUser);
}
return true;
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 (
<JellyfinContext.Provider value={contextValue}>
{/* don't render login page when loading and splash screen visible */}
{isLoadingOrFetching && splashScreenVisible ? undefined : children}
{children}
</JellyfinContext.Provider>
);
};
@@ -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 {

View File

@@ -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<SplashScreenContextValue | undefined>(
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 (
<SplashScreenContext.Provider value={contextValue}>
{children}
</SplashScreenContext.Provider>
);
};
/**
* 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;
}

View File

@@ -268,6 +268,8 @@ export const useSettings = () => {
const newSettings = { ..._settings, ...update };
setSettings(newSettings);
// @ts-expect-error
saveSettings(newSettings);
}
};