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

@@ -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>