refactor: pass down items with sources to children (#1218)
Some checks failed
🏗️ Build Apps / 🤖 Build Android APK (Phone) (push) Has been cancelled
🏗️ Build Apps / 🤖 Build Android APK (TV) (push) Has been cancelled
🏗️ Build Apps / 🍎 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
🌐 Translation Sync / sync-translations (push) Has been cancelled

This commit is contained in:
lostb1t
2025-11-17 21:15:59 +01:00
committed by GitHub
parent 3c57829360
commit c05cef295e
3 changed files with 24 additions and 27 deletions

View File

@@ -27,8 +27,8 @@ const Page: React.FC = () => {
ItemFields.MediaStreams, ItemFields.MediaStreams,
]); ]);
// preload media sources in background // preload media sources
useItemQuery(id, false, undefined, []); const { data: itemWithSources } = useItemQuery(id, false, undefined, []);
const opacity = useSharedValue(1); const opacity = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => { const animatedStyle = useAnimatedStyle(() => {
@@ -98,7 +98,13 @@ const Page: React.FC = () => {
<View className='h-12 bg-neutral-900 rounded-lg w-full mb-2' /> <View className='h-12 bg-neutral-900 rounded-lg w-full mb-2' />
<View className='h-24 bg-neutral-900 rounded-lg mb-1 w-full' /> <View className='h-24 bg-neutral-900 rounded-lg mb-1 w-full' />
</Animated.View> </Animated.View>
{item && <ItemContent item={item} isOffline={isOffline} />} {item && (
<ItemContent
item={item}
isOffline={isOffline}
itemWithSources={itemWithSources}
/>
)}
</View> </View>
); );
}; };

View File

