mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 23:59:08 +00:00
refactor: better settings (#1178)
Some checks failed
🏗️ Build Apps / 🤖 Build Android APK (TV) (push) Has been cancelled
🏗️ Build Apps / 🍎 Build iOS IPA (Phone) (push) Has been cancelled
🏗️ Build Apps / 🤖 Build Android APK (Phone) (push) Has been cancelled
🔒 Lockfile Consistency Check / 🔍 Check bun.lock and package.json consistency (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (actions) (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (javascript-typescript) (push) Has been cancelled
🏷️🔀Merge Conflict Labeler / 🏷️ Labeling Merge Conflicts (push) Has been cancelled
🚦 Security & Quality Gate / 📝 Validate PR Title (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Vulnerable Dependencies (push) Has been cancelled
🚦 Security & Quality Gate / 🚑 Expo Doctor Check (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (check) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (format) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (lint) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (typecheck) (push) Has been cancelled
🕒 Handle Stale Issues / 🗑️ Cleanup Stale Issues (push) Has been cancelled
Some checks failed
🏗️ Build Apps / 🤖 Build Android APK (TV) (push) Has been cancelled
🏗️ Build Apps / 🍎 Build iOS IPA (Phone) (push) Has been cancelled
🏗️ Build Apps / 🤖 Build Android APK (Phone) (push) Has been cancelled
🔒 Lockfile Consistency Check / 🔍 Check bun.lock and package.json consistency (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (actions) (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (javascript-typescript) (push) Has been cancelled
🏷️🔀Merge Conflict Labeler / 🏷️ Labeling Merge Conflicts (push) Has been cancelled
🚦 Security & Quality Gate / 📝 Validate PR Title (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Vulnerable Dependencies (push) Has been cancelled
🚦 Security & Quality Gate / 🚑 Expo Doctor Check (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (check) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (format) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (lint) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (typecheck) (push) Has been cancelled
🕒 Handle Stale Issues / 🗑️ Cleanup Stale Issues (push) Has been cancelled
This commit is contained in:
@@ -113,33 +113,144 @@ export default function IndexLayout() {
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='settings/marlin-search/page'
|
||||
name='settings/playback-controls/page'
|
||||
options={{
|
||||
title: "",
|
||||
title: t("home.settings.playback_controls.title"),
|
||||
headerBlurEffect: "none",
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
headerLeft: () => (
|
||||
<TouchableOpacity onPress={() => _router.back()} className='pl-0.5'>
|
||||
<TouchableOpacity
|
||||
onPress={() => _router.back()}
|
||||
className='pl-0.5'
|
||||
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
||||
>
|
||||
<Feather name='chevron-left' size={28} color='white' />
|
||||
</TouchableOpacity>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='settings/jellyseerr/page'
|
||||
name='settings/audio-subtitles/page'
|
||||
options={{
|
||||
title: "",
|
||||
title: t("home.settings.audio_subtitles.title"),
|
||||
headerBlurEffect: "none",
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
headerLeft: () => (
|
||||
<TouchableOpacity onPress={() => _router.back()} className='pl-0.5'>
|
||||
<TouchableOpacity
|
||||
onPress={() => _router.back()}
|
||||
className='pl-0.5'
|
||||
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
||||
>
|
||||
<Feather name='chevron-left' size={28} color='white' />
|
||||
</TouchableOpacity>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='settings/hide-libraries/page'
|
||||
name='settings/appearance/page'
|
||||
options={{
|
||||
title: "",
|
||||
title: t("home.settings.appearance.title"),
|
||||
headerBlurEffect: "none",
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
headerLeft: () => (
|
||||
<TouchableOpacity onPress={() => _router.back()} className='pl-0.5'>
|
||||
<TouchableOpacity
|
||||
onPress={() => _router.back()}
|
||||
className='pl-0.5'
|
||||
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
||||
>
|
||||
<Feather name='chevron-left' size={28} color='white' />
|
||||
</TouchableOpacity>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='settings/appearance/hide-libraries/page'
|
||||
options={{
|
||||
title: t("home.settings.other.hide_libraries"),
|
||||
headerBlurEffect: "none",
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
headerLeft: () => (
|
||||
<TouchableOpacity
|
||||
onPress={() => _router.back()}
|
||||
className='pl-0.5'
|
||||
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
||||
>
|
||||
<Feather name='chevron-left' size={28} color='white' />
|
||||
</TouchableOpacity>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='settings/plugins/page'
|
||||
options={{
|
||||
title: t("home.settings.plugins.plugins_title"),
|
||||
headerBlurEffect: "none",
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
headerLeft: () => (
|
||||
<TouchableOpacity
|
||||
onPress={() => _router.back()}
|
||||
className='pl-0.5'
|
||||
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
||||
>
|
||||
<Feather name='chevron-left' size={28} color='white' />
|
||||
</TouchableOpacity>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='settings/plugins/marlin-search/page'
|
||||
options={{
|
||||
title: "Marlin Search",
|
||||
headerBlurEffect: "none",
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
headerLeft: () => (
|
||||
<TouchableOpacity
|
||||
onPress={() => _router.back()}
|
||||
className='pl-0.5'
|
||||
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
||||
>
|
||||
<Feather name='chevron-left' size={28} color='white' />
|
||||
</TouchableOpacity>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='settings/plugins/jellyseerr/page'
|
||||
options={{
|
||||
title: "Jellyseerr",
|
||||
headerBlurEffect: "none",
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
headerLeft: () => (
|
||||
<TouchableOpacity
|
||||
onPress={() => _router.back()}
|
||||
className='pl-0.5'
|
||||
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
||||
>
|
||||
<Feather name='chevron-left' size={28} color='white' />
|
||||
</TouchableOpacity>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='settings/intro/page'
|
||||
options={{
|
||||
title: t("home.settings.intro.title"),
|
||||
headerBlurEffect: "none",
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
headerLeft: () => (
|
||||
<TouchableOpacity
|
||||
onPress={() => _router.back()}
|
||||
className='pl-0.5'
|
||||
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
||||
>
|
||||
<Feather name='chevron-left' size={28} color='white' />
|
||||
</TouchableOpacity>
|
||||
),
|
||||
@@ -148,9 +259,16 @@ export default function IndexLayout() {
|
||||
<Stack.Screen
|
||||
name='settings/logs/page'
|
||||
options={{
|
||||
title: "",
|
||||
title: t("home.settings.logs.logs_title"),
|
||||
headerBlurEffect: "none",
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
headerLeft: () => (
|
||||
<TouchableOpacity onPress={() => _router.back()} className='pl-0.5'>
|
||||
<TouchableOpacity
|
||||
onPress={() => _router.back()}
|
||||
className='pl-0.5'
|
||||
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
||||
>
|
||||
<Feather name='chevron-left' size={28} color='white' />
|
||||
</TouchableOpacity>
|
||||
),
|
||||
|
||||
@@ -8,34 +8,16 @@ import { Text } from "@/components/common/Text";
|
||||
import { ListGroup } from "@/components/list/ListGroup";
|
||||
import { ListItem } from "@/components/list/ListItem";
|
||||
import { AppLanguageSelector } from "@/components/settings/AppLanguageSelector";
|
||||
import { AudioToggles } from "@/components/settings/AudioToggles";
|
||||
import { ChromecastSettings } from "@/components/settings/ChromecastSettings";
|
||||
import DownloadSettings from "@/components/settings/DownloadSettings";
|
||||
import { GestureControls } from "@/components/settings/GestureControls";
|
||||
import { MediaProvider } from "@/components/settings/MediaContext";
|
||||
import { MediaToggles } from "@/components/settings/MediaToggles";
|
||||
import { OtherSettings } from "@/components/settings/OtherSettings";
|
||||
import { PluginSettings } from "@/components/settings/PluginSettings";
|
||||
import { QuickConnect } from "@/components/settings/QuickConnect";
|
||||
import { StorageSettings } from "@/components/settings/StorageSettings";
|
||||
import { SubtitleToggles } from "@/components/settings/SubtitleToggles";
|
||||
import { UserInfo } from "@/components/settings/UserInfo";
|
||||
import { useHaptic } from "@/hooks/useHaptic";
|
||||
import { useJellyfin, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { clearLogs } from "@/utils/log";
|
||||
import { storage } from "@/utils/mmkv";
|
||||
|
||||
export default function settings() {
|
||||
const router = useRouter();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [_user] = useAtom(userAtom);
|
||||
const { logout } = useJellyfin();
|
||||
const successHapticFeedback = useHaptic("success");
|
||||
|
||||
const onClearLogsClicked = async () => {
|
||||
clearLogs();
|
||||
successHapticFeedback();
|
||||
};
|
||||
|
||||
const navigation = useNavigation();
|
||||
useEffect(() => {
|
||||
@@ -63,58 +45,51 @@ export default function settings() {
|
||||
}}
|
||||
>
|
||||
<View
|
||||
className='p-4 flex flex-col gap-y-4'
|
||||
className='p-4 flex flex-col'
|
||||
style={{ paddingTop: Platform.OS === "android" ? 10 : 0 }}
|
||||
>
|
||||
<UserInfo />
|
||||
<View className='mb-4'>
|
||||
<UserInfo />
|
||||
</View>
|
||||
|
||||
<QuickConnect className='mb-4' />
|
||||
|
||||
<MediaProvider>
|
||||
<MediaToggles className='mb-4' />
|
||||
<GestureControls className='mb-4' />
|
||||
<AudioToggles className='mb-4' />
|
||||
<SubtitleToggles className='mb-4' />
|
||||
</MediaProvider>
|
||||
|
||||
<OtherSettings />
|
||||
|
||||
{!Platform.isTV && <DownloadSettings />}
|
||||
|
||||
<PluginSettings />
|
||||
|
||||
<AppLanguageSelector />
|
||||
|
||||
{!Platform.isTV && <ChromecastSettings />}
|
||||
|
||||
<ListGroup title={"Intro"}>
|
||||
<ListItem
|
||||
onPress={() => {
|
||||
router.push("/intro/page");
|
||||
}}
|
||||
title={t("home.settings.intro.show_intro")}
|
||||
/>
|
||||
<ListItem
|
||||
textColor='red'
|
||||
onPress={() => {
|
||||
storage.set("hasShownIntro", false);
|
||||
}}
|
||||
title={t("home.settings.intro.reset_intro")}
|
||||
/>
|
||||
</ListGroup>
|
||||
<View className='mb-4'>
|
||||
<AppLanguageSelector />
|
||||
</View>
|
||||
|
||||
<View className='mb-4'>
|
||||
<ListGroup title={t("home.settings.logs.logs_title")}>
|
||||
<ListGroup title={t("home.settings.categories.title")}>
|
||||
<ListItem
|
||||
onPress={() => router.push("/settings/playback-controls/page")}
|
||||
showArrow
|
||||
title={t("home.settings.playback_controls.title")}
|
||||
/>
|
||||
<ListItem
|
||||
onPress={() => router.push("/settings/audio-subtitles/page")}
|
||||
showArrow
|
||||
title={t("home.settings.audio_subtitles.title")}
|
||||
/>
|
||||
<ListItem
|
||||
onPress={() => router.push("/settings/appearance/page")}
|
||||
showArrow
|
||||
title={t("home.settings.appearance.title")}
|
||||
/>
|
||||
<ListItem
|
||||
onPress={() => router.push("/settings/plugins/page")}
|
||||
showArrow
|
||||
title={t("home.settings.plugins.plugins_title")}
|
||||
/>
|
||||
<ListItem
|
||||
onPress={() => router.push("/settings/intro/page")}
|
||||
showArrow
|
||||
title={t("home.settings.intro.title")}
|
||||
/>
|
||||
<ListItem
|
||||
onPress={() => router.push("/settings/logs/page")}
|
||||
showArrow
|
||||
title={t("home.settings.logs.logs_title")}
|
||||
/>
|
||||
<ListItem
|
||||
textColor='red'
|
||||
onPress={onClearLogsClicked}
|
||||
title={t("home.settings.logs.delete_all_logs")}
|
||||
/>
|
||||
</ListGroup>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { getUserViewsApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ScrollView, Switch, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { Loader } from "@/components/Loader";
|
||||
import { ListGroup } from "@/components/list/ListGroup";
|
||||
import { ListItem } from "@/components/list/ListItem";
|
||||
import DisabledSetting from "@/components/settings/DisabledSetting";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
|
||||
export default function page() {
|
||||
const { settings, updateSettings, pluginSettings } = useSettings();
|
||||
const user = useAtomValue(userAtom);
|
||||
const api = useAtomValue(apiAtom);
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ["user-views", user?.Id],
|
||||
queryFn: async () => {
|
||||
const response = await getUserViewsApi(api!).getUserViews({
|
||||
userId: user?.Id,
|
||||
});
|
||||
|
||||
return response.data.Items || null;
|
||||
},
|
||||
});
|
||||
|
||||
if (!settings) return null;
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<View className='mt-4'>
|
||||
<Loader />
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
contentContainerStyle={{
|
||||
paddingLeft: insets.left,
|
||||
paddingRight: insets.right,
|
||||
}}
|
||||
>
|
||||
<DisabledSetting
|
||||
disabled={pluginSettings?.hiddenLibraries?.locked === true}
|
||||
className='px-4'
|
||||
>
|
||||
<ListGroup title={t("home.settings.other.hide_libraries")}>
|
||||
{data?.map((view) => (
|
||||
<ListItem key={view.Id} title={view.Name} onPress={() => {}}>
|
||||
<Switch
|
||||
value={settings.hiddenLibraries?.includes(view.Id!) || false}
|
||||
onValueChange={(value) => {
|
||||
updateSettings({
|
||||
hiddenLibraries: value
|
||||
? [...(settings.hiddenLibraries || []), view.Id!]
|
||||
: settings.hiddenLibraries?.filter(
|
||||
(id) => id !== view.Id,
|
||||
),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</ListGroup>
|
||||
<Text className='px-4 text-xs text-neutral-500 mt-1'>
|
||||
{t("home.settings.other.select_liraries_you_want_to_hide")}
|
||||
</Text>
|
||||
</DisabledSetting>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
25
app/(auth)/(tabs)/(home)/settings/appearance/page.tsx
Normal file
25
app/(auth)/(tabs)/(home)/settings/appearance/page.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Platform, ScrollView, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { AppearanceSettings } from "@/components/settings/AppearanceSettings";
|
||||
|
||||
export default function AppearancePage() {
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
contentContainerStyle={{
|
||||
paddingLeft: insets.left,
|
||||
paddingRight: insets.right,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
className='p-4 flex flex-col'
|
||||
style={{ paddingTop: Platform.OS === "android" ? 10 : 0 }}
|
||||
>
|
||||
<AppearanceSettings />
|
||||
<View className='h-24' />
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
29
app/(auth)/(tabs)/(home)/settings/audio-subtitles/page.tsx
Normal file
29
app/(auth)/(tabs)/(home)/settings/audio-subtitles/page.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Platform, ScrollView, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { AudioToggles } from "@/components/settings/AudioToggles";
|
||||
import { MediaProvider } from "@/components/settings/MediaContext";
|
||||
import { SubtitleToggles } from "@/components/settings/SubtitleToggles";
|
||||
|
||||
export default function AudioSubtitlesPage() {
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
contentContainerStyle={{
|
||||
paddingLeft: insets.left,
|
||||
paddingRight: insets.right,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
className='p-4 flex flex-col'
|
||||
style={{ paddingTop: Platform.OS === "android" ? 10 : 0 }}
|
||||
>
|
||||
<MediaProvider>
|
||||
<AudioToggles className='mb-4' />
|
||||
<SubtitleToggles className='mb-4' />
|
||||
</MediaProvider>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
45
app/(auth)/(tabs)/(home)/settings/intro/page.tsx
Normal file
45
app/(auth)/(tabs)/(home)/settings/intro/page.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { useRouter } from "expo-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Platform, ScrollView, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { ListGroup } from "@/components/list/ListGroup";
|
||||
import { ListItem } from "@/components/list/ListItem";
|
||||
import { storage } from "@/utils/mmkv";
|
||||
|
||||
export default function IntroPage() {
|
||||
const router = useRouter();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
contentContainerStyle={{
|
||||
paddingLeft: insets.left,
|
||||
paddingRight: insets.right,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
className='p-4 flex flex-col'
|
||||
style={{ paddingTop: Platform.OS === "android" ? 10 : 0 }}
|
||||
>
|
||||
<ListGroup title={t("home.settings.intro.title")}>
|
||||
<ListItem
|
||||
onPress={() => {
|
||||
router.push("/intro/page");
|
||||
}}
|
||||
title={t("home.settings.intro.show_intro")}
|
||||
/>
|
||||
<ListItem
|
||||
textColor='red'
|
||||
onPress={() => {
|
||||
storage.set("hasShownIntro", false);
|
||||
}}
|
||||
title={t("home.settings.intro.reset_intro")}
|
||||
/>
|
||||
</ListGroup>
|
||||
<View className='h-24' />
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import DisabledSetting from "@/components/settings/DisabledSetting";
|
||||
import { JellyseerrSettings } from "@/components/settings/Jellyseerr";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
|
||||
export default function page() {
|
||||
const { pluginSettings } = useSettings();
|
||||
|
||||
return (
|
||||
<DisabledSetting
|
||||
disabled={pluginSettings?.jellyseerrServerUrl?.locked === true}
|
||||
className='p-4'
|
||||
>
|
||||
<JellyseerrSettings />
|
||||
</DisabledSetting>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useId, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ScrollView, TouchableOpacity, View } from "react-native";
|
||||
import Collapsible from "react-native-collapsible";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { FilterButton } from "@/components/filters/FilterButton";
|
||||
import { Loader } from "@/components/Loader";
|
||||
@@ -32,6 +33,7 @@ export default function Page() {
|
||||
|
||||
const _orderId = useId();
|
||||
const _levelsId = useId();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const filteredLogs = useMemo(
|
||||
() =>
|
||||
@@ -66,7 +68,7 @@ export default function Page() {
|
||||
loading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<TouchableOpacity onPress={share}>
|
||||
<TouchableOpacity onPress={share} className='px-2'>
|
||||
<Text>{t("home.settings.logs.export_logs")}</Text>
|
||||
</TouchableOpacity>
|
||||
),
|
||||
@@ -74,7 +76,12 @@ export default function Page() {
|
||||
}, [share, loading]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
className='flex-1'
|
||||
style={{
|
||||
paddingTop: insets.top + 48,
|
||||
}}
|
||||
>
|
||||
<View className='flex flex-row justify-end py-2 px-4 space-x-2'>
|
||||
<FilterButton
|
||||
id={orderFilterId}
|
||||
@@ -156,6 +163,6 @@ export default function Page() {
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useNavigation } from "expo-router";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Linking,
|
||||
Switch,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { toast } from "sonner-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { ListGroup } from "@/components/list/ListGroup";
|
||||
import { ListItem } from "@/components/list/ListItem";
|
||||
import DisabledSetting from "@/components/settings/DisabledSetting";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
|
||||
export default function page() {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { settings, updateSettings, pluginSettings } = useSettings();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [value, setValue] = useState<string>(settings?.marlinServerUrl || "");
|
||||
|
||||
const onSave = (val: string) => {
|
||||
updateSettings({
|
||||
marlinServerUrl: !val.endsWith("/") ? val : val.slice(0, -1),
|
||||
});
|
||||
toast.success(t("home.settings.plugins.marlin_search.toasts.saved"));
|
||||
};
|
||||
|
||||
const handleOpenLink = () => {
|
||||
Linking.openURL("https://github.com/fredrikburmester/marlin-search");
|
||||
};
|
||||
|
||||
const disabled = useMemo(() => {
|
||||
return (
|
||||
pluginSettings?.searchEngine?.locked === true &&
|
||||
pluginSettings?.marlinServerUrl?.locked === true
|
||||
);
|
||||
}, [pluginSettings]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pluginSettings?.marlinServerUrl?.locked) {
|
||||
navigation.setOptions({
|
||||
headerRight: () => (
|
||||
<TouchableOpacity onPress={() => onSave(value)}>
|
||||
<Text className='text-blue-500'>
|
||||
{t("home.settings.plugins.marlin_search.save_button")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
),
|
||||
});
|
||||
}
|
||||
}, [navigation, value]);
|
||||
|
||||
if (!settings) return null;
|
||||
|
||||
return (
|
||||
<DisabledSetting disabled={disabled} className='px-4'>
|
||||
<ListGroup>
|
||||
<DisabledSetting
|
||||
disabled={pluginSettings?.searchEngine?.locked === true}
|
||||
showText={!pluginSettings?.marlinServerUrl?.locked}
|
||||
>
|
||||
<ListItem
|
||||
title={t(
|
||||
"home.settings.plugins.marlin_search.enable_marlin_search",
|
||||
)}
|
||||
onPress={() => {
|
||||
updateSettings({ searchEngine: "Jellyfin" });
|
||||
queryClient.invalidateQueries({ queryKey: ["search"] });
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
value={settings.searchEngine === "Marlin"}
|
||||
onValueChange={(value) => {
|
||||
updateSettings({ searchEngine: value ? "Marlin" : "Jellyfin" });
|
||||
queryClient.invalidateQueries({ queryKey: ["search"] });
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
</DisabledSetting>
|
||||
</ListGroup>
|
||||
|
||||
<DisabledSetting
|
||||
disabled={pluginSettings?.marlinServerUrl?.locked === true}
|
||||
showText={!pluginSettings?.searchEngine?.locked}
|
||||
className='mt-2 flex flex-col rounded-xl overflow-hidden pl-4 bg-neutral-900 px-4'
|
||||
>
|
||||
<View className={"flex flex-row items-center bg-neutral-900 h-11 pr-4"}>
|
||||
<Text className='mr-4'>
|
||||
{t("home.settings.plugins.marlin_search.url")}
|
||||
</Text>
|
||||
<TextInput
|
||||
editable={settings.searchEngine === "Marlin"}
|
||||
className='text-white'
|
||||
placeholder={t(
|
||||
"home.settings.plugins.marlin_search.server_url_placeholder",
|
||||
)}
|
||||
value={value}
|
||||
keyboardType='url'
|
||||
returnKeyType='done'
|
||||
autoCapitalize='none'
|
||||
textContentType='URL'
|
||||
onChangeText={(text) => setValue(text)}
|
||||
/>
|
||||
</View>
|
||||
</DisabledSetting>
|
||||
<Text className='px-4 text-xs text-neutral-500 mt-1'>
|
||||
{t("home.settings.plugins.marlin_search.marlin_search_hint")}{" "}
|
||||
<Text className='text-blue-500' onPress={handleOpenLink}>
|
||||
{t("home.settings.plugins.marlin_search.read_more_about_marlin")}
|
||||
</Text>
|
||||
</Text>
|
||||
</DisabledSetting>
|
||||
);
|
||||
}
|
||||
35
app/(auth)/(tabs)/(home)/settings/playback-controls/page.tsx
Normal file
35
app/(auth)/(tabs)/(home)/settings/playback-controls/page.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Platform, ScrollView, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { GestureControls } from "@/components/settings/GestureControls";
|
||||
import { MediaProvider } from "@/components/settings/MediaContext";
|
||||
import { MediaToggles } from "@/components/settings/MediaToggles";
|
||||
import { PlaybackControlsSettings } from "@/components/settings/PlaybackControlsSettings";
|
||||
import { ChromecastSettings } from "../../../../../../components/settings/ChromecastSettings";
|
||||
|
||||
export default function PlaybackControlsPage() {
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
contentContainerStyle={{
|
||||
paddingLeft: insets.left,
|
||||
paddingRight: insets.right,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
className='p-4 flex flex-col'
|
||||
style={{ paddingTop: Platform.OS === "android" ? 10 : 0 }}
|
||||
>
|
||||
<View className='mb-4'>
|
||||
<MediaProvider>
|
||||
<MediaToggles className='mb-4' />
|
||||
<GestureControls className='mb-4' />
|
||||
<PlaybackControlsSettings />
|
||||
</MediaProvider>
|
||||
</View>
|
||||
{!Platform.isTV && <ChromecastSettings />}
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ScrollView } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import DisabledSetting from "@/components/settings/DisabledSetting";
|
||||
import { JellyseerrSettings } from "@/components/settings/Jellyseerr";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
|
||||
export default function page() {
|
||||
const { pluginSettings } = useSettings();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
contentContainerStyle={{
|
||||
paddingLeft: insets.left,
|
||||
paddingRight: insets.right,
|
||||
}}
|
||||
>
|
||||
<DisabledSetting
|
||||
disabled={pluginSettings?.jellyseerrServerUrl?.locked === true}
|
||||
className='px-4'
|
||||
>
|
||||
<JellyseerrSettings />
|
||||
</DisabledSetting>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
138
app/(auth)/(tabs)/(home)/settings/plugins/marlin-search/page.tsx
Normal file
138
app/(auth)/(tabs)/(home)/settings/plugins/marlin-search/page.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useNavigation } from "expo-router";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Linking,
|
||||
ScrollView,
|
||||
Switch,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { toast } from "sonner-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { ListGroup } from "@/components/list/ListGroup";
|
||||
import { ListItem } from "@/components/list/ListItem";
|
||||
import DisabledSetting from "@/components/settings/DisabledSetting";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
|
||||
export default function page() {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const { settings, updateSettings, pluginSettings } = useSettings();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [value, setValue] = useState<string>(settings?.marlinServerUrl || "");
|
||||
|
||||
const onSave = (val: string) => {
|
||||
updateSettings({
|
||||
marlinServerUrl: !val.endsWith("/") ? val : val.slice(0, -1),
|
||||
});
|
||||
toast.success(t("home.settings.plugins.marlin_search.toasts.saved"));
|
||||
};
|
||||
|
||||
const handleOpenLink = () => {
|
||||
Linking.openURL("https://github.com/fredrikburmester/marlin-search");
|
||||
};
|
||||
|
||||
const disabled = useMemo(() => {
|
||||
return (
|
||||
pluginSettings?.searchEngine?.locked === true &&
|
||||
pluginSettings?.marlinServerUrl?.locked === true
|
||||
);
|
||||
}, [pluginSettings]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pluginSettings?.marlinServerUrl?.locked) {
|
||||
navigation.setOptions({
|
||||
headerRight: () => (
|
||||
<TouchableOpacity onPress={() => onSave(value)} className='px-2'>
|
||||
<Text className='text-blue-500'>
|
||||
{t("home.settings.plugins.marlin_search.save_button")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
),
|
||||
});
|
||||
}
|
||||
}, [navigation, value]);
|
||||
|
||||
if (!settings) return null;
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
contentContainerStyle={{
|
||||
paddingLeft: insets.left,
|
||||
paddingRight: insets.right,
|
||||
}}
|
||||
>
|
||||
<DisabledSetting disabled={disabled} className='px-4'>
|
||||
<ListGroup>
|
||||
<DisabledSetting
|
||||
disabled={pluginSettings?.searchEngine?.locked === true}
|
||||
showText={!pluginSettings?.marlinServerUrl?.locked}
|
||||
>
|
||||
<ListItem
|
||||
title={t(
|
||||
"home.settings.plugins.marlin_search.enable_marlin_search",
|
||||
)}
|
||||
onPress={() => {
|
||||
updateSettings({ searchEngine: "Jellyfin" });
|
||||
queryClient.invalidateQueries({ queryKey: ["search"] });
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
value={settings.searchEngine === "Marlin"}
|
||||
onValueChange={(value) => {
|
||||
updateSettings({
|
||||
searchEngine: value ? "Marlin" : "Jellyfin",
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: ["search"] });
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
</DisabledSetting>
|
||||
</ListGroup>
|
||||
|
||||
<DisabledSetting
|
||||
disabled={pluginSettings?.marlinServerUrl?.locked === true}
|
||||
showText={!pluginSettings?.searchEngine?.locked}
|
||||
className='mt-2 flex flex-col rounded-xl overflow-hidden pl-4 bg-neutral-900 px-4'
|
||||
>
|
||||
<View
|
||||
className={"flex flex-row items-center bg-neutral-900 h-11 pr-4"}
|
||||
>
|
||||
<Text className='mr-4'>
|
||||
{t("home.settings.plugins.marlin_search.url")}
|
||||
</Text>
|
||||
<TextInput
|
||||
editable={settings.searchEngine === "Marlin"}
|
||||
className='text-white'
|
||||
placeholder={t(
|
||||
"home.settings.plugins.marlin_search.server_url_placeholder",
|
||||
)}
|
||||
value={value}
|
||||
keyboardType='url'
|
||||
returnKeyType='done'
|
||||
autoCapitalize='none'
|
||||
textContentType='URL'
|
||||
onChangeText={(text) => setValue(text)}
|
||||
/>
|
||||
</View>
|
||||
</DisabledSetting>
|
||||
<Text className='px-4 text-xs text-neutral-500 mt-1'>
|
||||
{t("home.settings.plugins.marlin_search.marlin_search_hint")}{" "}
|
||||
<Text className='text-blue-500' onPress={handleOpenLink}>
|
||||
{t("home.settings.plugins.marlin_search.read_more_about_marlin")}
|
||||
</Text>
|
||||
</Text>
|
||||
</DisabledSetting>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
24
app/(auth)/(tabs)/(home)/settings/plugins/page.tsx
Normal file
24
app/(auth)/(tabs)/(home)/settings/plugins/page.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Platform, ScrollView, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { PluginSettings } from "@/components/settings/PluginSettings";
|
||||
|
||||
export default function PluginsPage() {
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
contentContainerStyle={{
|
||||
paddingLeft: insets.left,
|
||||
paddingRight: insets.right,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
className='px-4 flex flex-col'
|
||||
style={{ paddingTop: Platform.OS === "android" ? 10 : 0 }}
|
||||
>
|
||||
<PluginSettings />
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user