fix(tv): poster design and other stuff

This commit is contained in:
Fredrik Burmester
2026-01-30 09:15:44 +01:00
parent 0cd74519d4
commit aed3a8f493
26 changed files with 758 additions and 1362 deletions

View File

@@ -1,106 +0,0 @@
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { Image } from "expo-image";
import { useAtom } from "jotai";
import { useMemo } from "react";
import { View } from "react-native";
import { WatchedIndicator } from "@/components/WatchedIndicator";
import { useScaledTVPosterSizes } from "@/constants/TVPosterSizes";
import {
GlassPosterView,
isGlassEffectAvailable,
} from "@/modules/glass-poster";
import { apiAtom } from "@/providers/JellyfinProvider";
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
type MoviePosterProps = {
item: BaseItemDto;
showProgress?: boolean;
};
const MoviePoster: React.FC<MoviePosterProps> = ({
item,
showProgress = false,
}) => {
const [api] = useAtom(apiAtom);
const posterSizes = useScaledTVPosterSizes();
const url = useMemo(() => {
return getPrimaryImageUrl({
api,
item,
width: posterSizes.poster * 2, // 2x for quality on large screens
});
}, [api, item, posterSizes.poster]);
const progress = item.UserData?.PlayedPercentage || 0;
const isWatched = item.UserData?.Played === true;
const blurhash = useMemo(() => {
const key = item.ImageTags?.Primary as string;
return item.ImageBlurHashes?.Primary?.[key];
}, [item]);
// Use glass effect on tvOS 26+
const useGlass = isGlassEffectAvailable();
if (useGlass) {
return (
<GlassPosterView
imageUrl={url ?? null}
aspectRatio={10 / 15}
cornerRadius={24}
progress={showProgress ? progress : 0}
showWatchedIndicator={isWatched}
isFocused={false}
width={posterSizes.poster}
style={{ width: posterSizes.poster }}
/>
);
}
// Fallback for older tvOS versions
return (
<View
style={{
position: "relative",
borderRadius: 24,
overflow: "hidden",
width: posterSizes.poster,
aspectRatio: 10 / 15,
}}
>
<Image
placeholder={{
blurhash,
}}
key={item.Id}
id={item.Id}
source={
url
? {
uri: url,
}
: null
}
cachePolicy={"memory-disk"}
contentFit='cover'
style={{
aspectRatio: 10 / 15,
width: "100%",
}}
/>
<WatchedIndicator item={item} />
{showProgress && progress > 0 && (
<View
style={{
height: 4,
backgroundColor: "#dc2626",
width: "100%",
}}
/>
)}
</View>
);
};
export default MoviePoster;

View File

@@ -1,92 +0,0 @@
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { Image } from "expo-image";
import { useAtom } from "jotai";
import { useMemo } from "react";
import { View } from "react-native";
import { useScaledTVPosterSizes } from "@/constants/TVPosterSizes";
import {
GlassPosterView,
isGlassEffectAvailable,
} from "@/modules/glass-poster";
import { apiAtom } from "@/providers/JellyfinProvider";
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
type SeriesPosterProps = {
item: BaseItemDto;
showProgress?: boolean;
};
const SeriesPoster: React.FC<SeriesPosterProps> = ({ item }) => {
const [api] = useAtom(apiAtom);
const posterSizes = useScaledTVPosterSizes();
const url = useMemo(() => {
if (item.Type === "Episode") {
return `${api?.basePath}/Items/${item.SeriesId}/Images/Primary?fillHeight=${posterSizes.poster * 3}&quality=80&tag=${item.SeriesPrimaryImageTag}`;
}
return getPrimaryImageUrl({
api,
item,
width: posterSizes.poster * 2, // 2x for quality on large screens
});
}, [api, item, posterSizes.poster]);
const blurhash = useMemo(() => {
const key = item.ImageTags?.Primary as string;
return item.ImageBlurHashes?.Primary?.[key];
}, [item]);
// Use glass effect on tvOS 26+
const useGlass = isGlassEffectAvailable();
if (useGlass) {
return (
<GlassPosterView
imageUrl={url ?? null}
aspectRatio={10 / 15}
cornerRadius={24}
progress={0}
showWatchedIndicator={false}
isFocused={false}
width={posterSizes.poster}
style={{ width: posterSizes.poster }}
/>
);
}
// Fallback for older tvOS versions
return (
<View
style={{
width: posterSizes.poster,
aspectRatio: 10 / 15,
position: "relative",
borderRadius: 24,
overflow: "hidden",
}}
>
<Image
placeholder={{
blurhash,
}}
key={item.Id}
id={item.Id}
source={
url
? {
uri: url,
}
: null
}
cachePolicy={"memory-disk"}
contentFit='cover'
style={{
height: "100%",
width: "100%",
}}
/>
</View>
);
};
export default SeriesPoster;