@@ -46,10 +46,11 @@ export type SelectedOptions = {
interface ItemContentProps { interface ItemContentProps {
item: BaseItemDto; item: BaseItemDto;
isOffline: boolean; isOffline: boolean;
itemWithSources?: BaseItemDto | null;
} }
export const ItemContent: React.FC<ItemContentProps> = React.memo( export const ItemContent: React.FC<ItemContentProps> = React.memo(
({ item, isOffline }) => { ({ item, isOffline, itemWithSources }) => {
const [api] = useAtom(apiAtom); const [api] = useAtom(apiAtom);
const { settings } = useSettings(); const { settings } = useSettings();
const { orientation } = useOrientation(); const { orientation } = useOrientation();
@@ -98,7 +99,7 @@ export const ItemContent: React.FC<ItemContentProps> = React.memo(
]); ]);
useEffect(() => { useEffect(() => {
if (!Platform.isTV) { if (!Platform.isTV && itemWithSources) {
navigation.setOptions({ navigation.setOptions({
headerRight: () => headerRight: () =>
item && item &&
@@ -108,7 +109,7 @@ export const ItemContent: React.FC<ItemContentProps> = React.memo(
{item.Type !== "Program" && ( {item.Type !== "Program" && (
<View className='flex flex-row items-center'> <View className='flex flex-row items-center'>
{!Platform.isTV && ( {!Platform.isTV && (
<DownloadSingleItem item={item} size='large' /> <DownloadSingleItem item={itemWithSources} size='large' />
)} )}
{user?.Policy?.IsAdministrator && ( {user?.Policy?.IsAdministrator && (
<PlayInRemoteSessionButton item={item} size='large' /> <PlayInRemoteSessionButton item={item} size='large' />
@@ -125,7 +126,7 @@ export const ItemContent: React.FC<ItemContentProps> = React.memo(
{item.Type !== "Program" && ( {item.Type !== "Program" && (
<View className='flex flex-row items-center space-x-2'> <View className='flex flex-row items-center space-x-2'>
{!Platform.isTV && ( {!Platform.isTV && (
<DownloadSingleItem item={item} size='large' /> <DownloadSingleItem item={itemWithSources} size='large' />
)} )}
{user?.Policy?.IsAdministrator && ( {user?.Policy?.IsAdministrator && (
<PlayInRemoteSessionButton item={item} size='large' /> <PlayInRemoteSessionButton item={item} size='large' />
@@ -139,7 +140,7 @@ export const ItemContent: React.FC<ItemContentProps> = React.memo(
)), )),
}); });
} }
}, [item, navigation, user]); }, [item, navigation, user, itemWithSources]);
useEffect(() => { useEffect(() => {
if (item) { if (item) {
@@ -212,7 +213,7 @@ export const ItemContent: React.FC<ItemContentProps> = React.memo(
<MediaSourceButton <MediaSourceButton
selectedOptions={selectedOptions} selectedOptions={selectedOptions}
setSelectedOptions={setSelectedOptions} setSelectedOptions={setSelectedOptions}
item={item} item={itemWithSources}
colors={itemColors} colors={itemColors}
/> />
)} )}

View File

@@ -7,13 +7,12 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ActivityIndicator, TouchableOpacity, View } from "react-native"; import { ActivityIndicator, TouchableOpacity, View } from "react-native";
import type { ThemeColors } from "@/hooks/useImageColorsReturn"; import type { ThemeColors } from "@/hooks/useImageColorsReturn";
import { useItemQuery } from "@/hooks/useItemQuery";
import { BITRATES } from "./BitRateSheet"; import { BITRATES } from "./BitRateSheet";
import type { SelectedOptions } from "./ItemContent"; import type { SelectedOptions } from "./ItemContent";
import { type OptionGroup, PlatformDropdown } from "./PlatformDropdown"; import { type OptionGroup, PlatformDropdown } from "./PlatformDropdown";
interface Props extends React.ComponentProps<typeof TouchableOpacity> { interface Props extends React.ComponentProps<typeof TouchableOpacity> {
item: BaseItemDto; item?: BaseItemDto | null;
selectedOptions: SelectedOptions; selectedOptions: SelectedOptions;
setSelectedOptions: React.Dispatch< setSelectedOptions: React.Dispatch<
React.SetStateAction<SelectedOptions | undefined> React.SetStateAction<SelectedOptions | undefined>
@@ -29,12 +28,6 @@ export const MediaSourceButton: React.FC<Props> = ({
}: Props) => { }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const { data: itemWithSources, isLoading } = useItemQuery(
item.Id,
false,
undefined,
[],
);
const effectiveColors = colors || { const effectiveColors = colors || {
primary: "#7c3aed", primary: "#7c3aed",
@@ -42,7 +35,7 @@ export const MediaSourceButton: React.FC<Props> = ({
}; };
useEffect(() => { useEffect(() => {
const firstMediaSource = itemWithSources?.MediaSources?.[0]; const firstMediaSource = item?.MediaSources?.[0];
if (!firstMediaSource) return; if (!firstMediaSource) return;
setSelectedOptions((prev) => { setSelectedOptions((prev) => {
if (!prev) return prev; if (!prev) return prev;
@@ -51,7 +44,7 @@ export const MediaSourceButton: React.FC<Props> = ({
mediaSource: firstMediaSource, mediaSource: firstMediaSource,
}; };
}); });
}, [itemWithSources, setSelectedOptions]); }, [item, setSelectedOptions]);
const getMediaSourceDisplayName = useCallback((source: MediaSourceInfo) => { const getMediaSourceDisplayName = useCallback((source: MediaSourceInfo) => {
const videoStream = source.MediaStreams?.find((x) => x.Type === "Video"); const videoStream = source.MediaStreams?.find((x) => x.Type === "Video");
@@ -93,13 +86,10 @@ export const MediaSourceButton: React.FC<Props> = ({
}); });
// Media Source group (only if multiple sources) // Media Source group (only if multiple sources)
if ( if (item?.MediaSources && item.MediaSources.length > 1) {
itemWithSources?.MediaSources &&
itemWithSources.MediaSources.length > 1
) {
groups.push({ groups.push({
title: t("item_card.video"), title: t("item_card.video"),
options: itemWithSources.MediaSources.map((source) => ({ options: item.MediaSources.map((source) => ({
type: "radio" as const, type: "radio" as const,
label: getMediaSourceDisplayName(source), label: getMediaSourceDisplayName(source),
value: source, value: source,
@@ -159,7 +149,7 @@ export const MediaSourceButton: React.FC<Props> = ({
return groups; return groups;
}, [ }, [
itemWithSources, item,
selectedOptions, selectedOptions,
audioStreams, audioStreams,
subtitleStreams, subtitleStreams,
@@ -170,7 +160,7 @@ export const MediaSourceButton: React.FC<Props> = ({
const trigger = ( const trigger = (
<TouchableOpacity <TouchableOpacity
disabled={!item || isLoading} disabled={!item}
onPress={() => setOpen(true)} onPress={() => setOpen(true)}
className='relative' className='relative'
> >
@@ -179,7 +169,7 @@ export const MediaSourceButton: React.FC<Props> = ({
className='absolute w-12 h-12 rounded-full' className='absolute w-12 h-12 rounded-full'
/> />
<View className='w-12 h-12 rounded-full z-10 items-center justify-center'> <View className='w-12 h-12 rounded-full z-10 items-center justify-center'>
{isLoading ? ( {!item ? (
<ActivityIndicator size='small' color={effectiveColors.text} /> <ActivityIndicator size='small' color={effectiveColors.text} />
) : ( ) : (
<Ionicons name='list' size={24} color={effectiveColors.text} /> <Ionicons name='list' size={24} color={effectiveColors.text} />