mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 23:59:08 +00:00
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
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:
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
Reference in New Issue
Block a user