import type { BaseItemDto, MediaSourceInfo, } from "@jellyfin/sdk/lib/generated-client/models"; import { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Platform, TouchableOpacity, View } from "react-native"; import { Text } from "./common/Text"; import { type OptionGroup, PlatformDropdown } from "./PlatformDropdown"; interface Props extends React.ComponentProps { item: BaseItemDto; onChange: (value: MediaSourceInfo) => void; selected?: MediaSourceInfo | null; } export const MediaSourceSelector: React.FC = ({ item, onChange, selected, ...props }) => { const isTv = Platform.isTV; const [open, setOpen] = useState(false); const { t } = useTranslation(); const getDisplayName = useCallback((source: MediaSourceInfo) => { const videoStream = source.MediaStreams?.find((x) => x.Type === "Video"); if (videoStream?.DisplayTitle) { return videoStream.DisplayTitle; } // Fallback to source name if (source.Name) { return source.Name; } // Last resort fallback return `Source ${source.Id}`; }, []); const selectedName = useMemo(() => { if (!selected) return ""; return getDisplayName(selected); }, [selected, getDisplayName]); const optionGroups: OptionGroup[] = useMemo( () => [ { options: item.MediaSources?.map((source) => ({ type: "radio" as const, label: getDisplayName(source), value: source, selected: source.Id === selected?.Id, onPress: () => onChange(source), })) || [], }, ], [item.MediaSources, selected, getDisplayName, onChange], ); const handleOptionSelect = (optionId: string) => { const selectedSource = item.MediaSources?.find( (source, idx) => `${source.Id || idx}` === optionId, ); if (selectedSource) { onChange(selectedSource); } setOpen(false); }; const trigger = ( {t("item_card.video")} setOpen(true)} > {selectedName} ); if (isTv) return null; return ( ); };