diff --git a/app/(auth)/(tabs)/(home)/settings.tv.tsx b/app/(auth)/(tabs)/(home)/settings.tv.tsx index 8110d1d5..10ad9029 100644 --- a/app/(auth)/(tabs)/(home)/settings.tv.tsx +++ b/app/(auth)/(tabs)/(home)/settings.tv.tsx @@ -15,6 +15,7 @@ import { TVSettingsTextInput, TVSettingsToggle, } from "@/components/tv"; +import { useScaledTVTypography } from "@/constants/TVTypography"; import { useTVOptionModal } from "@/hooks/useTVOptionModal"; import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider"; import { @@ -31,6 +32,7 @@ export default function SettingsTV() { const [user] = useAtom(userAtom); const [api] = useAtom(apiAtom); const { showOptions } = useTVOptionModal(); + const typography = useScaledTVTypography(); // Local state for OpenSubtitles API key (only commit on blur) const [openSubtitlesApiKey, setOpenSubtitlesApiKey] = useState( @@ -204,7 +206,7 @@ export default function SettingsTV() { {/* Header */} updateSettings({ showTVHeroCarousel: value })} /> + + updateSettings({ showSeriesPosterOnEpisode: value }) + } + /> {/* User Section */} = React.memo( return getPrimaryImageUrlById({ api, id: seasonId, width: 300 }); }, [api, item?.Type, item?.SeasonId, item?.ParentId]); - // Episode thumbnail URL - 16:9 horizontal image for episode items + // Episode thumbnail URL - episode's own primary image (16:9 for episodes) const episodeThumbnailUrl = useMemo(() => { if (item?.Type !== "Episode" || !api) return null; - // Use parent backdrop thumb if available (series/season thumbnail) - if (item.ParentBackdropItemId && item.ParentThumbImageTag) { - return `${api.basePath}/Items/${item.ParentBackdropItemId}/Images/Thumb?fillHeight=700&quality=80&tag=${item.ParentThumbImageTag}`; - } - // Fall back to episode's primary image (which is usually 16:9 for episodes) return `${api.basePath}/Items/${item.Id}/Images/Primary?fillHeight=700&quality=80`; }, [api, item]); + // Series thumb URL - used when showSeriesPosterOnEpisode setting is enabled + const seriesThumbUrl = useMemo(() => { + if (item?.Type !== "Episode" || !item.SeriesId || !api) return null; + return `${api.basePath}/Items/${item.SeriesId}/Images/Thumb?fillHeight=700&quality=80`; + }, [api, item]); + // Determine which option button is the last one (for focus guide targeting) const lastOptionButton = useMemo(() => { const hasSubtitleOption = @@ -738,9 +739,14 @@ export const ItemContentTV: React.FC = React.memo( shadowRadius: 20, }} > - {item.Type === "Episode" && episodeThumbnailUrl ? ( + {item.Type === "Episode" ? ( diff --git a/components/home/TVHeroCarousel.tsx b/components/home/TVHeroCarousel.tsx index 44efc01c..2c483671 100644 --- a/components/home/TVHeroCarousel.tsx +++ b/components/home/TVHeroCarousel.tsx @@ -63,17 +63,24 @@ const HeroCard: React.FC = React.memo( const posterUrl = useMemo(() => { if (!api) return null; - // Try thumb first, then primary + + // For episodes, always use series thumb + if (item.Type === "Episode") { + if (item.ParentThumbImageTag) { + return `${api.basePath}/Items/${item.ParentBackdropItemId}/Images/Thumb?fillHeight=400&quality=80&tag=${item.ParentThumbImageTag}`; + } + if (item.SeriesId) { + return `${api.basePath}/Items/${item.SeriesId}/Images/Thumb?fillHeight=400&quality=80`; + } + } + + // For non-episodes, use item's own thumb/primary if (item.ImageTags?.Thumb) { return `${api.basePath}/Items/${item.Id}/Images/Thumb?fillHeight=400&quality=80&tag=${item.ImageTags.Thumb}`; } if (item.ImageTags?.Primary) { return `${api.basePath}/Items/${item.Id}/Images/Primary?fillHeight=400&quality=80&tag=${item.ImageTags.Primary}`; } - // For episodes, use series thumb - if (item.Type === "Episode" && item.ParentThumbImageTag) { - return `${api.basePath}/Items/${item.ParentBackdropItemId}/Images/Thumb?fillHeight=400&quality=80&tag=${item.ParentThumbImageTag}`; - } return null; }, [api, item]); diff --git a/translations/en.json b/translations/en.json index c435ea30..53901aa3 100644 --- a/translations/en.json +++ b/translations/en.json @@ -127,6 +127,7 @@ "hide_remote_session_button": "Hide Remote Session Button", "show_home_backdrop": "Dynamic Home Backdrop", "show_hero_carousel": "Hero Carousel", + "show_series_poster_on_episode": "Show Series Poster on Episodes", "text_size": "Text Size", "text_size_small": "Small", "text_size_default": "Default", diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index c3f9ba81..aa4ac0c8 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -211,6 +211,7 @@ export type Settings = { showHomeBackdrop: boolean; showTVHeroCarousel: boolean; tvTypographyScale: TVTypographyScale; + showSeriesPosterOnEpisode: boolean; // Appearance hideRemoteSessionButton: boolean; hideWatchlistsTab: boolean; @@ -301,6 +302,7 @@ export const defaultValues: Settings = { showHomeBackdrop: true, showTVHeroCarousel: true, tvTypographyScale: TVTypographyScale.Default, + showSeriesPosterOnEpisode: false, // Appearance hideRemoteSessionButton: false, hideWatchlistsTab: false,