diff --git a/app/(auth)/(tabs)/(home)/index.tsx b/app/(auth)/(tabs)/(home)/index.tsx
index 75ae50b8..ac25bacb 100644
--- a/app/(auth)/(tabs)/(home)/index.tsx
+++ b/app/(auth)/(tabs)/(home)/index.tsx
@@ -109,7 +109,7 @@ export default function index() {
return response.data.Items || [];
},
enabled: !!api && !!user?.Id && settings?.usePopularPlugin === true,
- staleTime: 0,
+ staleTime: 60 * 1000,
});
const movieCollectionId = useMemo(() => {
@@ -148,6 +148,7 @@ export default function index() {
(
await getItemsApi(api).getResumeItems({
userId: user.Id,
+ enableImageTypes: ["Primary", "Backdrop", "Thumb"],
})
).data.Items || [],
type: "ScrollingCollectionList",
@@ -162,6 +163,7 @@ export default function index() {
userId: user?.Id,
fields: ["MediaSourceCount"],
limit: 20,
+ enableImageTypes: ["Primary", "Backdrop", "Thumb"],
})
).data.Items || [],
type: "ScrollingCollectionList",
@@ -220,7 +222,7 @@ export default function index() {
})
).data.Items || [],
type: "ScrollingCollectionList",
- orientation: "horizontal",
+ orientation: "vertical",
},
];
return ss;
diff --git a/app/(auth)/(tabs)/(home,libraries,search)/items/[id].tsx b/app/(auth)/(tabs)/(home,libraries,search)/items/[id].tsx
index 84958a90..e62f619a 100644
--- a/app/(auth)/(tabs)/(home,libraries,search)/items/[id].tsx
+++ b/app/(auth)/(tabs)/(home,libraries,search)/items/[id].tsx
@@ -62,7 +62,7 @@ const page: React.FC = () => {
itemId: id,
}),
enabled: !!id && !!api,
- staleTime: 60,
+ staleTime: 60 * 1000,
});
const { data: sessionData } = useQuery({
@@ -130,8 +130,8 @@ const page: React.FC = () => {
getBackdropUrl({
api,
item,
- quality: 90,
- width: 1000,
+ quality: 95,
+ width: 1200,
}),
[item]
);
@@ -227,16 +227,11 @@ const page: React.FC = () => {
-
-
-
- {item.Type === "Episode" && (
-
-
-
- )}
-
-
+
+
+ {item.Type === "Episode" && }
+
+
diff --git a/components/Button.tsx b/components/Button.tsx
index 813c4222..305312d4 100644
--- a/components/Button.tsx
+++ b/components/Button.tsx
@@ -10,7 +10,7 @@ interface ButtonProps extends React.ComponentProps {
disabled?: boolean;
children?: string | ReactNode;
loading?: boolean;
- color?: "purple" | "red" | "black";
+ color?: "purple" | "red" | "black" | "transparent";
iconRight?: ReactNode;
iconLeft?: ReactNode;
justify?: "center" | "between";
@@ -37,6 +37,8 @@ export const Button: React.FC> = ({
return "bg-red-600";
case "black":
return "bg-neutral-900 border border-neutral-800";
+ case "transparent":
+ return "bg-transparent";
}
}, [color]);
diff --git a/components/ContinueWatchingPoster.tsx b/components/ContinueWatchingPoster.tsx
index e057ee15..20acb45b 100644
--- a/components/ContinueWatchingPoster.tsx
+++ b/components/ContinueWatchingPoster.tsx
@@ -6,6 +6,8 @@ import { useMemo, useState } from "react";
import { View } from "react-native";
import { WatchedIndicator } from "./WatchedIndicator";
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
+import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
+import { getPrimaryImageUrlById } from "@/utils/jellyfin/image/getPrimaryImageUrlById";
type ContinueWatchingPosterProps = {
item: BaseItemDto;
@@ -20,12 +22,7 @@ const ContinueWatchingPoster: React.FC = ({
const url = useMemo(
() =>
- getPrimaryImageUrl({
- api,
- item,
- quality: 80,
- width: 300,
- }),
+ `${api?.basePath}/Items/${item.ParentBackdropItemId}/Images/Thumb?fillHeight=389&quality=80&tag=${item.ParentThumbImageTag}`,
[item]
);
diff --git a/components/SimilarItems.tsx b/components/SimilarItems.tsx
index 45624a52..f0d6ff40 100644
--- a/components/SimilarItems.tsx
+++ b/components/SimilarItems.tsx
@@ -6,16 +6,19 @@ import { useQuery } from "@tanstack/react-query";
import { router } from "expo-router";
import { useAtom } from "jotai";
import { useMemo } from "react";
-import { ScrollView, TouchableOpacity, View } from "react-native";
+import { ScrollView, TouchableOpacity, View, ViewProps } from "react-native";
import { Text } from "./common/Text";
import { ItemCardText } from "./ItemCardText";
import { Loader } from "./Loader";
-type SimilarItemsProps = {
+interface SimilarItemsProps extends ViewProps {
itemId: string;
-};
+}
-export const SimilarItems: React.FC = ({ itemId }) => {
+export const SimilarItems: React.FC = ({
+ itemId,
+ ...props
+}) => {
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
@@ -41,8 +44,8 @@ export const SimilarItems: React.FC = ({ itemId }) => {
);
return (
-
- Similar items
+
+ Similar items
{isLoading ? (
diff --git a/components/home/ScrollingCollectionList.tsx b/components/home/ScrollingCollectionList.tsx
index df86bb88..3a5f3dc5 100644
--- a/components/home/ScrollingCollectionList.tsx
+++ b/components/home/ScrollingCollectionList.tsx
@@ -11,6 +11,8 @@ import {
useQuery,
type QueryFunction,
} from "@tanstack/react-query";
+import SeriesPoster from "../posters/SeriesPoster";
+import { EpisodePoster } from "../posters/EpisodePoster";
interface Props extends ViewProps {
title?: string | null;
@@ -34,7 +36,7 @@ export const ScrollingCollectionList: React.FC = ({
queryKey,
queryFn,
enabled: !disabled,
- staleTime: 0,
+ staleTime: 60 * 1000,
});
if (disabled || !title) return null;
@@ -53,15 +55,18 @@ export const ScrollingCollectionList: React.FC = ({
key={index}
item={item}
className={`flex flex-col
- ${orientation === "vertical" ? "w-28" : "w-44"}
+ ${orientation === "horizontal" ? "w-44" : "w-28"}
`}
>
- {orientation === "vertical" ? (
-
- ) : (
+ {item.Type === "Episode" && orientation === "horizontal" && (
)}
+ {item.Type === "Episode" && orientation === "vertical" && (
+
+ )}
+ {item.Type === "Movie" && }
+ {item.Type === "Series" && }
diff --git a/components/posters/EpisodePoster.tsx b/components/posters/EpisodePoster.tsx
new file mode 100644
index 00000000..c82464d5
--- /dev/null
+++ b/components/posters/EpisodePoster.tsx
@@ -0,0 +1,64 @@
+import { apiAtom } from "@/providers/JellyfinProvider";
+import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
+import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
+import { Image } from "expo-image";
+import { useAtom } from "jotai";
+import { useMemo, useState } from "react";
+import { View } from "react-native";
+import { WatchedIndicator } from "@/components/WatchedIndicator";
+
+type MoviePosterProps = {
+ item: BaseItemDto;
+ showProgress?: boolean;
+};
+
+export const EpisodePoster: React.FC = ({
+ item,
+ showProgress = false,
+}) => {
+ const [api] = useAtom(apiAtom);
+
+ const url = useMemo(() => {
+ if (item.Type === "Episode") {
+ return `${api?.basePath}/Items/${item.ParentBackdropItemId}/Images/Thumb?fillHeight=389&quality=80&tag=${item.ParentThumbImageTag}`;
+ }
+ }, [item]);
+
+ const [progress, setProgress] = useState(
+ item.UserData?.PlayedPercentage || 0
+ );
+
+ const blurhash = useMemo(() => {
+ const key = item.ImageTags?.["Primary"] as string;
+ return item.ImageBlurHashes?.["Primary"]?.[key];
+ }, [item]);
+
+ return (
+
+
+
+ {showProgress && progress > 0 && (
+
+ )}
+
+ );
+};
diff --git a/components/posters/MoviePoster.tsx b/components/posters/MoviePoster.tsx
index 972392b6..ff9f3458 100644
--- a/components/posters/MoviePoster.tsx
+++ b/components/posters/MoviePoster.tsx
@@ -18,15 +18,16 @@ const MoviePoster: React.FC = ({
}) => {
const [api] = useAtom(apiAtom);
- const url = useMemo(
- () =>
- getPrimaryImageUrl({
- api,
- item,
- width: 300,
- }),
- [item]
- );
+ const url = useMemo(() => {
+ if (item.Type === "Episode") {
+ return `${api?.basePath}/Items/${item.ParentBackdropItemId}/Images/Thumb?fillHeight=389&quality=80&tag=${item.ParentThumbImageTag}`;
+ }
+ return getPrimaryImageUrl({
+ api,
+ item,
+ width: 300,
+ });
+ }, [item]);
const [progress, setProgress] = useState(
item.UserData?.PlayedPercentage || 0
diff --git a/components/posters/SeriesPoster.tsx b/components/posters/SeriesPoster.tsx
index d235639f..dbadcdce 100644
--- a/components/posters/SeriesPoster.tsx
+++ b/components/posters/SeriesPoster.tsx
@@ -15,14 +15,16 @@ type MoviePosterProps = {
const SeriesPoster: React.FC = ({ item }) => {
const [api] = useAtom(apiAtom);
- const url = useMemo(
- () =>
- getPrimaryImageUrl({
- api,
- item,
- }),
- [item]
- );
+ const url = useMemo(() => {
+ if (item.Type === "Episode") {
+ return `${api?.basePath}/Items/${item.SeriesId}/Images/Primary?fillHeight=389&quality=80&tag=${item.SeriesPrimaryImageTag}`;
+ }
+ return getPrimaryImageUrl({
+ api,
+ item,
+ width: 300,
+ });
+ }, [item]);
const blurhash = useMemo(() => {
const key = item.ImageTags?.["Primary"] as string;
diff --git a/components/series/CastAndCrew.tsx b/components/series/CastAndCrew.tsx
index ab041bd7..d0406ce6 100644
--- a/components/series/CastAndCrew.tsx
+++ b/components/series/CastAndCrew.tsx
@@ -1,27 +1,26 @@
+import { apiAtom } from "@/providers/JellyfinProvider";
+import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
import {
BaseItemDto,
BaseItemPerson,
} from "@jellyfin/sdk/lib/generated-client/models";
+import { router } from "expo-router";
+import { useAtom } from "jotai";
import React from "react";
-import { Linking, TouchableOpacity, View } from "react-native";
+import { TouchableOpacity, View, ViewProps } from "react-native";
import { HorizontalScroll } from "../common/HorrizontalScroll";
import { Text } from "../common/Text";
import Poster from "../posters/Poster";
-import { useAtom } from "jotai";
-import { apiAtom } from "@/providers/JellyfinProvider";
-import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
-import { router, usePathname } from "expo-router";
-import { useSettings } from "@/utils/atoms/settings";
-export const CastAndCrew = ({ item }: { item: BaseItemDto }) => {
+interface Props extends ViewProps {
+ item: BaseItemDto;
+}
+
+export const CastAndCrew: React.FC = ({ item, ...props }) => {
const [api] = useAtom(apiAtom);
- const [settings] = useSettings();
-
- const pathname = usePathname();
-
return (
-
+
Cast & Crew
>
data={item.People}
diff --git a/components/series/CurrentSeries.tsx b/components/series/CurrentSeries.tsx
index a71dea8d..8d06d2e7 100644
--- a/components/series/CurrentSeries.tsx
+++ b/components/series/CurrentSeries.tsx
@@ -3,17 +3,21 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { router } from "expo-router";
import { useAtom } from "jotai";
import React from "react";
-import { TouchableOpacity, View } from "react-native";
+import { TouchableOpacity, View, ViewProps } from "react-native";
import Poster from "../posters/Poster";
import { HorizontalScroll } from "../common/HorrizontalScroll";
import { Text } from "../common/Text";
import { getPrimaryImageUrlById } from "@/utils/jellyfin/image/getPrimaryImageUrlById";
-export const CurrentSeries = ({ item }: { item: BaseItemDto }) => {
+interface Props extends ViewProps {
+ item: BaseItemDto;
+}
+
+export const CurrentSeries: React.FC = ({ item, ...props }) => {
const [api] = useAtom(apiAtom);
return (
-
+
Series
data={[item]}
diff --git a/components/series/NextUp.tsx b/components/series/NextUp.tsx
index e4ac462d..fa8558fb 100644
--- a/components/series/NextUp.tsx
+++ b/components/series/NextUp.tsx
@@ -34,7 +34,7 @@ export const NextUp: React.FC<{ seriesId: string }> = ({ seriesId }) => {
if (!items?.length)
return (
-
+
Next up
No items to display