Merge branch 'develop' into feature/newarch

This commit is contained in:
Gauvain
2025-09-26 20:52:32 +02:00
committed by GitHub
93 changed files with 1304 additions and 763 deletions

View File

@@ -14,7 +14,7 @@ export type HapticFeedbackType =
| "error";
export const useHaptic = (feedbackType: HapticFeedbackType = "selection") => {
const [settings] = useSettings(null);
const { settings } = useSettings();
const isTv = Platform.isTV;
const isDisabled =
isTv ||

View File

@@ -14,7 +14,7 @@ import { useQueryClient } from "@tanstack/react-query";
import { t } from "i18next";
import { useCallback, useMemo } from "react";
import { toast } from "sonner-native";
import { defaultValues, Settings } from "@/utils/atoms/settings";
import { useSettings } from "@/utils/atoms/settings";
import type { RTRating } from "@/utils/jellyseerr/server/api/rating/rottentomatoes";
import {
IssueStatus,
@@ -40,6 +40,7 @@ import type { UserResultsResponse } from "@/utils/jellyseerr/server/interfaces/a
import type { MovieDetails } from "@/utils/jellyseerr/server/models/Movie";
import type {
CombinedCredit,
PersonCreditCast,
PersonDetails,
} from "@/utils/jellyseerr/server/models/Person";
import type {
@@ -386,7 +387,7 @@ export class JellyseerrApi {
`Jellyseerr response error\nerror: ${error.toString()}\nurl: ${error?.config?.url}`,
error.response?.data,
);
if (error.status === 403) {
if (error.response?.status === 403) {
clearJellyseerrStorageData();
}
return Promise.reject(error);
@@ -416,10 +417,8 @@ export class JellyseerrApi {
const jellyseerrUserAtom = atom(storage.get<JellyseerrUser>(JELLYSEERR_USER));
export const useJellyseerr = (
settings: Settings = defaultValues,
updateSettings: (update: Partial<Settings>) => void = () => {},
) => {
export const useJellyseerr = () => {
const { settings, updateSettings } = useSettings();
const [jellyseerrUser, setJellyseerrUser] = useAtom(jellyseerrUserAtom);
const queryClient = useQueryClient();
@@ -468,54 +467,52 @@ export const useJellyseerr = (
[jellyseerrApi],
);
const isJellyseerrResult = (
const isJellyseerrMovieOrTvResult = (
items: any | null | undefined,
): items is Results => {
): items is MovieResult | TvResult => {
return (
items &&
Object.hasOwn(items, "mediaType") &&
Object.values(MediaType).includes(items.mediaType as MediaType)
(items.mediaType === MediaType.MOVIE || items.mediaType === MediaType.TV)
);
};
const getTitle = (
item?: TvResult | TvDetails | MovieResult | MovieDetails,
item?: TvResult | TvDetails | MovieResult | MovieDetails | PersonCreditCast,
) => {
return isJellyseerrResult(item)
return isJellyseerrMovieOrTvResult(item)
? item.mediaType === MediaType.MOVIE
? item?.title
: item?.name
: item?.mediaInfo.mediaType === MediaType.MOVIE
: item?.mediaInfo?.mediaType === MediaType.MOVIE
? (item as MovieDetails)?.title
: (item as TvDetails)?.name;
};
const getYear = (
item?: TvResult | TvDetails | MovieResult | MovieDetails,
item?: TvResult | TvDetails | MovieResult | MovieDetails | PersonCreditCast,
) => {
return new Date(
(isJellyseerrResult(item)
(isJellyseerrMovieOrTvResult(item)
? item.mediaType === MediaType.MOVIE
? item?.releaseDate
: item?.firstAirDate
: item?.mediaInfo.mediaType === MediaType.MOVIE
: item?.mediaInfo?.mediaType === MediaType.MOVIE
? (item as MovieDetails)?.releaseDate
: (item as TvDetails)?.firstAirDate) || "",
)?.getFullYear?.();
};
const getMediaType = (
item?: TvResult | TvDetails | MovieResult | MovieDetails,
item?: TvResult | TvDetails | MovieResult | MovieDetails | PersonCreditCast,
): MediaType => {
return isJellyseerrResult(item)
? item.mediaType === "movie"
? MediaType.MOVIE
: MediaType.TV
return isJellyseerrMovieOrTvResult(item)
? (item.mediaType as MediaType)
: item?.mediaInfo?.mediaType;
};
const jellyseerrRegion = useMemo(
() => jellyseerrUser?.settings?.region || "US",
() => jellyseerrUser?.settings?.discoverRegion || "US",
[jellyseerrUser],
);
@@ -528,7 +525,7 @@ export const useJellyseerr = (
jellyseerrUser,
setJellyseerrUser,
clearAllJellyseerData,
isJellyseerrResult,
isJellyseerrMovieOrTvResult,
getTitle,
getYear,
getMediaType,

View File

@@ -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<boolean> {
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<boolean | null>(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 };
}

View File

@@ -1,4 +1,7 @@
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
import type {
BaseItemDto,
PlaybackProgressInfo,
} from "@jellyfin/sdk/lib/generated-client";
import { getPlaystateApi, getTvShowsApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query";
import { useAtomValue } from "jotai";
@@ -141,13 +144,10 @@ export const usePlaybackManager = ({
* @param positionTicks The current playback position in ticks.
*/
const reportPlaybackProgress = async (
itemId: string,
positionTicks: number,
metadata?: {
AudioStreamIndex: number;
SubtitleStreamIndex: number;
},
playbackProgressInfo: PlaybackProgressInfo,
) => {
const positionTicks = playbackProgressInfo.PositionTicks || 0;
const itemId = playbackProgressInfo.ItemId!;
const localItem = getDownloadedItemById(itemId);
// Handle local state update for downloaded items
@@ -192,14 +192,7 @@ export const usePlaybackManager = ({
if (isOnline && api) {
try {
await getPlaystateApi(api).reportPlaybackProgress({
playbackProgressInfo: {
ItemId: itemId,
PositionTicks: Math.floor(positionTicks),
...(metadata && { AudioStreamIndex: metadata.AudioStreamIndex }),
...(metadata && {
SubtitleStreamIndex: metadata.SubtitleStreamIndex,
}),
},
playbackProgressInfo,
});
} catch (error) {
console.error("Failed to report playback progress", error);