mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-01 19:48:28 +01:00
refactor: downloads to minimize prop drilling and improve layout and design (#1337)
Co-authored-by: Alex Kim <alexkim@Alexs-MacBook-Pro.local> Co-authored-by: Fredrik Burmester <fredrik.burmester@gmail.com> Co-authored-by: Simon-Eklundh <simon.eklundh@proton.me>
This commit is contained in:
86
hooks/useAppRouter.ts
Normal file
86
hooks/useAppRouter.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { useRouter } from "expo-router";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useOfflineMode } from "@/providers/OfflineModeProvider";
|
||||
|
||||
/**
|
||||
* Drop-in replacement for expo-router's useRouter that automatically
|
||||
* preserves offline state across navigation.
|
||||
*
|
||||
* - For object-form navigation, automatically adds offline=true when in offline context
|
||||
* - For string URLs, passes through unchanged (caller handles offline param)
|
||||
*
|
||||
* @example
|
||||
* import useRouter from "@/hooks/useAppRouter";
|
||||
*
|
||||
* const router = useRouter();
|
||||
* router.push({ pathname: "/items/page", params: { id: item.Id } }); // offline added automatically
|
||||
*/
|
||||
export function useAppRouter() {
|
||||
const router = useRouter();
|
||||
const isOffline = useOfflineMode();
|
||||
|
||||
const push = useCallback(
|
||||
(href: Parameters<typeof router.push>[0]) => {
|
||||
if (typeof href === "string") {
|
||||
router.push(href as any);
|
||||
} else {
|
||||
const callerParams = (href.params ?? {}) as Record<string, unknown>;
|
||||
const hasExplicitOffline = "offline" in callerParams;
|
||||
router.push({
|
||||
...href,
|
||||
params: {
|
||||
// Only add offline if caller hasn't explicitly set it
|
||||
...(isOffline && !hasExplicitOffline && { offline: "true" }),
|
||||
...callerParams,
|
||||
},
|
||||
} as any);
|
||||
}
|
||||
},
|
||||
[router, isOffline],
|
||||
);
|
||||
|
||||
const replace = useCallback(
|
||||
(href: Parameters<typeof router.replace>[0]) => {
|
||||
if (typeof href === "string") {
|
||||
router.replace(href as any);
|
||||
} else {
|
||||
const callerParams = (href.params ?? {}) as Record<string, unknown>;
|
||||
const hasExplicitOffline = "offline" in callerParams;
|
||||
router.replace({
|
||||
...href,
|
||||
params: {
|
||||
// Only add offline if caller hasn't explicitly set it
|
||||
...(isOffline && !hasExplicitOffline && { offline: "true" }),
|
||||
...callerParams,
|
||||
},
|
||||
} as any);
|
||||
}
|
||||
},
|
||||
[router, isOffline],
|
||||
);
|
||||
|
||||
const setParams = useCallback(
|
||||
(params: Parameters<typeof router.setParams>[0]) => {
|
||||
const callerParams = (params ?? {}) as Record<string, unknown>;
|
||||
const hasExplicitOffline = "offline" in callerParams;
|
||||
router.setParams({
|
||||
// Only add offline if caller hasn't explicitly set it
|
||||
...(isOffline && !hasExplicitOffline && { offline: "true" }),
|
||||
...callerParams,
|
||||
});
|
||||
},
|
||||
[router, isOffline],
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
...router,
|
||||
push,
|
||||
replace,
|
||||
setParams,
|
||||
}),
|
||||
[router, push, replace, setParams],
|
||||
);
|
||||
}
|
||||
|
||||
export default useAppRouter;
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import { useRouter } from "expo-router";
|
||||
import { useCallback } from "react";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { usePlaySettings } from "@/providers/PlaySettingsProvider";
|
||||
import { writeToLog } from "@/utils/log";
|
||||
|
||||
|
||||
@@ -36,12 +36,26 @@ export function useNetworkAwareQueryClient(): NetworkAwareQueryClient {
|
||||
);
|
||||
|
||||
return useMemo(() => {
|
||||
// Create a proxy-like object that inherits from queryClient
|
||||
// but overrides invalidateQueries
|
||||
const wrapped = Object.create(queryClient) as NetworkAwareQueryClient;
|
||||
wrapped.invalidateQueries = networkAwareInvalidate;
|
||||
wrapped.forceInvalidateQueries =
|
||||
queryClient.invalidateQueries.bind(queryClient);
|
||||
return wrapped;
|
||||
// Use a Proxy to wrap the queryClient and override invalidateQueries.
|
||||
// Object.create doesn't work because QueryClient uses private fields (#)
|
||||
// which can only be accessed on the exact instance they were defined on.
|
||||
const forceInvalidate = queryClient.invalidateQueries.bind(queryClient);
|
||||
|
||||
return new Proxy(queryClient, {
|
||||
get(target, prop) {
|
||||
if (prop === "invalidateQueries") {
|
||||
return networkAwareInvalidate;
|
||||
}
|
||||
if (prop === "forceInvalidateQueries") {
|
||||
return forceInvalidate;
|
||||
}
|
||||
const value = Reflect.get(target, prop, target);
|
||||
// Bind methods to the original target to preserve private field access
|
||||
if (typeof value === "function") {
|
||||
return value.bind(target);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
}) as NetworkAwareQueryClient;
|
||||
}, [queryClient, networkAwareInvalidate]);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
PlaybackProgressInfo,
|
||||
} from "@jellyfin/sdk/lib/generated-client";
|
||||
import { getPlaystateApi, getTvShowsApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { useMemo } from "react";
|
||||
import { useDownload } from "@/providers/DownloadProvider";
|
||||
@@ -69,6 +69,7 @@ export const usePlaybackManager = ({
|
||||
const api = useAtomValue(apiAtom);
|
||||
const user = useAtomValue(userAtom);
|
||||
const { isConnected } = useNetworkStatus();
|
||||
const queryClient = useQueryClient();
|
||||
const { getDownloadedItemById, updateDownloadedItem, getDownloadedItems } =
|
||||
useDownload();
|
||||
|
||||
@@ -186,6 +187,9 @@ export const usePlaybackManager = ({
|
||||
},
|
||||
},
|
||||
});
|
||||
// Force invalidate queries so they refetch from updated local database
|
||||
queryClient.invalidateQueries({ queryKey: ["item", itemId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["episodes"] });
|
||||
}
|
||||
|
||||
// Handle remote state update if online
|
||||
@@ -226,6 +230,9 @@ export const usePlaybackManager = ({
|
||||
},
|
||||
},
|
||||
});
|
||||
// Force invalidate queries so they refetch from updated local database
|
||||
queryClient.invalidateQueries({ queryKey: ["item", itemId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["episodes"] });
|
||||
}
|
||||
|
||||
// Handle remote state update if online
|
||||
@@ -268,6 +275,9 @@ export const usePlaybackManager = ({
|
||||
},
|
||||
},
|
||||
});
|
||||
// Force invalidate queries so they refetch from updated local database
|
||||
queryClient.invalidateQueries({ queryKey: ["item", itemId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["episodes"] });
|
||||
}
|
||||
|
||||
// Handle remote state update if online
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRouter } from "expo-router";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Alert } from "react-native";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { useWebSocketContext } from "@/providers/WebSocketProvider";
|
||||
|
||||
interface UseWebSocketProps {
|
||||
|
||||
Reference in New Issue
Block a user