This commit is contained in:
Fredrik Burmester
2025-09-30 13:07:15 +02:00
parent 7a11f4a54b
commit afe57d4c76
9 changed files with 346 additions and 311 deletions

View File

@@ -88,7 +88,6 @@ export const AudioTrackSelector: React.FC<Props> = ({
hostStyle: { flex: 1 },
}}
bottomSheetConfig={{
enableDynamicSizing: true,
enablePanDownToClose: true,
}}
/>

View File

@@ -136,7 +136,6 @@ export const BitrateSelector: React.FC<Props> = ({
hostStyle: { flex: 1 },
}}
bottomSheetConfig={{
enableDynamicSizing: true,
enablePanDownToClose: true,
}}
/>

View File

@@ -104,7 +104,6 @@ export const MediaSourceSelector: React.FC<Props> = ({
hostStyle: { flex: 1 },
}}
bottomSheetConfig={{
enableDynamicSizing: true,
enablePanDownToClose: true,
}}
/>

View File

@@ -193,7 +193,7 @@ export function PlatformDropdown({
onOptionSelect={onOptionSelect}
/>,
{
enableDynamicSizing: bottomSheetConfig?.enableDynamicSizing ?? true,
snapPoints: ["90%"],
enablePanDownToClose: bottomSheetConfig?.enablePanDownToClose ?? true,
},
);
@@ -214,7 +214,7 @@ export function PlatformDropdown({
<Host style={expoUIConfig?.hostStyle}>
<ContextMenu>
<ContextMenu.Trigger>
<View className='w-9 h-9 flex items-center justify-center'>
<View className=''>
{trigger || <Button variant='bordered'>Show Menu</Button>}
</View>
</ContextMenu.Trigger>

View File

@@ -114,7 +114,6 @@ export const SubtitleTrackSelector: React.FC<Props> = ({
hostStyle: { flex: 1 },
}}
bottomSheetConfig={{
enableDynamicSizing: true,
enablePanDownToClose: true,
}}
/>

View File

@@ -1,158 +0,0 @@
import {
type PropsWithChildren,
type ReactNode,
useEffect,
useMemo,
useState,
} from "react";
import { Platform, TouchableOpacity, View, type ViewProps } from "react-native";
import { Text } from "@/components/common/Text";
import DisabledSetting from "@/components/settings/DisabledSetting";
import { type OptionGroup, PlatformDropdown } from "../PlatformDropdown";
interface Props<T> {
data: T[];
disabled?: boolean;
placeholderText?: string;
keyExtractor: (item: T) => string;
titleExtractor: (item: T) => string | undefined;
title: string | ReactNode;
label: string;
onSelected: (...item: T[]) => void;
multiple?: boolean;
}
const Dropdown = <T,>({
data,
disabled,
placeholderText,
keyExtractor,
titleExtractor,
title,
label,
onSelected,
multiple = false,
...props
}: PropsWithChildren<Props<T> & ViewProps>) => {
const isTv = Platform.isTV;
const [open, setOpen] = useState(false);
const [selected, setSelected] = useState<T[]>();
useEffect(() => {
if (selected !== undefined) {
onSelected(...selected);
}
}, [selected, onSelected]);
const handleOptionSelect = (optionId: string, value?: any) => {
const selectedItem = data.find((item) => keyExtractor(item) === optionId);
if (!selectedItem) return;
if (multiple) {
setSelected((prev) => {
const prevItems = prev || [];
if (value) {
// Add item if not already selected
if (!prevItems.some((s) => keyExtractor(s) === optionId)) {
return [...prevItems, selectedItem];
}
return prevItems;
} else {
// Remove item
return prevItems.filter((s) => keyExtractor(s) !== optionId);
}
});
} else {
setSelected([selectedItem]);
setOpen(false);
}
};
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(",");
}
return placeholderText || "";
};
const trigger =
typeof title === "string" ? (
<View className='flex flex-col'>
<Text className='opacity-50 mb-1 text-xs'>{title}</Text>
<TouchableOpacity
className='bg-neutral-900 h-10 rounded-xl border-neutral-800 border px-3 py-2 flex flex-row items-center justify-between'
onPress={() => setOpen(true)}
disabled={disabled}
>
<Text numberOfLines={1}>{getDisplayValue()}</Text>
</TouchableOpacity>
</View>
) : (
<TouchableOpacity onPress={() => setOpen(true)} disabled={disabled}>
{title}
</TouchableOpacity>
);
if (isTv) return null;
return (
<DisabledSetting disabled={disabled === true} showText={false} {...props}>
<PlatformDropdown
groups={optionGroups}
trigger={trigger}
title={label}
open={open}
onOpenChange={setOpen}
onOptionSelect={handleOptionSelect}
expoUIConfig={{
hostStyle: { flex: 1 },
}}
bottomSheetConfig={{
enableDynamicSizing: true,
enablePanDownToClose: true,
}}
/>
</DisabledSetting>
);
};
export default Dropdown;

View File

@@ -8,10 +8,10 @@ import type { BottomSheetModalMethods } from "@gorhom/bottom-sheet/lib/typescrip
import { useQuery } from "@tanstack/react-query";
import { forwardRef, useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { View, type ViewProps } from "react-native";
import { TouchableOpacity, View, type ViewProps } from "react-native";
import { Button } from "@/components/Button";
import Dropdown from "@/components/common/Dropdown";
import { Text } from "@/components/common/Text";
import { PlatformDropdown } from "@/components/PlatformDropdown";
import { useJellyseerr } from "@/hooks/useJellyseerr";
import type {
QualityProfile,
@@ -138,6 +138,115 @@ const RequestModal = forwardRef<
});
}, [requestBody?.seasons]);
const pathTitleExtractor = (item: RootFolder) =>
`${item.path} (${item.freeSpace.bytesToReadable()})`;
const qualityProfileOptions = useMemo(
() => [
{
title: t("jellyseerr.quality_profile"),
options:
defaultServiceDetails?.profiles.map((profile) => ({
type: "radio" as const,
label: profile.name,
value: profile.id.toString(),
selected:
(requestOverrides.profileId || defaultProfile?.id) ===
profile.id,
onPress: () =>
setRequestOverrides((prev) => ({
...prev,
profileId: profile.id,
})),
})) || [],
},
],
[
defaultServiceDetails?.profiles,
defaultProfile,
requestOverrides.profileId,
t,
],
);
const rootFolderOptions = useMemo(
() => [
{
title: t("jellyseerr.root_folder"),
options:
defaultServiceDetails?.rootFolders.map((folder) => ({
type: "radio" as const,
label: pathTitleExtractor(folder),
value: folder.id.toString(),
selected:
(requestOverrides.rootFolder || defaultFolder?.path) ===
folder.path,
onPress: () =>
setRequestOverrides((prev) => ({
...prev,
rootFolder: folder.path,
})),
})) || [],
},
],
[
defaultServiceDetails?.rootFolders,
defaultFolder,
requestOverrides.rootFolder,
t,
],
);
const tagsOptions = useMemo(
() => [
{
title: t("jellyseerr.tags"),
options:
defaultServiceDetails?.tags.map((tag) => ({
type: "toggle" as const,
label: tag.label,
value:
requestOverrides.tags?.includes(tag.id) ||
defaultTags.some((dt) => dt.id === tag.id),
onToggle: () =>
setRequestOverrides((prev) => {
const currentTags = prev.tags || defaultTags.map((t) => t.id);
const hasTag = currentTags.includes(tag.id);
return {
...prev,
tags: hasTag
? currentTags.filter((id) => id !== tag.id)
: [...currentTags, tag.id],
};
}),
})) || [],
},
],
[defaultServiceDetails?.tags, defaultTags, requestOverrides.tags, t],
);
const usersOptions = useMemo(
() => [
{
title: t("jellyseerr.request_as"),
options:
users?.map((user) => ({
type: "radio" as const,
label: user.displayName,
value: user.id.toString(),
selected:
(requestOverrides.userId || jellyseerrUser?.id) === user.id,
onPress: () =>
setRequestOverrides((prev) => ({
...prev,
userId: user.id,
})),
})) || [],
},
],
[users, jellyseerrUser, requestOverrides.userId, t],
);
const request = useCallback(() => {
const body = {
is4k: defaultService?.is4k || defaultServiceDetails?.server.is4k,
@@ -163,9 +272,6 @@ const RequestModal = forwardRef<
defaultTags,
]);
const pathTitleExtractor = (item: RootFolder) =>
`${item.path} (${item.freeSpace.bytesToReadable()})`;
return (
<BottomSheetModal
ref={ref}
@@ -199,70 +305,104 @@ const RequestModal = forwardRef<
<View className='flex flex-col space-y-2'>
{defaultService && defaultServiceDetails && users && (
<>
<Dropdown
data={defaultServiceDetails.profiles}
titleExtractor={(item) => item.name}
placeholderText={
requestOverrides.profileName || defaultProfile.name
}
keyExtractor={(item) => item.id.toString()}
label={t("jellyseerr.quality_profile")}
onSelected={(item) =>
item &&
setRequestOverrides((prev) => ({
...prev,
profileId: item?.id,
}))
}
title={t("jellyseerr.quality_profile")}
/>
<Dropdown
data={defaultServiceDetails.rootFolders}
titleExtractor={pathTitleExtractor}
placeholderText={
defaultFolder ? pathTitleExtractor(defaultFolder) : ""
}
keyExtractor={(item) => item.id.toString()}
label={t("jellyseerr.root_folder")}
onSelected={(item) =>
item &&
setRequestOverrides((prev) => ({
...prev,
rootFolder: item.path,
}))
}
title={t("jellyseerr.root_folder")}
/>
<Dropdown
multiple
data={defaultServiceDetails.tags}
titleExtractor={(item) => item.label}
placeholderText={defaultTags.map((t) => t.label).join(",")}
keyExtractor={(item) => item.id.toString()}
label={t("jellyseerr.tags")}
onSelected={(...selected) =>
setRequestOverrides((prev) => ({
...prev,
tags: selected.map((i) => i.id),
}))
}
title={t("jellyseerr.tags")}
/>
<Dropdown
data={users}
titleExtractor={(item) => item.displayName}
placeholderText={jellyseerrUser!.displayName}
keyExtractor={(item) => item.id.toString() || ""}
label={t("jellyseerr.request_as")}
onSelected={(item) =>
item &&
setRequestOverrides((prev) => ({
...prev,
userId: item?.id,
}))
}
title={t("jellyseerr.request_as")}
/>
<View className='flex flex-col'>
<Text className='opacity-50 mb-1 text-xs'>
{t("jellyseerr.quality_profile")}
</Text>
<PlatformDropdown
groups={qualityProfileOptions}
trigger={
<TouchableOpacity className='bg-neutral-900 h-10 rounded-xl border-neutral-800 border px-3 py-2 flex flex-row items-center justify-between'>
<Text numberOfLines={1}>
{defaultServiceDetails.profiles.find(
(p) =>
p.id ===
(requestOverrides.profileId ||
defaultProfile?.id),
)?.name || defaultProfile?.name}
</Text>
</TouchableOpacity>
}
title={t("jellyseerr.quality_profile")}
/>
</View>
<View className='flex flex-col'>
<Text className='opacity-50 mb-1 text-xs'>
{t("jellyseerr.root_folder")}
</Text>
<PlatformDropdown
groups={rootFolderOptions}
trigger={
<TouchableOpacity className='bg-neutral-900 h-10 rounded-xl border-neutral-800 border px-3 py-2 flex flex-row items-center justify-between'>
<Text numberOfLines={1}>
{defaultServiceDetails.rootFolders.find(
(f) =>
f.path ===
(requestOverrides.rootFolder ||
defaultFolder?.path),
)
? pathTitleExtractor(
defaultServiceDetails.rootFolders.find(
(f) =>
f.path ===
(requestOverrides.rootFolder ||
defaultFolder?.path),
)!,
)
: pathTitleExtractor(defaultFolder!)}
</Text>
</TouchableOpacity>
}
title={t("jellyseerr.root_folder")}
/>
</View>
<View className='flex flex-col'>
<Text className='opacity-50 mb-1 text-xs'>
{t("jellyseerr.tags")}
</Text>
<PlatformDropdown
groups={tagsOptions}
trigger={
<TouchableOpacity className='bg-neutral-900 h-10 rounded-xl border-neutral-800 border px-3 py-2 flex flex-row items-center justify-between'>
<Text numberOfLines={1}>
{requestOverrides.tags
? defaultServiceDetails.tags
.filter((t) =>
requestOverrides.tags!.includes(t.id),
)
.map((t) => t.label)
.join(", ") ||
defaultTags.map((t) => t.label).join(", ")
: defaultTags.map((t) => t.label).join(", ")}
</Text>
</TouchableOpacity>
}
title={t("jellyseerr.tags")}
/>
</View>
<View className='flex flex-col'>
<Text className='opacity-50 mb-1 text-xs'>
{t("jellyseerr.request_as")}
</Text>
<PlatformDropdown
groups={usersOptions}
trigger={
<TouchableOpacity className='bg-neutral-900 h-10 rounded-xl border-neutral-800 border px-3 py-2 flex flex-row items-center justify-between'>
<Text numberOfLines={1}>
{users.find(
(u) =>
u.id ===
(requestOverrides.userId || jellyseerrUser?.id),
)?.displayName || jellyseerrUser!.displayName}
</Text>
</TouchableOpacity>
}
title={t("jellyseerr.request_as")}
/>
</View>
</>
)}
</View>

View File

@@ -5,10 +5,10 @@ import { TFunction } from "i18next";
import type React from "react";
import { useEffect, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Linking, Platform, Switch, TouchableOpacity } from "react-native";
import { Linking, Platform, Switch, View } from "react-native";
import { toast } from "sonner-native";
import { BITRATES } from "@/components/BitrateSelector";
import Dropdown from "@/components/common/Dropdown";
import { PlatformDropdown } from "@/components/PlatformDropdown";
import DisabledSetting from "@/components/settings/DisabledSetting";
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
import { ScreenOrientationEnum, useSettings } from "@/utils/atoms/settings";
@@ -89,6 +89,55 @@ export const OtherSettings: React.FC = () => {
[],
);
const orientationOptions = useMemo(
() => [
{
title: t("home.settings.other.orientation"),
options: orientations.map((orientation) => ({
type: "radio" as const,
label: t(ScreenOrientationEnum[orientation]),
value: String(orientation),
selected: orientation === settings?.defaultVideoOrientation,
onPress: () =>
updateSettings({ defaultVideoOrientation: orientation }),
})),
},
],
[orientations, settings?.defaultVideoOrientation, t, updateSettings],
);
const bitrateOptions = useMemo(
() => [
{
title: t("home.settings.other.default_quality"),
options: BITRATES.map((bitrate) => ({
type: "radio" as const,
label: bitrate.key,
value: bitrate.key,
selected: bitrate.key === settings?.defaultBitrate?.key,
onPress: () => updateSettings({ defaultBitrate: bitrate }),
})),
},
],
[settings?.defaultBitrate?.key, t, updateSettings],
);
const autoPlayEpisodeOptions = useMemo(
() => [
{
title: t("home.settings.other.max_auto_play_episode_count"),
options: AUTOPLAY_EPISODES_COUNT(t).map((item) => ({
type: "radio" as const,
label: item.key,
value: item.key,
selected: item.key === settings?.maxAutoPlayEpisodeCount?.key,
onPress: () => updateSettings({ maxAutoPlayEpisodeCount: item }),
})),
},
],
[settings?.maxAutoPlayEpisodeCount?.key, t, updateSettings],
);
if (!settings) return null;
return (
@@ -114,16 +163,10 @@ export const OtherSettings: React.FC = () => {
settings.followDeviceOrientation
}
>
<Dropdown
data={orientations}
disabled={
pluginSettings?.defaultVideoOrientation?.locked ||
settings.followDeviceOrientation
}
keyExtractor={String}
titleExtractor={(item) => t(ScreenOrientationEnum[item])}
title={
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<PlatformDropdown
groups={orientationOptions}
trigger={
<View className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>
{t(
orientationTranslations[
@@ -136,12 +179,9 @@ export const OtherSettings: React.FC = () => {
size={18}
color='#5A5960'
/>
</TouchableOpacity>
}
label={t("home.settings.other.orientation")}
onSelected={(defaultVideoOrientation) =>
updateSettings({ defaultVideoOrientation })
</View>
}
title={t("home.settings.other.orientation")}
/>
</ListItem>
@@ -214,13 +254,10 @@ export const OtherSettings: React.FC = () => {
title={t("home.settings.other.default_quality")}
disabled={pluginSettings?.defaultBitrate?.locked}
>
<Dropdown
data={BITRATES}
disabled={pluginSettings?.defaultBitrate?.locked}
keyExtractor={(item) => item.key}
titleExtractor={(item) => item.key}
title={
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<PlatformDropdown
groups={bitrateOptions}
trigger={
<View className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>
{settings.defaultBitrate?.key}
</Text>
@@ -229,10 +266,9 @@ export const OtherSettings: React.FC = () => {
size={18}
color='#5A5960'
/>
</TouchableOpacity>
</View>
}
label={t("home.settings.other.default_quality")}
onSelected={(defaultBitrate) => updateSettings({ defaultBitrate })}
title={t("home.settings.other.default_quality")}
/>
</ListItem>
<ListItem
@@ -248,12 +284,10 @@ export const OtherSettings: React.FC = () => {
/>
</ListItem>
<ListItem title={t("home.settings.other.max_auto_play_episode_count")}>
<Dropdown
data={AUTOPLAY_EPISODES_COUNT(t)}
keyExtractor={(item) => item.key}
titleExtractor={(item) => item.key}
title={
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<PlatformDropdown
groups={autoPlayEpisodeOptions}
trigger={
<View className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>
{t(settings?.maxAutoPlayEpisodeCount.key)}
</Text>
@@ -262,12 +296,9 @@ export const OtherSettings: React.FC = () => {
size={18}
color='#5A5960'
/>
</TouchableOpacity>
}
label={t("home.settings.other.max_auto_play_episode_count")}
onSelected={(maxAutoPlayEpisodeCount) =>
updateSettings({ maxAutoPlayEpisodeCount })
</View>
}
title={t("home.settings.other.max_auto_play_episode_count")}
/>
</ListItem>
</ListGroup>

View File

@@ -1,14 +1,15 @@
import { Ionicons } from "@expo/vector-icons";
import { SubtitlePlaybackMode } from "@jellyfin/sdk/lib/generated-client";
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 Dropdown from "@/components/common/Dropdown";
import { Stepper } from "@/components/inputs/Stepper";
import { useSettings } from "@/utils/atoms/settings";
import { Text } from "../common/Text";
import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
import { PlatformDropdown } from "../PlatformDropdown";
import { useMedia } from "./MediaContext";
interface Props extends ViewProps {}
@@ -22,9 +23,6 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
const cultures = media.cultures;
const { t } = useTranslation();
if (isTv) return null;
if (!settings) return null;
const subtitleModes = [
SubtitlePlaybackMode.Default,
SubtitlePlaybackMode.Smart,
@@ -42,6 +40,57 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
[SubtitlePlaybackMode.None]: "home.settings.subtitles.modes.None",
};
const subtitleLanguageOptionGroups = useMemo(() => {
const options = [
{
type: "radio" as const,
label: t("home.settings.subtitles.none"),
value: "none",
selected: !settings?.defaultSubtitleLanguage,
onPress: () => updateSettings({ defaultSubtitleLanguage: null }),
},
...(cultures?.map((culture) => ({
type: "radio" as const,
label: culture.DisplayName || "Unknown",
value:
culture.ThreeLetterISOLanguageName ||
culture.DisplayName ||
"unknown",
selected:
culture.ThreeLetterISOLanguageName ===
settings?.defaultSubtitleLanguage?.ThreeLetterISOLanguageName,
onPress: () => updateSettings({ defaultSubtitleLanguage: culture }),
})) || []),
];
return [
{
title: t("home.settings.subtitles.language"),
options,
},
];
}, [cultures, settings?.defaultSubtitleLanguage, t, updateSettings]);
const subtitleModeOptionGroups = useMemo(() => {
const options = subtitleModes.map((mode) => ({
type: "radio" as const,
label: t(subtitleModeKeys[mode]) || String(mode),
value: String(mode),
selected: mode === settings?.subtitleMode,
onPress: () => updateSettings({ subtitleMode: mode }),
}));
return [
{
title: t("home.settings.subtitles.subtitle_mode"),
options,
},
];
}, [settings?.subtitleMode, t, updateSettings]);
if (isTv) return null;
if (!settings) return null;
return (
<View {...props}>
<ListGroup
@@ -53,20 +102,10 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
}
>
<ListItem title={t("home.settings.subtitles.subtitle_language")}>
<Dropdown
data={[
{
DisplayName: t("home.settings.subtitles.none"),
ThreeLetterISOLanguageName: "none-subs",
},
...(cultures ?? []),
]}
keyExtractor={(item) =>
item?.ThreeLetterISOLanguageName ?? "unknown"
}
titleExtractor={(item) => item?.DisplayName}
title={
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<PlatformDropdown
groups={subtitleLanguageOptionGroups}
trigger={
<View className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>
{settings?.defaultSubtitleLanguage?.DisplayName ||
t("home.settings.subtitles.none")}
@@ -76,18 +115,9 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
size={18}
color='#5A5960'
/>
</TouchableOpacity>
}
label={t("home.settings.subtitles.language")}
onSelected={(defaultSubtitleLanguage) =>
updateSettings({
defaultSubtitleLanguage:
defaultSubtitleLanguage.DisplayName ===
t("home.settings.subtitles.none")
? null
: defaultSubtitleLanguage,
})
</View>
}
title={t("home.settings.subtitles.language")}
/>
</ListItem>
@@ -95,13 +125,10 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
title={t("home.settings.subtitles.subtitle_mode")}
disabled={pluginSettings?.subtitleMode?.locked}
>
<Dropdown
data={subtitleModes}
disabled={pluginSettings?.subtitleMode?.locked}
keyExtractor={String}
titleExtractor={(item) => t(subtitleModeKeys[item]) || String(item)}
title={
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<PlatformDropdown
groups={subtitleModeOptionGroups}
trigger={
<View className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>
{t(subtitleModeKeys[settings?.subtitleMode]) ||
t("home.settings.subtitles.loading")}
@@ -111,10 +138,9 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
size={18}
color='#5A5960'
/>
</TouchableOpacity>
</View>
}
label={t("home.settings.subtitles.subtitle_mode")}
onSelected={(subtitleMode) => updateSettings({ subtitleMode })}
title={t("home.settings.subtitles.subtitle_mode")}
/>
</ListItem>