diff --git a/app/(auth)/(tabs)/(home)/_layout.tsx b/app/(auth)/(tabs)/(home)/_layout.tsx
index f6c185a8..3dd18ab4 100644
--- a/app/(auth)/(tabs)/(home)/_layout.tsx
+++ b/app/(auth)/(tabs)/(home)/_layout.tsx
@@ -26,14 +26,18 @@ export default function IndexLayout() {
headerShadowVisible: false,
headerRight: () => (
- {!Platform.isTV && }
- {
- router.push("/(auth)/settings");
- }}
- >
-
-
+ {!Platform.isTV && (
+ <>
+
+ {
+ router.push("/(auth)/settings");
+ }}
+ >
+
+
+ >
+ )}
),
}}
diff --git a/app/(auth)/(tabs)/(home)/intro/page.tsx b/app/(auth)/(tabs)/(home)/intro/page.tsx
index 7aae4ae2..dcbd3bf9 100644
--- a/app/(auth)/(tabs)/(home)/intro/page.tsx
+++ b/app/(auth)/(tabs)/(home)/intro/page.tsx
@@ -5,7 +5,7 @@ import { Feather, Ionicons } from "@expo/vector-icons";
import { Image } from "expo-image";
import { useFocusEffect, useRouter } from "expo-router";
import { useCallback } from "react";
-import {useTranslation } from "react-i18next";
+import { useTranslation } from "react-i18next";
import { Linking, TouchableOpacity, View } from "react-native";
export default function page() {
@@ -30,10 +30,10 @@ export default function page() {
- {t("home.intro.features_title")}
-
- {t("home.intro.features_description")}
+
+ {t("home.intro.features_title")}
+ {t("home.intro.features_description")}
- {t("home.intro.downloads_feature_title")}
+
+ {t("home.intro.downloads_feature_title")}
+
{t("home.intro.downloads_feature_description")}
@@ -94,7 +96,9 @@ export default function page() {
- {t("home.intro.centralised_settings_plugin_title")}
+
+ {t("home.intro.centralised_settings_plugin_title")}
+
{t("home.intro.centralised_settings_plugin_description")}{" "}
- {t("home.intro.go_to_settings_button")}
+
+ {t("home.intro.go_to_settings_button")}
+
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 7796050d..52e64bc5 100644
--- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx
+++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx
@@ -36,9 +36,9 @@ import React, {
useRef,
useState,
} from "react";
-import { TouchableOpacity, View } from "react-native";
+import { Platform, TouchableOpacity, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
-import * as DropdownMenu from "zeego/dropdown-menu";
+const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import RequestModal from "@/components/jellyseerr/RequestModal";
import { ANIME_KEYWORD_ID } from "@/utils/jellyseerr/server/api/themoviedb/constants";
import { MediaRequestBody } from "@/utils/jellyseerr/server/interfaces/api/requestInterfaces";
diff --git a/bun.lockb b/bun.lockb
index 5d6a7a4a..7d5dc622 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/components/AudioTrackSelector.tsx b/components/AudioTrackSelector.tsx
index 579dab79..ad4b08c0 100644
--- a/components/AudioTrackSelector.tsx
+++ b/components/AudioTrackSelector.tsx
@@ -1,7 +1,7 @@
import { MediaSourceInfo } from "@jellyfin/sdk/lib/generated-client/models";
import { useMemo } from "react";
-import { TouchableOpacity, View } from "react-native";
-import * as DropdownMenu from "zeego/dropdown-menu";
+import { Platform, TouchableOpacity, View } from "react-native";
+const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import { Text } from "./common/Text";
import { useTranslation } from "react-i18next";
@@ -17,6 +17,7 @@ export const AudioTrackSelector: React.FC = ({
selected,
...props
}) => {
+ if (Platform.isTV) return null;
const audioStreams = useMemo(
() => source?.MediaStreams?.filter((x) => x.Type === "Audio"),
[source]
diff --git a/components/BitrateSelector.tsx b/components/BitrateSelector.tsx
index 94cf14e1..31b4f2cd 100644
--- a/components/BitrateSelector.tsx
+++ b/components/BitrateSelector.tsx
@@ -1,5 +1,5 @@
-import { TouchableOpacity, View } from "react-native";
-import * as DropdownMenu from "zeego/dropdown-menu";
+import { Platform, TouchableOpacity, View } from "react-native";
+const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import { Text } from "./common/Text";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
@@ -54,6 +54,7 @@ export const BitrateSelector: React.FC = ({
inverted,
...props
}) => {
+ if (Platform.isTV) return null;
const sorted = useMemo(() => {
if (inverted)
return BITRATES.sort(
diff --git a/components/MediaSourceSelector.tsx b/components/MediaSourceSelector.tsx
index 7a0ea2ca..744b8a10 100644
--- a/components/MediaSourceSelector.tsx
+++ b/components/MediaSourceSelector.tsx
@@ -3,8 +3,8 @@ import {
MediaSourceInfo,
} from "@jellyfin/sdk/lib/generated-client/models";
import { useMemo } from "react";
-import { TouchableOpacity, View } from "react-native";
-import * as DropdownMenu from "zeego/dropdown-menu";
+import { Platform, TouchableOpacity, View } from "react-native";
+const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import { Text } from "./common/Text";
import { useTranslation } from "react-i18next";
@@ -20,6 +20,7 @@ export const MediaSourceSelector: React.FC = ({
selected,
...props
}) => {
+ if (Platform.isTV) return null;
const selectedName = useMemo(
() =>
item.MediaSources?.find((x) => x.Id === selected?.Id)?.MediaStreams?.find(
diff --git a/components/SubtitleTrackSelector.tsx b/components/SubtitleTrackSelector.tsx
index 641d4ef0..9b864f29 100644
--- a/components/SubtitleTrackSelector.tsx
+++ b/components/SubtitleTrackSelector.tsx
@@ -2,7 +2,7 @@ import { tc } from "@/utils/textTools";
import { MediaSourceInfo } from "@jellyfin/sdk/lib/generated-client/models";
import { useMemo } from "react";
import { Platform, TouchableOpacity, View } from "react-native";
-import * as DropdownMenu from "zeego/dropdown-menu";
+const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import { Text } from "./common/Text";
import { SubtitleHelper } from "@/utils/SubtitleHelper";
import { useTranslation } from "react-i18next";
@@ -21,6 +21,7 @@ export const SubtitleTrackSelector: React.FC = ({
isTranscoding,
...props
}) => {
+ if (Platform.isTV) return null;
const subtitleStreams = useMemo(() => {
const subtitleHelper = new SubtitleHelper(source?.MediaStreams ?? []);
diff --git a/components/common/Dropdown.tsx b/components/common/Dropdown.tsx
index fec36d2f..84418a92 100644
--- a/components/common/Dropdown.tsx
+++ b/components/common/Dropdown.tsx
@@ -1,22 +1,27 @@
-import * as DropdownMenu from "zeego/dropdown-menu";
-import {TouchableOpacity, View, ViewProps} from "react-native";
-import {Text} from "@/components/common/Text";
-import React, {PropsWithChildren, ReactNode, useEffect, useState} from "react";
+const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
+import { Platform, TouchableOpacity, View, ViewProps } from "react-native";
+import { Text } from "@/components/common/Text";
+import React, {
+ PropsWithChildren,
+ ReactNode,
+ useEffect,
+ useState,
+} from "react";
import DisabledSetting from "@/components/settings/DisabledSetting";
interface Props {
- data: T[]
- disabled?: boolean
- placeholderText?: string,
- keyExtractor: (item: T) => string
- titleExtractor: (item: T) => string | undefined
- title: string | ReactNode,
- label: string,
- onSelected: (...item: T[]) => void
- multi?: boolean
+ data: T[];
+ disabled?: boolean;
+ placeholderText?: string;
+ keyExtractor: (item: T) => string;
+ titleExtractor: (item: T) => string | undefined;
+ title: string | ReactNode;
+ label: string;
+ onSelected: (...item: T[]) => void;
+ multi?: boolean;
}
-const Dropdown = ({
+const Dropdown = ({
data,
disabled,
placeholderText,
@@ -28,38 +33,32 @@ const Dropdown = ({
multi = false,
...props
}: PropsWithChildren & ViewProps>) => {
+ if (Platform.isTV) return null;
const [selected, setSelected] = useState();
useEffect(() => {
if (selected !== undefined) {
- onSelected(...selected)
+ onSelected(...selected);
}
}, [selected]);
return (
-
+
- {typeof title === 'string' ? (
+ {typeof title === "string" ? (
-
- {title}
-
-
+ {title}
+
- {selected?.length !== undefined ? selected.map(titleExtractor).join(",") : placeholderText}
+ {selected?.length !== undefined
+ ? selected.map(titleExtractor).join(",")
+ : placeholderText}
) : (
- <>
- {title}
- >
+ <>{title}>
)}
({
sideOffset={0}
>
{label}
- {data.map((item, idx) => (
+ {data.map((item, idx) =>
multi ? (
- keyExtractor(s) == keyExtractor(item)) ? 'on' : 'off'}
- key={keyExtractor(item)}
- onValueChange={(next, previous) =>
- setSelected((p) => {
- const prev = p || []
- if (next == 'on') {
- return [...prev, item]
- }
- return [...prev.filter(p => keyExtractor(p) !== keyExtractor(item))]
- })
- }
- >
- {titleExtractor(item)}
-
- )
- : (
- setSelected([item])}
- >
- {titleExtractor(item)}
-
- )
- ))}
+ keyExtractor(s) == keyExtractor(item))
+ ? "on"
+ : "off"
+ }
+ key={keyExtractor(item)}
+ onValueChange={(next, previous) =>
+ setSelected((p) => {
+ const prev = p || [];
+ if (next == "on") {
+ return [...prev, item];
+ }
+ return [
+ ...prev.filter(
+ (p) => keyExtractor(p) !== keyExtractor(item)
+ ),
+ ];
+ })
+ }
+ >
+
+ {titleExtractor(item)}
+
+
+ ) : (
+ setSelected([item])}
+ >
+
+ {titleExtractor(item)}
+
+
+ )
+ )}
- )
+ );
};
-export default Dropdown;
\ No newline at end of file
+export default Dropdown;
diff --git a/components/common/TouchableItemRouter.tsx b/components/common/TouchableItemRouter.tsx
index e3dc325c..d4d53e79 100644
--- a/components/common/TouchableItemRouter.tsx
+++ b/components/common/TouchableItemRouter.tsx
@@ -7,7 +7,7 @@ import { useRouter, useSegments } from "expo-router";
import { PropsWithChildren, useCallback } from "react";
import { TouchableOpacity, TouchableOpacityProps } from "react-native";
import { useActionSheet } from "@expo/react-native-action-sheet";
-import * as Haptics from "expo-haptics";
+import { useHaptic } from "@/hooks/useHaptic";
interface Props extends TouchableOpacityProps {
item: BaseItemDto;
@@ -74,10 +74,10 @@ export const TouchableItemRouter: React.FC> = ({
async (selectedIndex) => {
if (selectedIndex === 0) {
await markAsPlayedStatus(true);
- Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
+ // Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
} else if (selectedIndex === 1) {
await markAsPlayedStatus(false);
- Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
+ // Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
}
}
);
diff --git a/components/series/SeasonDropdown.tsx b/components/series/SeasonDropdown.tsx
index 438966a3..25a09c17 100644
--- a/components/series/SeasonDropdown.tsx
+++ b/components/series/SeasonDropdown.tsx
@@ -1,7 +1,7 @@
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { useEffect, useMemo } from "react";
-import { TouchableOpacity, View } from "react-native";
-import * as DropdownMenu from "zeego/dropdown-menu";
+import { Platform, TouchableOpacity, View } from "react-native";
+const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import { Text } from "../common/Text";
import { t } from "i18next";
@@ -30,6 +30,8 @@ export const SeasonDropdown: React.FC = ({
state,
onSelect,
}) => {
+ if (Platform.isTV) return null;
+
const keys = useMemo(
() =>
item.Type === "Episode"
diff --git a/components/settings/AppLanguageSelector.tsx b/components/settings/AppLanguageSelector.tsx
index 1c296038..e9445136 100644
--- a/components/settings/AppLanguageSelector.tsx
+++ b/components/settings/AppLanguageSelector.tsx
@@ -1,4 +1,4 @@
-import * as DropdownMenu from "zeego/dropdown-menu";
+const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import { Platform, TouchableOpacity, View, ViewProps } from "react-native";
import { Text } from "../common/Text";
import { useSettings } from "@/utils/atoms/settings";
@@ -10,14 +10,12 @@ import { APP_LANGUAGES } from "@/i18n";
interface Props extends ViewProps {}
export const AppLanguageSelector: React.FC = ({ ...props }) => {
+ if (Platform.isTV) return null;
const [settings, updateSettings] = useSettings();
const { t } = useTranslation();
if (!settings) return null;
- // todo: fix
- if (Platform.isTV) return null;
-
return (
diff --git a/components/settings/AudioToggles.tsx b/components/settings/AudioToggles.tsx
index b9508191..ba8f19e3 100644
--- a/components/settings/AudioToggles.tsx
+++ b/components/settings/AudioToggles.tsx
@@ -1,5 +1,5 @@
-import { TouchableOpacity, View, ViewProps } from "react-native";
-import * as DropdownMenu from "zeego/dropdown-menu";
+import { Platform, TouchableOpacity, View, ViewProps } from "react-native";
+const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import { Text } from "../common/Text";
import { useMedia } from "./MediaContext";
import { Switch } from "react-native-gesture-handler";
@@ -12,6 +12,7 @@ import { useSettings } from "@/utils/atoms/settings";
interface Props extends ViewProps {}
export const AudioToggles: React.FC = ({ ...props }) => {
+ if (Platform.isTV) return null;
const media = useMedia();
const [_, __, pluginSettings] = useSettings();
const { settings, updateSettings } = media;
diff --git a/components/settings/DownloadSettings.tsx b/components/settings/DownloadSettings.tsx
index 7b234929..7da6002b 100644
--- a/components/settings/DownloadSettings.tsx
+++ b/components/settings/DownloadSettings.tsx
@@ -5,8 +5,8 @@ import { Ionicons } from "@expo/vector-icons";
import { useQueryClient } from "@tanstack/react-query";
import { useRouter } from "expo-router";
import React, { useMemo } from "react";
-import { Switch, TouchableOpacity } from "react-native";
-import * as DropdownMenu from "zeego/dropdown-menu";
+import { Platform, Switch, TouchableOpacity } from "react-native";
+const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import { Text } from "../common/Text";
import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
diff --git a/components/settings/SubtitleToggles.tsx b/components/settings/SubtitleToggles.tsx
index 3d2e1117..6bd5e6c2 100644
--- a/components/settings/SubtitleToggles.tsx
+++ b/components/settings/SubtitleToggles.tsx
@@ -1,5 +1,5 @@
-import { TouchableOpacity, View, ViewProps } from "react-native";
-import * as DropdownMenu from "@/components/DropdownMenu";
+import { Platform, TouchableOpacity, View, ViewProps } from "react-native";
+const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import { Text } from "../common/Text";
import { useMedia } from "./MediaContext";
import { Switch } from "react-native-gesture-handler";
@@ -8,13 +8,14 @@ import { ListItem } from "../list/ListItem";
import { Ionicons } from "@expo/vector-icons";
import { SubtitlePlaybackMode } from "@jellyfin/sdk/lib/generated-client";
import { useTranslation } from "react-i18next";
-import {useSettings} from "@/utils/atoms/settings";
-import {Stepper} from "@/components/inputs/Stepper";
+import { useSettings } from "@/utils/atoms/settings";
+import { Stepper } from "@/components/inputs/Stepper";
import Dropdown from "@/components/common/Dropdown";
interface Props extends ViewProps {}
export const SubtitleToggles: React.FC = ({ ...props }) => {
+ if (Platform.isTV) return null;
const media = useMedia();
const [_, __, pluginSettings] = useSettings();
const { settings, updateSettings } = media;
@@ -34,7 +35,8 @@ export const SubtitleToggles: React.FC = ({ ...props }) => {
const subtitleModeKeys = {
[SubtitlePlaybackMode.Default]: "home.settings.subtitles.modes.Default",
[SubtitlePlaybackMode.Smart]: "home.settings.subtitles.modes.Smart",
- [SubtitlePlaybackMode.OnlyForced]: "home.settings.subtitles.modes.OnlyForced",
+ [SubtitlePlaybackMode.OnlyForced]:
+ "home.settings.subtitles.modes.OnlyForced",
[SubtitlePlaybackMode.Always]: "home.settings.subtitles.modes.Always",
[SubtitlePlaybackMode.None]: "home.settings.subtitles.modes.None",
};
@@ -51,13 +53,22 @@ export const SubtitleToggles: React.FC = ({ ...props }) => {
>
item?.ThreeLetterISOLanguageName ?? "unknown"}
+ data={[
+ {
+ DisplayName: t("home.settings.subtitles.none"),
+ ThreeLetterISOLanguageName: "none-subs",
+ },
+ ...(cultures ?? []),
+ ]}
+ keyExtractor={(item) =>
+ item?.ThreeLetterISOLanguageName ?? "unknown"
+ }
titleExtractor={(item) => item?.DisplayName}
title={
- {settings?.defaultSubtitleLanguage?.DisplayName || t("home.settings.subtitles.none")}
+ {settings?.defaultSubtitleLanguage?.DisplayName ||
+ t("home.settings.subtitles.none")}
= ({ ...props }) => {
label={t("home.settings.subtitles.language")}
onSelected={(defaultSubtitleLanguage) =>
updateSettings({
- defaultSubtitleLanguage: defaultSubtitleLanguage.DisplayName === t("home.settings.subtitles.none")
- ? null
- : defaultSubtitleLanguage
+ defaultSubtitleLanguage:
+ defaultSubtitleLanguage.DisplayName ===
+ t("home.settings.subtitles.none")
+ ? null
+ : defaultSubtitleLanguage,
})
- }
+ }
/>
@@ -89,7 +102,8 @@ export const SubtitleToggles: React.FC = ({ ...props }) => {
title={
- {t(subtitleModeKeys[settings?.subtitleMode]) || t("home.settings.subtitles.loading")}
+ {t(subtitleModeKeys[settings?.subtitleMode]) ||
+ t("home.settings.subtitles.loading")}
= ({ ...props }) => {
}
label={t("home.settings.subtitles.subtitle_mode")}
- onSelected={(subtitleMode) =>
- updateSettings({subtitleMode})
- }
+ onSelected={(subtitleMode) => updateSettings({ subtitleMode })}
/>
@@ -128,7 +140,7 @@ export const SubtitleToggles: React.FC = ({ ...props }) => {
step={5}
min={0}
max={120}
- onUpdate={(subtitleSize) => updateSettings({subtitleSize})}
+ onUpdate={(subtitleSize) => updateSettings({ subtitleSize })}
/>
diff --git a/components/video-player/controls/Controls.tsx b/components/video-player/controls/Controls.tsx
index f209a411..8dd5b9cc 100644
--- a/components/video-player/controls/Controls.tsx
+++ b/components/video-player/controls/Controls.tsx
@@ -31,7 +31,7 @@ import {
} from "@jellyfin/sdk/lib/generated-client";
import { Image } from "expo-image";
import { useLocalSearchParams, useRouter } from "expo-router";
-import * as ScreenOrientation from "expo-screen-orientation";
+import * as ScreenOrientation from "@/packages/expo-screen-orientation";
import { useAtom } from "jotai";
import { debounce } from "lodash";
import React, { useCallback, useEffect, useRef, useState } from "react";
diff --git a/components/video-player/controls/dropdown/DropdownViewDirect.tsx b/components/video-player/controls/dropdown/DropdownViewDirect.tsx
index e2ba25fd..f6c2bfe4 100644
--- a/components/video-player/controls/dropdown/DropdownViewDirect.tsx
+++ b/components/video-player/controls/dropdown/DropdownViewDirect.tsx
@@ -1,7 +1,7 @@
import React, { useMemo, useState } from "react";
-import { View, TouchableOpacity } from "react-native";
+import { View, TouchableOpacity, Platform } from "react-native";
import { Ionicons } from "@expo/vector-icons";
-import * as DropdownMenu from "zeego/dropdown-menu";
+const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import { useControlContext } from "../contexts/ControlContext";
import { useVideoContext } from "../contexts/VideoContext";
import { EmbeddedSubtitle, ExternalSubtitle } from "../types";
diff --git a/components/video-player/controls/dropdown/DropdownViewTranscoding.tsx b/components/video-player/controls/dropdown/DropdownViewTranscoding.tsx
index 5a05dd17..eccc68a1 100644
--- a/components/video-player/controls/dropdown/DropdownViewTranscoding.tsx
+++ b/components/video-player/controls/dropdown/DropdownViewTranscoding.tsx
@@ -1,7 +1,7 @@
import React, { useCallback, useMemo, useState } from "react";
-import { View, TouchableOpacity } from "react-native";
+import { View, TouchableOpacity, Platform } from "react-native";
import { Ionicons } from "@expo/vector-icons";
-import * as DropdownMenu from "zeego/dropdown-menu";
+const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import { useControlContext } from "../contexts/ControlContext";
import { useVideoContext } from "../contexts/VideoContext";
import { TranscodedSubtitle } from "../types";
diff --git a/package.json b/package.json
index 500bd54c..32b4a337 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
"expo-keep-awake": "~13.0.2",
"expo-linear-gradient": "~13.0.2",
"expo-linking": "~6.3.1",
- "expo-localization": "~15.0.3",
+ "expo-localization": "~16.0.1",
"expo-network": "~6.0.1",
"expo-notifications": "~0.28.19",
"expo-router": "~3.5.24",
diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts
index 3beff72a..cf849e15 100644
--- a/utils/atoms/settings.ts
+++ b/utils/atoms/settings.ts
@@ -1,6 +1,6 @@
import { atom, useAtom } from "jotai";
import { useCallback, useEffect, useMemo } from "react";
-import * as ScreenOrientation from "expo-screen-orientation";
+import * as ScreenOrientation from "@/packages/expo-screen-orientation";
import { storage } from "../mmkv";
import { Platform } from "react-native";
import {