diff --git a/app/(auth)/(tabs)/(custom-links)/_layout.tsx b/app/(auth)/(tabs)/(custom-links)/_layout.tsx index f3b436a7..3b8a58e2 100644 --- a/app/(auth)/(tabs)/(custom-links)/_layout.tsx +++ b/app/(auth)/(tabs)/(custom-links)/_layout.tsx @@ -9,7 +9,7 @@ export default function CustomMenuLayout() { { data={flatData} renderItem={renderItem} keyExtractor={keyExtractor} - estimatedItemSize={255} numColumns={ orientation === ScreenOrientation.Orientation.PORTRAIT_UP ? 3 : 5 } diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx index 60b96e86..e2816a92 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx @@ -22,10 +22,7 @@ import DetailFacts from "@/components/jellyseerr/DetailFacts"; import RequestModal from "@/components/jellyseerr/RequestModal"; import { OverviewText } from "@/components/OverviewText"; import { ParallaxScrollView } from "@/components/ParallaxPage"; -import { - type OptionGroup, - PlatformOptionsMenu, -} from "@/components/PlatformOptionsMenu"; +import { PlatformDropdown } from "@/components/PlatformDropdown"; import { JellyserrRatings } from "@/components/Ratings"; import JellyseerrSeasons from "@/components/series/JellyseerrSeasons"; import { ItemActions } from "@/components/series/SeriesActions"; @@ -66,7 +63,6 @@ const Page: React.FC = () => { const [issueType, setIssueType] = useState(); const [issueMessage, setIssueMessage] = useState(); const [requestBody, _setRequestBody] = useState(); - const [issueTypeMenuOpen, setIssueTypeMenuOpen] = useState(false); const advancedReqModalRef = useRef(null); const bottomSheetModalRef = useRef(null); @@ -158,30 +154,24 @@ const Page: React.FC = () => { [details], ); - const issueTypeOptionGroups: OptionGroup[] = useMemo( + const issueTypeOptionGroups = useMemo( () => [ { - id: "issue-types", title: t("jellyseerr.types"), options: Object.entries(IssueTypeName) .reverse() .map(([key, value]) => ({ - id: key, type: "radio" as const, - groupId: "issue-types", label: value, + value: key, selected: key === String(issueType), + onPress: () => setIssueType(key as unknown as IssueType), })), }, ], [issueType, t], ); - const handleIssueTypeSelect = (optionId: string) => { - setIssueType(optionId as unknown as IssueType); - setIssueTypeMenuOpen(false); - }; - useEffect(() => { if (details) { navigation.setOptions({ @@ -390,37 +380,22 @@ const Page: React.FC = () => { - - + + {t("jellyseerr.issue_type")} + + - - {t("jellyseerr.issue_type")} + + + {issueType + ? IssueTypeName[issueType] + : t("jellyseerr.select_an_issue")} - setIssueTypeMenuOpen(true)} - > - - {issueType - ? IssueTypeName[issueType] - : t("jellyseerr.select_an_issue")} - - } title={t("jellyseerr.types")} - open={issueTypeMenuOpen} - onOpenChange={setIssueTypeMenuOpen} - onOptionSelect={handleIssueTypeSelect} - expoUIConfig={{ - hostStyle: { flex: 1 }, - }} - bottomSheetConfig={{ - enableDynamicSizing: true, - enablePanDownToClose: true, - }} /> diff --git a/app/(auth)/(tabs)/(libraries)/_layout.tsx b/app/(auth)/(tabs)/(libraries)/_layout.tsx index 89a3e847..60dd9e1e 100644 --- a/app/(auth)/(tabs)/(libraries)/_layout.tsx +++ b/app/(auth)/(tabs)/(libraries)/_layout.tsx @@ -1,85 +1,159 @@ import { Ionicons } from "@expo/vector-icons"; import { Stack } from "expo-router"; -import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { Platform, TouchableOpacity } from "react-native"; -import { LibraryOptionsSheet } from "@/components/settings/LibraryOptionsSheet"; +import { Platform } from "react-native"; +import { PlatformDropdown } from "@/components/PlatformDropdown"; import { nestedTabPageScreenOptions } from "@/components/stacks/NestedTabPageStack"; import { useSettings } from "@/utils/atoms/settings"; export default function IndexLayout() { const { settings, updateSettings, pluginSettings } = useSettings(); - const [optionsSheetOpen, setOptionsSheetOpen] = useState(false); const { t } = useTranslation(); if (!settings?.libraryOptions) return null; return ( - <> - - - !pluginSettings?.libraryOptions?.locked && - !Platform.isTV && ( - setOptionsSheetOpen(true)} - className='flex flex-row items-center justify-center w-9 h-9' - > - - - ), - }} - /> - - {Object.entries(nestedTabPageScreenOptions).map(([name, options]) => ( - - ))} - - - - updateSettings({ - libraryOptions: { - ...settings.libraryOptions, - ...options, - }, - }) - } - disabled={pluginSettings?.libraryOptions?.locked} + + ( + + } + title={t("library.options.display")} + groups={[ + { + title: t("library.options.display"), + options: [ + { + type: "radio", + label: t("library.options.row"), + value: "row", + selected: settings.libraryOptions.display === "row", + onPress: () => + updateSettings({ + libraryOptions: { + ...settings.libraryOptions, + display: "row", + }, + }), + }, + { + type: "radio", + label: t("library.options.list"), + value: "list", + selected: settings.libraryOptions.display === "list", + onPress: () => + updateSettings({ + libraryOptions: { + ...settings.libraryOptions, + display: "list", + }, + }), + }, + ], + }, + { + title: t("library.options.image_style"), + options: [ + { + type: "radio", + label: t("library.options.poster"), + value: "poster", + selected: settings.libraryOptions.imageStyle === "poster", + onPress: () => + updateSettings({ + libraryOptions: { + ...settings.libraryOptions, + imageStyle: "poster", + }, + }), + }, + { + type: "radio", + label: t("library.options.cover"), + value: "cover", + selected: settings.libraryOptions.imageStyle === "cover", + onPress: () => + updateSettings({ + libraryOptions: { + ...settings.libraryOptions, + imageStyle: "cover", + }, + }), + }, + ], + }, + { + title: "Options", + options: [ + { + type: "toggle", + label: t("library.options.show_titles"), + value: settings.libraryOptions.showTitles, + onToggle: () => + updateSettings({ + libraryOptions: { + ...settings.libraryOptions, + showTitles: !settings.libraryOptions.showTitles, + }, + }), + disabled: settings.libraryOptions.imageStyle === "poster", + }, + { + type: "toggle", + label: t("library.options.show_stats"), + value: settings.libraryOptions.showStats, + onToggle: () => + updateSettings({ + libraryOptions: { + ...settings.libraryOptions, + showStats: !settings.libraryOptions.showStats, + }, + }), + }, + ], + }, + ]} + /> + ), + }} /> - + + {Object.entries(nestedTabPageScreenOptions).map(([name, options]) => ( + + ))} + + ); } diff --git a/app/(auth)/(tabs)/(libraries)/index.tsx b/app/(auth)/(tabs)/(libraries)/index.tsx index 7e8bc387..67be860a 100644 --- a/app/(auth)/(tabs)/(libraries)/index.tsx +++ b/app/(auth)/(tabs)/(libraries)/index.tsx @@ -87,8 +87,8 @@ export default function index() { paddingTop: 17, paddingHorizontal: settings?.libraryOptions?.display === "row" ? 0 : 17, paddingBottom: 150, - paddingLeft: insets.left, - paddingRight: insets.right, + paddingLeft: insets.left + 17, + paddingRight: insets.right + 17, }} data={libraries} renderItem={({ item }) => } diff --git a/bun.lock b/bun.lock index 9191042d..55d49cfe 100644 --- a/bun.lock +++ b/bun.lock @@ -7,7 +7,7 @@ "@bottom-tabs/react-navigation": "^0.11.2", "@expo/metro-runtime": "~6.1.1", "@expo/react-native-action-sheet": "^4.1.1", - "@expo/ui": "~0.2.0-beta.4", + "@expo/ui": "^0.2.0-beta.4", "@expo/vector-icons": "^15.0.2", "@gorhom/bottom-sheet": "^5.1.0", "@jellyfin/sdk": "^0.11.0", diff --git a/components/AudioTrackSelector.tsx b/components/AudioTrackSelector.tsx index e2c0ec7f..02e06d3d 100644 --- a/components/AudioTrackSelector.tsx +++ b/components/AudioTrackSelector.tsx @@ -3,7 +3,7 @@ import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Platform, TouchableOpacity, View } from "react-native"; import { Text } from "./common/Text"; -import { type OptionGroup, PlatformOptionsMenu } from "./PlatformOptionsMenu"; +import { type OptionGroup, PlatformDropdown } from "./PlatformDropdown"; interface Props extends React.ComponentProps { source?: MediaSourceInfo; @@ -34,32 +34,25 @@ export const AudioTrackSelector: React.FC = ({ const optionGroups: OptionGroup[] = useMemo( () => [ { - id: "audio-streams", title: "Audio streams", options: audioStreams?.map((audio, idx) => ({ - id: `${audio.Index || idx}`, type: "radio" as const, - groupId: "audio-streams", label: audio.DisplayTitle || `Audio Stream ${idx + 1}`, + value: audio.Index ?? idx, selected: audio.Index === selected, + onPress: () => { + if (audio.Index !== null && audio.Index !== undefined) { + onChange(audio.Index); + } + }, })) || [], }, ], - [audioStreams, selected], + [audioStreams, selected, onChange], ); - const handleOptionSelect = (optionId: string) => { - const selectedStream = audioStreams?.find( - (audio, idx) => `${audio.Index || idx}` === optionId, - ); - if ( - selectedStream && - selectedStream.Index !== null && - selectedStream.Index !== undefined - ) { - onChange(selectedStream.Index); - } + const handleOptionSelect = () => { setOpen(false); }; @@ -84,7 +77,7 @@ export const AudioTrackSelector: React.FC = ({ minWidth: 50, }} > - = ({ maxWidth: 200, }} > - { item: BaseItemDto; @@ -93,7 +93,7 @@ export const MediaSourceSelector: React.FC = ({ minWidth: 50, }} > - = { + type: "radio"; + label: string; + value: T; + selected: boolean; + onPress: () => void; + disabled?: boolean; +}; + +export type ToggleOption = { + type: "toggle"; + label: string; + value: boolean; + onToggle: () => void; + disabled?: boolean; +}; + +export type Option = RadioOption | ToggleOption; + +// Option group structure +export type OptionGroup = { + title?: string; + options: Option[]; +}; + +interface PlatformDropdownProps { + trigger?: React.ReactNode; + title?: string; + groups: OptionGroup[]; + open?: boolean; + onOpenChange?: (open: boolean) => void; + onOptionSelect?: (value?: any) => void; + expoUIConfig?: { + hostStyle?: any; + }; + bottomSheetConfig?: { + enableDynamicSizing?: boolean; + enablePanDownToClose?: boolean; + }; +} + +const ToggleSwitch: React.FC<{ value: boolean }> = ({ value }) => ( + + + +); + +const OptionItem: React.FC<{ option: Option; isLast?: boolean }> = ({ + option, + isLast, +}) => { + const isToggle = option.type === "toggle"; + const handlePress = isToggle ? option.onToggle : option.onPress; + + return ( + <> + + {option.label} + {isToggle ? ( + + ) : option.selected ? ( + + ) : ( + + )} + + {!isLast && ( + + )} + + ); +}; + +const OptionGroupComponent: React.FC<{ group: OptionGroup }> = ({ group }) => ( + + {group.title && ( + + {group.title} + + )} + + {group.options.map((option, index) => ( + + ))} + + +); + +const BottomSheetContent: React.FC<{ + title?: string; + groups: OptionGroup[]; + onOptionSelect?: (value?: any) => void; +}> = ({ title, groups, onOptionSelect }) => { + const insets = useSafeAreaInsets(); + + // Wrap the groups to call onOptionSelect when an option is pressed + const wrappedGroups = groups.map((group) => ({ + ...group, + options: group.options.map((option) => { + if (option.type === "radio") { + return { + ...option, + onPress: () => { + option.onPress(); + onOptionSelect?.(option.value); + }, + }; + } + if (option.type === "toggle") { + return { + ...option, + onToggle: () => { + option.onToggle(); + onOptionSelect?.(option.value); + }, + }; + } + return option; + }), + })); + + return ( + + {title && {title}} + {wrappedGroups.map((group, index) => ( + + ))} + + ); +}; + +export function PlatformDropdown({ + trigger, + title, + groups, + open, + onOpenChange, + onOptionSelect, + expoUIConfig, + bottomSheetConfig, +}: PlatformDropdownProps) { + const { showModal, hideModal } = useGlobalModal(); + + const handlePress = () => { + if (Platform.OS === "android") { + onOpenChange?.(true); + showModal( + , + { + enableDynamicSizing: bottomSheetConfig?.enableDynamicSizing ?? true, + enablePanDownToClose: bottomSheetConfig?.enablePanDownToClose ?? true, + }, + ); + } + }; + + // Close modal when open prop changes to false + useEffect(() => { + if (Platform.OS === "android" && open === false) { + hideModal(); + } + }, [open, hideModal]); + + if (Platform.OS === "ios") { + console.log("[PlatformDropdown iOS] Rendering with groups:", groups.length); + + return ( + + + + + {trigger || } + + + + {groups.flatMap((group, groupIndex) => { + // Check if this group has radio options + const radioOptions = group.options.filter( + (opt) => opt.type === "radio", + ) as RadioOption[]; + const toggleOptions = group.options.filter( + (opt) => opt.type === "toggle", + ) as ToggleOption[]; + + console.log( + `[PlatformDropdown iOS] Group ${groupIndex}: ${radioOptions.length} radio, ${toggleOptions.length} toggle`, + ); + + const items = []; + + // Add Picker for radio options if present + if (radioOptions.length > 0) { + items.push( + opt.label)} + variant='menu' + selectedIndex={radioOptions.findIndex( + (opt) => opt.selected, + )} + onOptionSelected={(event: any) => { + console.log( + "[PlatformDropdown iOS] Picker option selected:", + event.nativeEvent.index, + ); + const index = event.nativeEvent.index; + const selectedOption = radioOptions[index]; + selectedOption?.onPress(); + onOptionSelect?.(selectedOption?.value); + }} + />, + ); + } + + // Add Buttons for toggle options + toggleOptions.forEach((option, optionIndex) => { + items.push( + , + ); + }); + + return items; + })} + + + + ); + } + + // Android: Trigger button for bottom modal + return ( + + {trigger || Open Menu} + + ); +} diff --git a/components/SubtitleTrackSelector.tsx b/components/SubtitleTrackSelector.tsx index 4e97c422..7147f90f 100644 --- a/components/SubtitleTrackSelector.tsx +++ b/components/SubtitleTrackSelector.tsx @@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next"; import { Platform, TouchableOpacity, View } from "react-native"; import { tc } from "@/utils/textTools"; import { Text } from "./common/Text"; -import { type OptionGroup, PlatformOptionsMenu } from "./PlatformOptionsMenu"; +import { type OptionGroup, PlatformDropdown } from "./PlatformDropdown"; interface Props extends React.ComponentProps { source?: MediaSourceInfo; @@ -103,7 +103,7 @@ export const SubtitleTrackSelector: React.FC = ({ maxWidth: 200, }} > - { data: T[]; @@ -44,29 +44,6 @@ const Dropdown = ({ } }, [selected, onSelected]); - const optionGroups: OptionGroup[] = useMemo( - () => [ - { - id: "dropdown-items", - title: label, - options: data.map((item) => { - const key = keyExtractor(item); - const isSelected = - selected?.some((s) => keyExtractor(s) === key) || false; - - return { - id: key, - type: multiple ? "checkbox" : ("radio" as const), - groupId: "dropdown-items", - label: titleExtractor(item) || key, - ...(multiple ? { checked: isSelected } : { selected: isSelected }), - }; - }), - }, - ], - [data, selected, multiple, keyExtractor, titleExtractor, label], - ); - const handleOptionSelect = (optionId: string, value?: any) => { const selectedItem = data.find((item) => keyExtractor(item) === optionId); if (!selectedItem) return; @@ -91,6 +68,45 @@ const Dropdown = ({ } }; + const optionGroups: OptionGroup[] = useMemo( + () => [ + { + title: label, + options: data.map((item) => { + const key = keyExtractor(item); + const isSelected = + selected?.some((s) => keyExtractor(s) === key) || false; + + if (multiple) { + return { + type: "toggle" as const, + label: titleExtractor(item) || key, + value: isSelected, + onToggle: () => handleOptionSelect(key, !isSelected), + }; + } + + return { + type: "radio" as const, + label: titleExtractor(item) || key, + value: key, + selected: isSelected, + onPress: () => handleOptionSelect(key), + }; + }), + }, + ], + [ + data, + selected, + multiple, + keyExtractor, + titleExtractor, + label, + handleOptionSelect, + ], + ); + const getDisplayValue = () => { if (selected?.length !== undefined && selected.length > 0) { return selected.map(titleExtractor).join(","); @@ -120,7 +136,7 @@ const Dropdown = ({ return ( - = ({ onSelect, }) => { const isTv = Platform.isTV; - const [open, setOpen] = useState(false); const keys = useMemo( () => @@ -57,10 +56,9 @@ export const SeasonDropdown: React.FC = ({ const sortByIndex = (a: BaseItemDto, b: BaseItemDto) => Number(a[keys.index]) - Number(b[keys.index]); - const optionGroups: OptionGroup[] = useMemo( + const optionGroups = useMemo( () => [ { - id: "seasons", title: t("item_card.seasons"), options: seasons?.sort(sortByIndex).map((season: any) => { @@ -69,28 +67,18 @@ export const SeasonDropdown: React.FC = ({ season.Name || `Season ${season.IndexNumber}`; return { - id: `${season.Id || season.IndexNumber}`, type: "radio" as const, - groupId: "seasons", label: title, + value: season.Id || season.IndexNumber, selected: Number(season[keys.index]) === Number(seasonIndex), + onPress: () => onSelect(season), }; }) || [], }, ], - [seasons, keys, seasonIndex], + [seasons, keys, seasonIndex, onSelect], ); - const handleSeasonSelect = (optionId: string) => { - const selectedSeason = seasons?.find( - (season: any) => `${season.Id || season.IndexNumber}` === optionId, - ); - if (selectedSeason) { - onSelect(selectedSeason); - } - setOpen(false); - }; - useEffect(() => { if (isTv) return; if (seasons && seasons.length > 0 && seasonIndex === undefined) { @@ -132,36 +120,19 @@ export const SeasonDropdown: React.FC = ({ keys, ]); - const trigger = ( - - setOpen(true)} - > - - {t("item_card.season")} {seasonIndex} - - - - ); - if (isTv) return null; return ( - + + {t("item_card.season")} {seasonIndex} + + + } title={t("item_card.seasons")} - open={open} - onOpenChange={setOpen} - onOptionSelect={handleSeasonSelect} - expoUIConfig={{ - hostStyle: { flex: 1 }, - }} - bottomSheetConfig={{ - enableDynamicSizing: true, - enablePanDownToClose: true, - }} /> ); }; diff --git a/components/settings/AppLanguageSelector.tsx b/components/settings/AppLanguageSelector.tsx index 0aefe3ee..c384a897 100644 --- a/components/settings/AppLanguageSelector.tsx +++ b/components/settings/AppLanguageSelector.tsx @@ -1,12 +1,12 @@ -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; -import { Platform, TouchableOpacity, View, type ViewProps } from "react-native"; +import { Platform, View, type ViewProps } from "react-native"; import { APP_LANGUAGES } from "@/i18n"; import { useSettings } from "@/utils/atoms/settings"; import { Text } from "../common/Text"; import { ListGroup } from "../list/ListGroup"; import { ListItem } from "../list/ListItem"; -import { type OptionGroup, PlatformOptionsMenu } from "../PlatformOptionsMenu"; +import { PlatformDropdown } from "../PlatformDropdown"; interface Props extends ViewProps {} @@ -14,55 +14,32 @@ export const AppLanguageSelector: React.FC = () => { const isTv = Platform.isTV; const { settings, updateSettings } = useSettings(); const { t } = useTranslation(); - const [open, setOpen] = useState(false); - const optionGroups: OptionGroup[] = useMemo(() => { + const optionGroups = useMemo(() => { const options = [ { - id: "system", type: "radio" as const, - groupId: "languages", label: t("home.settings.languages.system"), + value: "system", selected: !settings?.preferedLanguage, + onPress: () => updateSettings({ preferedLanguage: undefined }), }, ...APP_LANGUAGES.map((lang) => ({ - id: lang.value, type: "radio" as const, - groupId: "languages", label: lang.label, + value: lang.value, selected: lang.value === settings?.preferedLanguage, + onPress: () => updateSettings({ preferedLanguage: lang.value }), })), ]; return [ { - id: "languages", title: t("home.settings.languages.title"), options, }, ]; - }, [settings?.preferedLanguage, t]); - - const handleOptionSelect = (optionId: string) => { - if (optionId === "system") { - updateSettings({ preferedLanguage: undefined }); - } else { - updateSettings({ preferedLanguage: optionId }); - } - setOpen(false); - }; - - const trigger = ( - setOpen(true)} - > - - {APP_LANGUAGES.find((l) => l.value === settings?.preferedLanguage) - ?.label || t("home.settings.languages.system")} - - - ); + }, [settings?.preferedLanguage, t, updateSettings]); if (isTv) return null; if (!settings) return null; @@ -71,20 +48,18 @@ export const AppLanguageSelector: React.FC = () => { - + + {APP_LANGUAGES.find( + (l) => l.value === settings?.preferedLanguage, + )?.label || t("home.settings.languages.system")} + + + } title={t("home.settings.languages.title")} - open={open} - onOpenChange={setOpen} - onOptionSelect={handleOptionSelect} - expoUIConfig={{ - hostStyle: { flex: 1 }, - }} - bottomSheetConfig={{ - enableDynamicSizing: true, - enablePanDownToClose: true, - }} /> diff --git a/components/settings/AudioToggles.tsx b/components/settings/AudioToggles.tsx index 966311a1..aa6d2bd6 100644 --- a/components/settings/AudioToggles.tsx +++ b/components/settings/AudioToggles.tsx @@ -1,20 +1,19 @@ import { Ionicons } from "@expo/vector-icons"; -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; -import { Platform, TouchableOpacity, View, type ViewProps } from "react-native"; +import { Platform, View, type ViewProps } from "react-native"; import { Switch } from "react-native-gesture-handler"; import { useSettings } from "@/utils/atoms/settings"; import { Text } from "../common/Text"; import { ListGroup } from "../list/ListGroup"; import { ListItem } from "../list/ListItem"; -import { type OptionGroup, PlatformOptionsMenu } from "../PlatformOptionsMenu"; +import { PlatformDropdown } from "../PlatformDropdown"; import { useMedia } from "./MediaContext"; interface Props extends ViewProps {} export const AudioToggles: React.FC = ({ ...props }) => { const isTv = Platform.isTV; - const [open, setOpen] = useState(false); const media = useMedia(); const { pluginSettings } = useSettings(); @@ -22,69 +21,39 @@ export const AudioToggles: React.FC = ({ ...props }) => { const cultures = media.cultures; const { t } = useTranslation(); - const optionGroups: OptionGroup[] = useMemo(() => { + const optionGroups = useMemo(() => { const options = [ { - id: "none", type: "radio" as const, - groupId: "audio-languages", label: t("home.settings.audio.none"), + value: "none", selected: !settings?.defaultAudioLanguage, + onPress: () => updateSettings({ defaultAudioLanguage: null }), }, ...(cultures?.map((culture) => ({ - id: - culture.ThreeLetterISOLanguageName || - culture.DisplayName || - "unknown", type: "radio" as const, - groupId: "audio-languages", label: culture.DisplayName || culture.ThreeLetterISOLanguageName || "Unknown", + value: + culture.ThreeLetterISOLanguageName || + culture.DisplayName || + "unknown", selected: culture.ThreeLetterISOLanguageName === settings?.defaultAudioLanguage?.ThreeLetterISOLanguageName, + onPress: () => updateSettings({ defaultAudioLanguage: culture }), })) || []), ]; return [ { - id: "audio-languages", title: t("home.settings.audio.language"), options, }, ]; - }, [cultures, settings?.defaultAudioLanguage, t]); - - const handleOptionSelect = (optionId: string) => { - if (optionId === "none") { - updateSettings({ defaultAudioLanguage: null }); - } else { - const selectedCulture = cultures?.find( - (culture) => - (culture.ThreeLetterISOLanguageName || culture.DisplayName) === - optionId, - ); - if (selectedCulture) { - updateSettings({ defaultAudioLanguage: selectedCulture }); - } - } - setOpen(false); - }; - - const trigger = ( - setOpen(true)} - > - - {settings?.defaultAudioLanguage?.DisplayName || - t("home.settings.audio.none")} - - - - ); + }, [cultures, settings?.defaultAudioLanguage, t, updateSettings]); if (isTv) return null; if (!settings) return null; @@ -112,20 +81,22 @@ export const AudioToggles: React.FC = ({ ...props }) => { /> - + + {settings?.defaultAudioLanguage?.DisplayName || + t("home.settings.audio.none")} + + + + } title={t("home.settings.audio.language")} - open={open} - onOpenChange={setOpen} - onOptionSelect={handleOptionSelect} - expoUIConfig={{ - hostStyle: { flex: 1 }, - }} - bottomSheetConfig={{ - enableDynamicSizing: true, - enablePanDownToClose: true, - }} /> diff --git a/components/stacks/NestedTabPageStack.tsx b/components/stacks/NestedTabPageStack.tsx index ec4ba1e8..41e6f880 100644 --- a/components/stacks/NestedTabPageStack.tsx +++ b/components/stacks/NestedTabPageStack.tsx @@ -1,5 +1,6 @@ import type { ParamListBase, RouteProp } from "@react-navigation/native"; import type { NativeStackNavigationOptions } from "@react-navigation/native-stack"; +import { Platform } from "react-native"; import { HeaderBackButton } from "../common/HeaderBackButton"; type ICommonScreenOptions = @@ -12,9 +13,9 @@ type ICommonScreenOptions = export const commonScreenOptions: ICommonScreenOptions = { title: "", headerShown: true, - headerTransparent: true, + headerTransparent: Platform.OS === "ios", headerShadowVisible: false, - headerBlurEffect: "none", + headerBlurEffect: Platform.OS === "ios" ? "none" : undefined, headerLeft: () => , }; diff --git a/package.json b/package.json index 147bba61..e2a170f8 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@bottom-tabs/react-navigation": "^0.11.2", "@expo/metro-runtime": "~6.1.1", "@expo/react-native-action-sheet": "^4.1.1", - "@expo/ui": "~0.2.0-beta.4", + "@expo/ui": "^0.2.0-beta.4", "@expo/vector-icons": "^15.0.2", "@gorhom/bottom-sheet": "^5.1.0", "@jellyfin/sdk": "^0.11.0",