feat: Expo 54 (new arch) support + new in-house download module (#1174)

Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com>
Co-authored-by: sarendsen <coding-mosses0z@icloud.com>
Co-authored-by: Lance Chant <13349722+lancechant@users.noreply.github.com>
Co-authored-by: Gauvain <68083474+Gauvino@users.noreply.github.com>
This commit is contained in:
Fredrik Burmester
2025-11-11 08:53:23 +01:00
committed by GitHub
parent 154788cf91
commit 485dc6eeac
181 changed files with 8422 additions and 4298 deletions

View File

@@ -2,13 +2,11 @@ import type {
BaseItemDto,
MediaSourceInfo,
} from "@jellyfin/sdk/lib/generated-client/models";
import { useCallback, useMemo } from "react";
import { Platform, TouchableOpacity, View } from "react-native";
const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
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<typeof View> {
item: BaseItemDto;
@@ -23,7 +21,7 @@ export const MediaSourceSelector: React.FC<Props> = ({
...props
}) => {
const isTv = Platform.isTV;
const [open, setOpen] = useState(false);
const { t } = useTranslation();
const getDisplayName = useCallback((source: MediaSourceInfo) => {
@@ -46,50 +44,60 @@ export const MediaSourceSelector: React.FC<Props> = ({
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 = (
<View className='flex flex-col' {...props}>
<Text className='opacity-50 mb-1 text-xs'>{t("item_card.video")}</Text>
<TouchableOpacity
className='bg-neutral-900 h-10 rounded-xl border-neutral-800 border px-3 py-2 flex flex-row items-center'
onPress={() => setOpen(true)}
>
<Text numberOfLines={1}>{selectedName}</Text>
</TouchableOpacity>
</View>
);
if (isTv) return null;
return (
<View
className='flex shrink'
style={{
minWidth: 50,
<PlatformDropdown
groups={optionGroups}
trigger={trigger}
title={t("item_card.video")}
open={open}
onOpenChange={setOpen}
onOptionSelect={handleOptionSelect}
expoUIConfig={{
hostStyle: { flex: 1 },
}}
>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<View className='flex flex-col' {...props}>
<Text className='opacity-50 mb-1 text-xs'>
{t("item_card.video")}
</Text>
<TouchableOpacity className='bg-neutral-900 h-10 rounded-xl border-neutral-800 border px-3 py-2 flex flex-row items-center'>
<Text numberOfLines={1}>{selectedName}</Text>
</TouchableOpacity>
</View>
</DropdownMenu.Trigger>
<DropdownMenu.Content
loop={true}
side='bottom'
align='start'
alignOffset={0}
avoidCollisions={true}
collisionPadding={8}
sideOffset={8}
>
<DropdownMenu.Label>Media sources</DropdownMenu.Label>
{item.MediaSources?.map((source, idx: number) => (
<DropdownMenu.Item
key={idx.toString()}
onSelect={() => {
onChange(source);
}}
>
<DropdownMenu.ItemTitle>
{getDisplayName(source)}
</DropdownMenu.ItemTitle>
</DropdownMenu.Item>
))}
</DropdownMenu.Content>
</DropdownMenu.Root>
</View>
bottomSheetConfig={{
enablePanDownToClose: true,
}}
/>
);
};