mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 23:59:08 +00:00
fix: resolve type issues and improve component reliability (#1078)
Some checks failed
🤖 Android APK Build (Phone + TV) / 🏗️ Build Android APK (phone) (push) Has been cancelled
🤖 Android APK Build (Phone + TV) / 🏗️ Build Android APK (tv) (push) Has been cancelled
🤖 iOS IPA Build (Phone + TV) / 🏗️ Build iOS IPA (phone) (push) Has been cancelled
🔒 Lockfile Consistency Check / 🔍 Check bun.lock and package.json consistency (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (actions) (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (javascript-typescript) (push) Has been cancelled
🏷️🔀Merge Conflict Labeler / 🏷️ Labeling Merge Conflicts (push) Has been cancelled
🚦 Security & Quality Gate / 📝 Validate PR Title (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Vulnerable Dependencies (push) Has been cancelled
🚦 Security & Quality Gate / 🚑 Expo Doctor Check (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (check) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (format) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (lint) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (typecheck) (push) Has been cancelled
🕒 Handle Stale Issues / 🗑️ Cleanup Stale Issues (push) Has been cancelled
Some checks failed
🤖 Android APK Build (Phone + TV) / 🏗️ Build Android APK (phone) (push) Has been cancelled
🤖 Android APK Build (Phone + TV) / 🏗️ Build Android APK (tv) (push) Has been cancelled
🤖 iOS IPA Build (Phone + TV) / 🏗️ Build iOS IPA (phone) (push) Has been cancelled
🔒 Lockfile Consistency Check / 🔍 Check bun.lock and package.json consistency (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (actions) (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (javascript-typescript) (push) Has been cancelled
🏷️🔀Merge Conflict Labeler / 🏷️ Labeling Merge Conflicts (push) Has been cancelled
🚦 Security & Quality Gate / 📝 Validate PR Title (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Vulnerable Dependencies (push) Has been cancelled
🚦 Security & Quality Gate / 🚑 Expo Doctor Check (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (check) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (format) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (lint) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (typecheck) (push) Has been cancelled
🕒 Handle Stale Issues / 🗑️ Cleanup Stale Issues (push) Has been cancelled
This commit is contained in:
@@ -139,7 +139,15 @@ const Page: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
requestMedia(mediaTitle, body, refetch);
|
requestMedia(mediaTitle, body, refetch);
|
||||||
}, [details, result, requestMedia, hasAdvancedRequestPermission]);
|
}, [
|
||||||
|
details,
|
||||||
|
result,
|
||||||
|
requestMedia,
|
||||||
|
hasAdvancedRequestPermission,
|
||||||
|
mediaTitle,
|
||||||
|
refetch,
|
||||||
|
mediaType,
|
||||||
|
]);
|
||||||
|
|
||||||
const isAnime = useMemo(
|
const isAnime = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -277,12 +285,16 @@ const Page: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
className='flex-1 bg-purple-600/50 border-purple-400 ring-purple-400 text-purple-100'
|
className='flex-1 bg-purple-600/50 border-purple-400 ring-purple-400 text-purple-100'
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
const url =
|
router.push({
|
||||||
mediaType === MediaType.MOVIE
|
pathname:
|
||||||
? `/(auth)/(tabs)/(search)/items/page?id=${details?.mediaInfo.jellyfinMediaId}`
|
mediaType === MediaType.MOVIE
|
||||||
: `/(auth)/(tabs)/(search)/series/${details?.mediaInfo.jellyfinMediaId}`;
|
? "/(auth)/(tabs)/(search)/items/page"
|
||||||
// @ts-expect-error
|
: "/(auth)/(tabs)/(search)/series/[id]",
|
||||||
router.push(url);
|
params:
|
||||||
|
mediaType === MediaType.MOVIE
|
||||||
|
? { id: details?.mediaInfo.jellyfinMediaId }
|
||||||
|
: { id: details?.mediaInfo.jellyfinMediaId },
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
iconLeft={
|
iconLeft={
|
||||||
<Ionicons name='play-outline' size={20} color='white' />
|
<Ionicons name='play-outline' size={20} color='white' />
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const TouchableJellyseerrRouter: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
const segments = useSegments();
|
const segments = useSegments();
|
||||||
const { jellyseerrApi, jellyseerrUser, requestMedia } = useJellyseerr();
|
const { jellyseerrApi, jellyseerrUser, requestMedia } = useJellyseerr();
|
||||||
|
|
||||||
const from = segments[2] || "(home)";
|
const from = (segments as string[])[2] || "(home)";
|
||||||
|
|
||||||
const autoApprove = useMemo(() => {
|
const autoApprove = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -43,6 +43,48 @@ export const itemRouter = (item: BaseItemDto, from: string) => {
|
|||||||
return `/(auth)/(tabs)/${from}/items/page?id=${item.Id}`;
|
return `/(auth)/(tabs)/${from}/items/page?id=${item.Id}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getItemNavigation = (item: BaseItemDto, _from: string) => {
|
||||||
|
if ("CollectionType" in item && item.CollectionType === "livetv") {
|
||||||
|
return {
|
||||||
|
pathname: "/livetv" as const,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Type === "Series") {
|
||||||
|
return {
|
||||||
|
pathname: "/series/[id]" as const,
|
||||||
|
params: { id: item.Id! },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Type === "Person") {
|
||||||
|
return {
|
||||||
|
pathname: "/persons/[personId]" as const,
|
||||||
|
params: { personId: item.Id! },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Type === "BoxSet" || item.Type === "UserView") {
|
||||||
|
return {
|
||||||
|
pathname: "/collections/[collectionId]" as const,
|
||||||
|
params: { collectionId: item.Id! },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Type === "CollectionFolder" || item.Type === "Playlist") {
|
||||||
|
return {
|
||||||
|
pathname: "/[libraryId]" as const,
|
||||||
|
params: { libraryId: item.Id! },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default case - items page
|
||||||
|
return {
|
||||||
|
pathname: "/items/page" as const,
|
||||||
|
params: { id: item.Id! },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const TouchableItemRouter: React.FC<PropsWithChildren<Props>> = ({
|
export const TouchableItemRouter: React.FC<PropsWithChildren<Props>> = ({
|
||||||
item,
|
item,
|
||||||
isOffline = false,
|
isOffline = false,
|
||||||
@@ -55,7 +97,7 @@ export const TouchableItemRouter: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
const markAsPlayedStatus = useMarkAsPlayed([item]);
|
const markAsPlayedStatus = useMarkAsPlayed([item]);
|
||||||
const { isFavorite, toggleFavorite } = useFavorite(item);
|
const { isFavorite, toggleFavorite } = useFavorite(item);
|
||||||
|
|
||||||
const from = segments[2] || "(home)";
|
const from = (segments as string[])[2] || "(home)";
|
||||||
|
|
||||||
const showActionSheet = useCallback(() => {
|
const showActionSheet = useCallback(() => {
|
||||||
if (
|
if (
|
||||||
@@ -101,12 +143,15 @@ export const TouchableItemRouter: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onLongPress={showActionSheet}
|
onLongPress={showActionSheet}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
let url = itemRouter(item, from);
|
|
||||||
if (isOffline) {
|
if (isOffline) {
|
||||||
url += `&offline=true`;
|
// For offline mode, we still need to use query params
|
||||||
|
const url = `${itemRouter(item, from)}&offline=true`;
|
||||||
|
router.push(url as any);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// @ts-expect-error
|
|
||||||
router.push(url);
|
const navigation = getItemNavigation(item, from);
|
||||||
|
router.push(navigation as any);
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
|||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
||||||
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
|
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
|
||||||
import { itemRouter } from "../common/TouchableItemRouter";
|
import { getItemNavigation } from "../common/TouchableItemRouter";
|
||||||
|
|
||||||
interface Props extends ViewProps {}
|
interface Props extends ViewProps {}
|
||||||
|
|
||||||
@@ -146,16 +146,15 @@ const RenderItem: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
|||||||
}, [item]);
|
}, [item]);
|
||||||
|
|
||||||
const segments = useSegments();
|
const segments = useSegments();
|
||||||
const from = segments[2] || "(home)";
|
const from = (segments as string[])[2] || "(home)";
|
||||||
|
|
||||||
const opacity = useSharedValue(1);
|
const opacity = useSharedValue(1);
|
||||||
|
|
||||||
const handleRoute = useCallback(() => {
|
const handleRoute = useCallback(() => {
|
||||||
if (!from) return;
|
if (!from) return;
|
||||||
const url = itemRouter(item, from);
|
|
||||||
lightHapticFeedback();
|
lightHapticFeedback();
|
||||||
// @ts-expect-error
|
const navigation = getItemNavigation(item, from);
|
||||||
if (url) router.push(url);
|
router.push(navigation as any);
|
||||||
}, [item, from]);
|
}, [item, from]);
|
||||||
|
|
||||||
const tap = Gesture.Tap()
|
const tap = Gesture.Tap()
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
import { BottomSheetTextInput } from "@gorhom/bottom-sheet";
|
import { BottomSheetTextInput } from "@gorhom/bottom-sheet";
|
||||||
import React, { useCallback, useImperativeHandle, useRef } from "react";
|
import React, { useCallback, useImperativeHandle, useRef } from "react";
|
||||||
import { StyleSheet, Text, type TextInputProps, View } from "react-native";
|
import {
|
||||||
|
type StyleProp,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
type TextInputProps,
|
||||||
|
View,
|
||||||
|
type ViewStyle,
|
||||||
|
} from "react-native";
|
||||||
|
|
||||||
interface PinInputProps extends Omit<TextInputProps, "value" | "onChangeText"> {
|
interface PinInputProps
|
||||||
|
extends Omit<TextInputProps, "value" | "onChangeText" | "style"> {
|
||||||
value: string;
|
value: string;
|
||||||
onChangeText: (text: string) => void;
|
onChangeText: (text: string) => void;
|
||||||
length?: number;
|
length?: number;
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
|
style?: StyleProp<ViewStyle>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PinInputRef {
|
export interface PinInputRef {
|
||||||
@@ -118,6 +127,5 @@ const styles = StyleSheet.create({
|
|||||||
width: 2,
|
width: 2,
|
||||||
height: 24,
|
height: 24,
|
||||||
backgroundColor: "#6366F1",
|
backgroundColor: "#6366F1",
|
||||||
animation: "blink 1s infinite",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const PersonPoster: React.FC<Props & ViewProps> = ({
|
|||||||
const { jellyseerrApi } = useJellyseerr();
|
const { jellyseerrApi } = useJellyseerr();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const segments = useSegments();
|
const segments = useSegments();
|
||||||
const from = segments[2] || "(home)";
|
const from = (segments as string[])[2] || "(home)";
|
||||||
|
|
||||||
if (from === "(home)" || from === "(search)" || from === "(libraries)")
|
if (from === "(home)" || from === "(search)" || from === "(libraries)")
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -16,13 +16,12 @@ const CompanySlide: React.FC<
|
|||||||
> = ({ slide, data, ...props }) => {
|
> = ({ slide, data, ...props }) => {
|
||||||
const segments = useSegments();
|
const segments = useSegments();
|
||||||
const { jellyseerrApi } = useJellyseerr();
|
const { jellyseerrApi } = useJellyseerr();
|
||||||
const from = segments[2] || "(home)";
|
const from = (segments as string[])[2] || "(home)";
|
||||||
|
|
||||||
const navigate = useCallback(
|
const navigate = useCallback(
|
||||||
({ id, image, name }: Network | Studio) =>
|
({ id, image, name }: Network | Studio) =>
|
||||||
router.push({
|
router.push({
|
||||||
// @ts-expect-error - Dynamic pathname for jellyseerr routing
|
pathname: `/(auth)/(tabs)/${from}/jellyseerr/company/${id}` as any,
|
||||||
pathname: `/(auth)/(tabs)/${from}/jellyseerr/company/${id}`,
|
|
||||||
params: { id, image, name, type: slide.type },
|
params: { id, image, name, type: slide.type },
|
||||||
}),
|
}),
|
||||||
[slide],
|
[slide],
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ import { genreColorMap } from "@/utils/jellyseerr/src/components/Discover/consta
|
|||||||
const GenreSlide: React.FC<SlideProps & ViewProps> = ({ slide, ...props }) => {
|
const GenreSlide: React.FC<SlideProps & ViewProps> = ({ slide, ...props }) => {
|
||||||
const segments = useSegments();
|
const segments = useSegments();
|
||||||
const { jellyseerrApi } = useJellyseerr();
|
const { jellyseerrApi } = useJellyseerr();
|
||||||
const from = segments[2] || "(home)";
|
const from = (segments as string[])[2] || "(home)";
|
||||||
|
|
||||||
const navigate = useCallback(
|
const navigate = useCallback(
|
||||||
(genre: GenreSliderItem) =>
|
(genre: GenreSliderItem) =>
|
||||||
router.push({
|
router.push({
|
||||||
// @ts-expect-error - Dynamic pathname for jellyseerr routing
|
pathname: `/(auth)/(tabs)/${from}/jellyseerr/genre/${genre.id}` as any,
|
||||||
pathname: `/(auth)/(tabs)/${from}/jellyseerr/genre/${genre.id}`,
|
|
||||||
params: { type: slide.type, name: genre.name },
|
params: { type: slide.type, name: genre.name },
|
||||||
}),
|
}),
|
||||||
[slide],
|
[slide],
|
||||||
|
|||||||
@@ -8,7 +8,14 @@ import { MediaType } from "@/utils/jellyseerr/server/constants/media";
|
|||||||
import type MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest";
|
import type MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest";
|
||||||
import type { NonFunctionProperties } from "@/utils/jellyseerr/server/interfaces/api/common";
|
import type { NonFunctionProperties } from "@/utils/jellyseerr/server/interfaces/api/common";
|
||||||
|
|
||||||
const RequestCard: React.FC<{ request: MediaRequest }> = ({ request }) => {
|
type ExtendedMediaRequest = NonFunctionProperties<MediaRequest> & {
|
||||||
|
profileName: string;
|
||||||
|
canRemove: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const RequestCard: React.FC<{ request: ExtendedMediaRequest }> = ({
|
||||||
|
request,
|
||||||
|
}) => {
|
||||||
const { jellyseerrApi } = useJellyseerr();
|
const { jellyseerrApi } = useJellyseerr();
|
||||||
|
|
||||||
const { data: details } = useQuery({
|
const { data: details } = useQuery({
|
||||||
@@ -67,9 +74,15 @@ const RecentRequestsSlide: React.FC<SlideProps & ViewProps> = ({
|
|||||||
<Slide
|
<Slide
|
||||||
{...props}
|
{...props}
|
||||||
slide={slide}
|
slide={slide}
|
||||||
data={requests.results}
|
data={
|
||||||
|
requests.results.map((item) => ({
|
||||||
|
...item,
|
||||||
|
profileName: item.profileName ?? "Unknown",
|
||||||
|
canRemove: Boolean(item.canRemove),
|
||||||
|
})) as ExtendedMediaRequest[]
|
||||||
|
}
|
||||||
keyExtractor={(item) => item.id.toString()}
|
keyExtractor={(item) => item.id.toString()}
|
||||||
renderItem={(item: NonFunctionProperties<MediaRequest>) => (
|
renderItem={(item: ExtendedMediaRequest) => (
|
||||||
<RequestCard request={item} />
|
<RequestCard request={item} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { apiAtom } from "@/providers/JellyfinProvider";
|
|||||||
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
|
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
|
||||||
import { HorizontalScroll } from "../common/HorizontalScroll";
|
import { HorizontalScroll } from "../common/HorizontalScroll";
|
||||||
import { Text } from "../common/Text";
|
import { Text } from "../common/Text";
|
||||||
import { itemRouter } from "../common/TouchableItemRouter";
|
|
||||||
import Poster from "../posters/Poster";
|
import Poster from "../posters/Poster";
|
||||||
|
|
||||||
interface Props extends ViewProps {
|
interface Props extends ViewProps {
|
||||||
@@ -24,7 +23,7 @@ export const CastAndCrew: React.FC<Props> = ({ item, loading, ...props }) => {
|
|||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
const segments = useSegments();
|
const segments = useSegments();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const from = segments[2];
|
const from = (segments as string[])[2];
|
||||||
|
|
||||||
const destinctPeople = useMemo(() => {
|
const destinctPeople = useMemo(() => {
|
||||||
const people: Record<string, BaseItemPerson> = {};
|
const people: Record<string, BaseItemPerson> = {};
|
||||||
@@ -56,15 +55,12 @@ export const CastAndCrew: React.FC<Props> = ({ item, loading, ...props }) => {
|
|||||||
renderItem={(i) => (
|
renderItem={(i) => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
const url = itemRouter(
|
if (i.Id) {
|
||||||
{
|
router.push({
|
||||||
Id: i.Id,
|
pathname: "/persons/[personId]",
|
||||||
Type: "Person",
|
params: { personId: i.Id },
|
||||||
},
|
});
|
||||||
from,
|
}
|
||||||
);
|
|
||||||
// @ts-expect-error
|
|
||||||
router.push(url);
|
|
||||||
}}
|
}}
|
||||||
className='flex flex-col w-28'
|
className='flex flex-col w-28'
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { getTvShowsApi } from "@jellyfin/sdk/lib/utils/api";
|
import { getTvShowsApi } from "@jellyfin/sdk/lib/utils/api";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
import { TouchableOpacity, type ViewProps } from "react-native";
|
import { TouchableOpacity, type ViewProps } from "react-native";
|
||||||
import { useDownload } from "@/providers/DownloadProvider";
|
import { useDownload } from "@/providers/DownloadProvider";
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
|
|
||||||
import ContinueWatchingPoster from "../ContinueWatchingPoster";
|
import ContinueWatchingPoster from "../ContinueWatchingPoster";
|
||||||
import {
|
import {
|
||||||
HorizontalScroll,
|
HorizontalScroll,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { getTvShowsApi } from "@jellyfin/sdk/lib/utils/api";
|
import { getTvShowsApi } from "@jellyfin/sdk/lib/utils/api";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { atom, useAtom } from "jotai";
|
import { atom, useAtom } from "jotai";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
type SeasonIndexState,
|
type SeasonIndexState,
|
||||||
} from "@/components/series/SeasonDropdown";
|
} from "@/components/series/SeasonDropdown";
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
|
|
||||||
import { runtimeTicksToSeconds } from "@/utils/time";
|
import { runtimeTicksToSeconds } from "@/utils/time";
|
||||||
import ContinueWatchingPoster from "../ContinueWatchingPoster";
|
import ContinueWatchingPoster from "../ContinueWatchingPoster";
|
||||||
import { Text } from "../common/Text";
|
import { Text } from "../common/Text";
|
||||||
@@ -101,8 +100,6 @@ export const SeasonPicker: React.FC<Props> = ({ item }) => {
|
|||||||
enabled: !!api && !!user?.Id && !!item.Id && !!selectedSeasonId,
|
enabled: !!api && !!user?.Id && !!item.Id && !!selectedSeasonId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
// Used for height calculation
|
// Used for height calculation
|
||||||
const [nrOfEpisodes, setNrOfEpisodes] = useState(0);
|
const [nrOfEpisodes, setNrOfEpisodes] = useState(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export const HomeIndex = () => {
|
|||||||
const segments = useSegments();
|
const segments = useSegments();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = eventBus.on("scrollToTop", () => {
|
const unsubscribe = eventBus.on("scrollToTop", () => {
|
||||||
if (segments[2] === "(home)")
|
if ((segments as string[])[2] === "(home)")
|
||||||
scrollViewRef.current?.scrollTo({ y: -152, animated: true });
|
scrollViewRef.current?.scrollTo({ y: -152, animated: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ export const HomeIndex = () => {
|
|||||||
const id = section.title || `section-${index}`;
|
const id = section.title || `section-${index}`;
|
||||||
ss.push({
|
ss.push({
|
||||||
title: t(`${id}`),
|
title: t(`${id}`),
|
||||||
queryKey: ["home", id],
|
queryKey: ["home", "custom", String(index), section.title ?? null],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (section.items) {
|
if (section.items) {
|
||||||
const response = await getItemsApi(api).getItems({
|
const response = await getItemsApi(api).getItems({
|
||||||
@@ -364,8 +364,8 @@ export const HomeIndex = () => {
|
|||||||
const sections = settings?.home?.sections ? customSections : defaultSections;
|
const sections = settings?.home?.sections ? customSections : defaultSections;
|
||||||
|
|
||||||
if (!isConnected || serverConnected !== true) {
|
if (!isConnected || serverConnected !== true) {
|
||||||
let title: string;
|
let title = "";
|
||||||
let subtitle: string;
|
let subtitle = "";
|
||||||
|
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
// No network connection
|
// No network connection
|
||||||
|
|||||||
@@ -379,8 +379,7 @@ export const Controls: FC<Props> = ({
|
|||||||
|
|
||||||
console.log("queryParams", queryParams);
|
console.log("queryParams", queryParams);
|
||||||
|
|
||||||
// @ts-expect-error
|
router.replace(`player/direct-player?${queryParams}` as any);
|
||||||
router.replace(`player/direct-player?${queryParams}`);
|
|
||||||
},
|
},
|
||||||
[settings, subtitleIndex, audioIndex, mediaSource, bitrateValue, router],
|
[settings, subtitleIndex, audioIndex, mediaSource, bitrateValue, router],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -95,8 +95,7 @@ export const VideoProvider: React.FC<VideoProviderProps> = ({
|
|||||||
playbackPosition: playbackPosition,
|
playbackPosition: playbackPosition,
|
||||||
}).toString();
|
}).toString();
|
||||||
|
|
||||||
//@ts-expect-error
|
router.replace(`player/direct-player?${queryParams}` as any);
|
||||||
router.replace(`player/direct-player?${queryParams}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const setTrackParams = (
|
const setTrackParams = (
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ const DropdownView = () => {
|
|||||||
bitrateValue: bitrate.toString(),
|
bitrateValue: bitrate.toString(),
|
||||||
playbackPosition: playbackPosition,
|
playbackPosition: playbackPosition,
|
||||||
}).toString();
|
}).toString();
|
||||||
// @ts-expect-error
|
router.replace(`player/direct-player?${queryParams}` as any);
|
||||||
router.replace(`player/direct-player?${queryParams}`);
|
|
||||||
},
|
},
|
||||||
[item, mediaSource, subtitleIndex, audioIndex, playbackPosition],
|
[item, mediaSource, subtitleIndex, audioIndex, playbackPosition],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ export class JellyseerrApi {
|
|||||||
`Jellyseerr response error\nerror: ${error.toString()}\nurl: ${error?.config?.url}`,
|
`Jellyseerr response error\nerror: ${error.toString()}\nurl: ${error?.config?.url}`,
|
||||||
error.response?.data,
|
error.response?.data,
|
||||||
);
|
);
|
||||||
if (error.status === 403) {
|
if (error.response?.status === 403) {
|
||||||
clearJellyseerrStorageData();
|
clearJellyseerrStorageData();
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
@@ -512,7 +512,7 @@ export const useJellyseerr = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const jellyseerrRegion = useMemo(
|
const jellyseerrRegion = useMemo(
|
||||||
() => jellyseerrUser?.settings?.region || "US",
|
() => jellyseerrUser?.settings?.discoverRegion || "US",
|
||||||
[jellyseerrUser],
|
[jellyseerrUser],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -284,7 +284,28 @@
|
|||||||
"collections": "Collections",
|
"collections": "Collections",
|
||||||
"actors": "Actors",
|
"actors": "Actors",
|
||||||
"request_movies": "Request Movies",
|
"request_movies": "Request Movies",
|
||||||
"request_series": "Request Series"
|
"request_series": "Request Series",
|
||||||
|
"recently_added": "Recently Added",
|
||||||
|
"recent_requests": "Recent Requests",
|
||||||
|
"plex_watchlist": "Plex Watchlist",
|
||||||
|
"trending": "Trending",
|
||||||
|
"popular_movies": "Popular Movies",
|
||||||
|
"movie_genres": "Movie Genres",
|
||||||
|
"upcoming_movies": "Upcoming Movies",
|
||||||
|
"studios": "Studios",
|
||||||
|
"popular_tv": "Popular TV",
|
||||||
|
"tv_genres": "TV Genres",
|
||||||
|
"upcoming_tv": "Upcoming TV",
|
||||||
|
"networks": "Networks",
|
||||||
|
"tmdb_movie_keyword": "TMDB Movie Keyword",
|
||||||
|
"tmdb_movie_genre": "TMDB Movie Genre",
|
||||||
|
"tmdb_tv_keyword": "TMDB TV Keyword",
|
||||||
|
"tmdb_tv_genre": "TMDB TV Genre",
|
||||||
|
"tmdb_search": "TMDB Search",
|
||||||
|
"tmdb_studio": "TMDB Studio",
|
||||||
|
"tmdb_network": "TMDB Network",
|
||||||
|
"tmdb_movie_streaming_services": "TMDB Movie Streaming Services",
|
||||||
|
"tmdb_tv_streaming_services": "TMDB TV Streaming Services"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"no_results": "No Results",
|
"no_results": "No Results",
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export type Home = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type HomeSection = {
|
export type HomeSection = {
|
||||||
|
title?: string;
|
||||||
orientation?: "horizontal" | "vertical";
|
orientation?: "horizontal" | "vertical";
|
||||||
items?: HomeSectionItemResolver;
|
items?: HomeSectionItemResolver;
|
||||||
nextUp?: HomeSectionNextUpResolver;
|
nextUp?: HomeSectionNextUpResolver;
|
||||||
|
|||||||
Reference in New Issue
Block a user