chore: Apply linting rules and add git hok (#611)

Co-authored-by: Fredrik Burmester <fredrik.burmester@gmail.com>
This commit is contained in:
lostb1t
2025-03-16 18:01:12 +01:00
committed by GitHub
parent 2688e1b981
commit 92513e234f
268 changed files with 9197 additions and 8394 deletions

View File

@@ -1,11 +1,11 @@
const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import { Platform, TouchableOpacity, View, ViewProps } from "react-native";
import { Text } from "../common/Text";
import { APP_LANGUAGES } from "@/i18n";
import { useSettings } from "@/utils/atoms/settings";
import { useTranslation } from "react-i18next";
import { Platform, TouchableOpacity, View, type ViewProps } from "react-native";
import { Text } from "../common/Text";
import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
import { useTranslation } from "react-i18next";
import { APP_LANGUAGES } from "@/i18n";
interface Props extends ViewProps {}
@@ -22,18 +22,18 @@ export const AppLanguageSelector: React.FC<Props> = ({ ...props }) => {
<ListItem title={t("home.settings.languages.app_language")}>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<TouchableOpacity className="bg-neutral-800 rounded-lg border-neutral-900 border px-3 py-2 flex flex-row items-center justify-between">
<TouchableOpacity className='bg-neutral-800 rounded-lg border-neutral-900 border px-3 py-2 flex flex-row items-center justify-between'>
<Text>
{APP_LANGUAGES.find(
(l) => l.value === settings?.preferedLanguage
(l) => l.value === settings?.preferedLanguage,
)?.label || t("home.settings.languages.system")}
</Text>
</TouchableOpacity>
</DropdownMenu.Trigger>
<DropdownMenu.Content
loop={true}
side="bottom"
align="start"
side='bottom'
align='start'
alignOffset={0}
avoidCollisions={true}
collisionPadding={8}

View File

@@ -1,13 +1,13 @@
import { Platform, TouchableOpacity, View, ViewProps } from "react-native";
import { Platform, TouchableOpacity, View, type 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";
import { useSettings } from "@/utils/atoms/settings";
import { Ionicons } from "@expo/vector-icons";
import { useTranslation } from "react-i18next";
import { Switch } from "react-native-gesture-handler";
import { Text } from "../common/Text";
import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
import { Ionicons } from "@expo/vector-icons";
import { useSettings } from "@/utils/atoms/settings";
import { useMedia } from "./MediaContext";
interface Props extends ViewProps {}
@@ -26,7 +26,7 @@ export const AudioToggles: React.FC<Props> = ({ ...props }) => {
<ListGroup
title={t("home.settings.audio.audio_title")}
description={
<Text className="text-[#8E8D91] text-xs">
<Text className='text-[#8E8D91] text-xs'>
{t("home.settings.audio.audio_hint")}
</Text>
}
@@ -46,22 +46,22 @@ export const AudioToggles: React.FC<Props> = ({ ...props }) => {
<ListItem title={t("home.settings.audio.audio_language")}>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<TouchableOpacity className="flex flex-row items-center justify-between py-3 pl-3 ">
<Text className="mr-1 text-[#8E8D91]">
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3 '>
<Text className='mr-1 text-[#8E8D91]'>
{settings?.defaultAudioLanguage?.DisplayName ||
t("home.settings.audio.none")}
</Text>
<Ionicons
name="chevron-expand-sharp"
name='chevron-expand-sharp'
size={18}
color="#5A5960"
color='#5A5960'
/>
</TouchableOpacity>
</DropdownMenu.Trigger>
<DropdownMenu.Content
loop={true}
side="bottom"
align="start"
side='bottom'
align='start'
alignOffset={0}
avoidCollisions={true}
collisionPadding={8}

View File

@@ -1,6 +1,6 @@
import { useSettings } from "@/utils/atoms/settings";
import { Switch, View } from "react-native";
import { ListGroup } from "../list/ListGroup";
import { useSettings } from "@/utils/atoms/settings";
import { ListItem } from "../list/ListItem";
export const ChromecastSettings: React.FC = ({ ...props }) => {

View File

@@ -1,11 +1,11 @@
import { useSessions, type useSessionsProps } from "@/hooks/useSessions";
import { useSettings } from "@/utils/atoms/settings";
import { useRouter } from "expo-router";
import React from "react";
import { useTranslation } from "react-i18next";
import { View } from "react-native";
import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
import { useTranslation } from "react-i18next";
import { useSessions, useSessionsProps } from "@/hooks/useSessions";
export const Dashboard = () => {
const [settings, updateSettings] = useSettings();
@@ -17,7 +17,7 @@ export const Dashboard = () => {
if (!settings) return null;
return (
<View>
<ListGroup title={t("home.settings.dashboard.title")} className="mt-4">
<ListGroup title={t("home.settings.dashboard.title")} className='mt-4'>
<ListItem
className={sessions.length != 0 ? "bg-purple-900" : ""}
onPress={() => router.push("/settings/dashboard/sessions")}

View File

@@ -1,13 +1,9 @@
import {View, ViewProps} from "react-native";
import {Text} from "@/components/common/Text";
import { Text } from "@/components/common/Text";
import { View, type ViewProps } from "react-native";
const DisabledSetting: React.FC<{disabled: boolean, showText?: boolean, text?: string} & ViewProps> = ({
disabled = false,
showText = true,
text,
children,
...props
}) => (
const DisabledSetting: React.FC<
{ disabled: boolean; showText?: boolean; text?: string } & ViewProps
> = ({ disabled = false, showText = true, text, children, ...props }) => (
<View
pointerEvents={disabled ? "none" : "auto"}
style={{
@@ -15,12 +11,14 @@ const DisabledSetting: React.FC<{disabled: boolean, showText?: boolean, text?: s
}}
>
<View {...props}>
{disabled && showText &&
<Text className="text-center text-red-700 my-4">{text ?? "Currently disabled by admin."}</Text>
}
{disabled && showText && (
<Text className='text-center text-red-700 my-4'>
{text ?? "Currently disabled by admin."}
</Text>
)}
{children}
</View>
</View>
)
);
export default DisabledSetting;
export default DisabledSetting;

View File

@@ -1,17 +1,21 @@
import { Stepper } from "@/components/inputs/Stepper";
import { useDownload } from "@/providers/DownloadProvider";
import { DownloadMethod, Settings, useSettings } from "@/utils/atoms/settings";
import {
DownloadMethod,
type Settings,
useSettings,
} from "@/utils/atoms/settings";
import { Ionicons } from "@expo/vector-icons";
import { useQueryClient } from "@tanstack/react-query";
import { useRouter } from "expo-router";
import React, { useMemo } from "react";
import { Platform, Switch, TouchableOpacity } from "react-native";
const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import DisabledSetting from "@/components/settings/DisabledSetting";
import { useTranslation } from "react-i18next";
import { Text } from "../common/Text";
import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
import { useTranslation } from "react-i18next";
import DisabledSetting from "@/components/settings/DisabledSetting";
export default function DownloadSettings({ ...props }) {
const [settings, updateSettings, pluginSettings] = useSettings();
@@ -25,13 +29,13 @@ export default function DownloadSettings({ ...props }) {
pluginSettings?.downloadMethod?.locked === true &&
pluginSettings?.remuxConcurrentLimit?.locked === true &&
pluginSettings?.autoDownload.locked === true,
[pluginSettings]
[pluginSettings],
);
if (!settings) return null;
return (
<DisabledSetting disabled={allDisabled} {...props} className="mb-4">
<DisabledSetting disabled={allDisabled} {...props} className='mb-4'>
<ListGroup title={t("home.settings.downloads.downloads_title")}>
<ListItem
title={t("home.settings.downloads.download_method")}
@@ -39,23 +43,23 @@ export default function DownloadSettings({ ...props }) {
>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<TouchableOpacity className="flex flex-row items-center justify-between py-3 pl-3">
<Text className="mr-1 text-[#8E8D91]">
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>
{settings.downloadMethod === DownloadMethod.Remux
? t("home.settings.downloads.default")
: t("home.settings.downloads.optimized")}
</Text>
<Ionicons
name="chevron-expand-sharp"
name='chevron-expand-sharp'
size={18}
color="#5A5960"
color='#5A5960'
/>
</TouchableOpacity>
</DropdownMenu.Trigger>
<DropdownMenu.Content
loop={true}
side="bottom"
align="start"
side='bottom'
align='start'
alignOffset={0}
avoidCollisions={true}
collisionPadding={8}
@@ -65,7 +69,7 @@ export default function DownloadSettings({ ...props }) {
{t("home.settings.downloads.download_method")}
</DropdownMenu.Label>
<DropdownMenu.Item
key="1"
key='1'
onSelect={() => {
updateSettings({ downloadMethod: DownloadMethod.Remux });
setProcesses([]);
@@ -76,7 +80,7 @@ export default function DownloadSettings({ ...props }) {
</DropdownMenu.ItemTitle>
</DropdownMenu.Item>
<DropdownMenu.Item
key="2"
key='2'
onSelect={() => {
updateSettings({ downloadMethod: DownloadMethod.Optimized });
setProcesses([]);

View File

@@ -1,8 +1,8 @@
import { Button } from "@/components/Button";
import { Loader } from "@/components/Loader";
import { Text } from "@/components/common/Text";
import { LargeMovieCarousel } from "@/components/home/LargeMovieCarousel";
import { ScrollingCollectionList } from "@/components/home/ScrollingCollectionList";
import { Loader } from "@/components/Loader";
import { MediaListSection } from "@/components/medialists/MediaListSection";
import { Colors } from "@/constants/Colors";
import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
@@ -11,8 +11,8 @@ import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { useSettings } from "@/utils/atoms/settings";
import { eventBus } from "@/utils/eventBus";
import { Feather, Ionicons } from "@expo/vector-icons";
import { Api } from "@jellyfin/sdk";
import {
import type { Api } from "@jellyfin/sdk";
import type {
BaseItemDto,
BaseItemKind,
} from "@jellyfin/sdk/lib/generated-client/models";
@@ -24,7 +24,7 @@ import {
getUserViewsApi,
} from "@jellyfin/sdk/lib/utils/api";
import NetInfo from "@react-native-community/netinfo";
import { QueryFunction, useQuery } from "@tanstack/react-query";
import { type QueryFunction, useQuery } from "@tanstack/react-query";
import {
useNavigation,
usePathname,
@@ -94,10 +94,10 @@ export const HomeIndex = () => {
onPress={() => {
router.push("/(auth)/downloads");
}}
className="p-2"
className='p-2'
>
<Feather
name="download"
name='download'
color={hasDownloads ? Colors.primary : "white"}
size={22}
/>
@@ -108,7 +108,7 @@ export const HomeIndex = () => {
useEffect(() => {
cleanCacheDirectory().catch((e) =>
console.error("Something went wrong cleaning cache directory")
console.error("Something went wrong cleaning cache directory"),
);
}, []);
@@ -174,14 +174,14 @@ export const HomeIndex = () => {
const userViews = useMemo(
() => data?.filter((l) => !settings?.hiddenLibraries?.includes(l.Id!)),
[data, settings?.hiddenLibraries]
[data, settings?.hiddenLibraries],
);
const collections = useMemo(() => {
const allow = ["movies", "tvshows"];
return (
userViews?.filter(
(c) => c.CollectionType && allow.includes(c.CollectionType)
(c) => c.CollectionType && allow.includes(c.CollectionType),
) || []
);
}, [userViews]);
@@ -194,13 +194,13 @@ export const HomeIndex = () => {
await invalidateCache();
setLoading(false);
};
const createCollectionConfig = useCallback(
(
title: string,
queryKey: string[],
includeItemTypes: BaseItemKind[],
parentId: string | undefined
parentId: string | undefined,
): ScrollingCollectionListSection => ({
title,
queryKey,
@@ -222,7 +222,7 @@ export const HomeIndex = () => {
},
type: "ScrollingCollectionList",
}),
[api, user?.Id]
[api, user?.Id],
);
let sections: Section[] = [];
@@ -244,7 +244,7 @@ export const HomeIndex = () => {
title || "",
queryKey,
includeItemTypes,
c.Id
c.Id,
);
});
@@ -312,7 +312,7 @@ export const HomeIndex = () => {
try {
const suggestions = await getSuggestions(api, user.Id);
const nextUpPromises = suggestions.map((series) =>
getNextUp(api, user.Id, series.Id)
getNextUp(api, user.Id, series.Id),
);
const nextUpResults = await Promise.all(nextUpPromises);
@@ -376,32 +376,32 @@ export const HomeIndex = () => {
if (isConnected === false) {
return (
<View className="flex flex-col items-center justify-center h-full -mt-6 px-8">
<Text className="text-3xl font-bold mb-2">{t("home.no_internet")}</Text>
<Text className="text-center opacity-70">
<View className='flex flex-col items-center justify-center h-full -mt-6 px-8'>
<Text className='text-3xl font-bold mb-2'>{t("home.no_internet")}</Text>
<Text className='text-center opacity-70'>
{t("home.no_internet_message")}
</Text>
<View className="mt-4">
<View className='mt-4'>
<Button
color="purple"
color='purple'
onPress={() => router.push("/(auth)/downloads")}
justify="center"
justify='center'
iconRight={
<Ionicons name="arrow-forward" size={20} color="white" />
<Ionicons name='arrow-forward' size={20} color='white' />
}
>
{t("home.go_to_downloads")}
</Button>
<Button
color="black"
color='black'
onPress={() => {
checkConnection();
}}
justify="center"
className="mt-2"
justify='center'
className='mt-2'
iconRight={
loadingRetry ? null : (
<Ionicons name="refresh" size={20} color="white" />
<Ionicons name='refresh' size={20} color='white' />
)
}
>
@@ -418,9 +418,9 @@ export const HomeIndex = () => {
if (e1)
return (
<View className="flex flex-col items-center justify-center h-full -mt-6">
<Text className="text-3xl font-bold mb-2">{t("home.oops")}</Text>
<Text className="text-center opacity-70">
<View className='flex flex-col items-center justify-center h-full -mt-6'>
<Text className='text-3xl font-bold mb-2'>{t("home.oops")}</Text>
<Text className='text-center opacity-70'>
{t("home.error_message")}
</Text>
</View>
@@ -428,7 +428,7 @@ export const HomeIndex = () => {
if (l1)
return (
<View className="justify-center items-center h-full">
<View className='justify-center items-center h-full'>
<Loader />
</View>
);
@@ -438,7 +438,7 @@ export const HomeIndex = () => {
scrollToOverflowEnabled={true}
ref={scrollViewRef}
nestedScrollEnabled
contentInsetAdjustmentBehavior="automatic"
contentInsetAdjustmentBehavior='automatic'
refreshControl={
<RefreshControl refreshing={loading} onRefresh={refetch} />
}
@@ -448,7 +448,7 @@ export const HomeIndex = () => {
paddingBottom: 16,
}}
>
<View className="flex flex-col space-y-4">
<View className='flex flex-col space-y-4'>
<LargeMovieCarousel />
{sections.map((section, index) => {
@@ -495,7 +495,7 @@ async function getSuggestions(api: Api, userId: string | undefined) {
async function getNextUp(
api: Api,
userId: string | undefined,
seriesId: string | undefined
seriesId: string | undefined,
) {
if (!userId || !seriesId) return null;
const response = await getTvShowsApi(api).getNextUp({

View File

@@ -2,9 +2,9 @@ import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr";
import { userAtom } from "@/providers/JellyfinProvider";
import { useSettings } from "@/utils/atoms/settings";
import { useMutation } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
import { useAtom } from "jotai";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { View } from "react-native";
import { toast } from "sonner-native";
import { Button } from "../Button";
@@ -41,7 +41,7 @@ export const JellyseerrSettings = () => {
if (!user?.Name)
throw new Error("Missing required information for login");
const jellyseerrTempApi = new JellyseerrApi(
jellyseerrServerUrl || settings.jellyseerrServerUrl || ""
jellyseerrServerUrl || settings.jellyseerrServerUrl || "",
);
const testResult = await jellyseerrTempApi.test();
if (!testResult.isValid) throw new Error("Invalid server url");
@@ -68,14 +68,14 @@ export const JellyseerrSettings = () => {
};
return (
<View className="">
<View className=''>
<View>
{jellyseerrUser ? (
<>
<ListGroup title={"Jellyseerr"}>
<ListItem
title={t(
"home.settings.plugins.jellyseerr.total_media_requests"
"home.settings.plugins.jellyseerr.total_media_requests",
)}
value={jellyseerrUser?.requestCount?.toString()}
/>
@@ -109,69 +109,69 @@ export const JellyseerrSettings = () => {
/>
</ListGroup>
<View className="p-4">
<Button color="red" onPress={clearData}>
<View className='p-4'>
<Button color='red' onPress={clearData}>
{t(
"home.settings.plugins.jellyseerr.reset_jellyseerr_config_button"
"home.settings.plugins.jellyseerr.reset_jellyseerr_config_button",
)}
</Button>
</View>
</>
) : (
<View className="flex flex-col rounded-xl overflow-hidden p-4 bg-neutral-900">
<Text className="text-xs text-red-600 mb-2">
<View className='flex flex-col rounded-xl overflow-hidden p-4 bg-neutral-900'>
<Text className='text-xs text-red-600 mb-2'>
{t("home.settings.plugins.jellyseerr.jellyseerr_warning")}
</Text>
<Text className="font-bold mb-1">
<Text className='font-bold mb-1'>
{t("home.settings.plugins.jellyseerr.server_url")}
</Text>
<View className="flex flex-col shrink mb-2">
<Text className="text-xs text-gray-600">
<View className='flex flex-col shrink mb-2'>
<Text className='text-xs text-gray-600'>
{t("home.settings.plugins.jellyseerr.server_url_hint")}
</Text>
</View>
<Input
className="border border-neutral-800 mb-2"
className='border border-neutral-800 mb-2'
placeholder={t(
"home.settings.plugins.jellyseerr.server_url_placeholder"
"home.settings.plugins.jellyseerr.server_url_placeholder",
)}
value={jellyseerrServerUrl ?? settings?.jellyseerrServerUrl}
defaultValue={
settings?.jellyseerrServerUrl ?? jellyseerrServerUrl
}
keyboardType="url"
returnKeyType="done"
autoCapitalize="none"
textContentType="URL"
keyboardType='url'
returnKeyType='done'
autoCapitalize='none'
textContentType='URL'
onChangeText={setjellyseerrServerUrl}
editable={!loginToJellyseerrMutation.isPending}
/>
<View>
<Text className="font-bold mb-2">
<Text className='font-bold mb-2'>
{t("home.settings.plugins.jellyseerr.password")}
</Text>
<Input
className="border border-neutral-800"
className='border border-neutral-800'
autoFocus={true}
focusable={true}
placeholder={t(
"home.settings.plugins.jellyseerr.password_placeholder",
{ username: user?.Name }
{ username: user?.Name },
)}
value={jellyseerrPassword}
keyboardType="default"
keyboardType='default'
secureTextEntry={true}
returnKeyType="done"
autoCapitalize="none"
textContentType="password"
returnKeyType='done'
autoCapitalize='none'
textContentType='password'
onChangeText={setJellyseerrPassword}
editable={!loginToJellyseerrMutation.isPending}
/>
<Button
loading={loginToJellyseerrMutation.isPending}
disabled={loginToJellyseerrMutation.isPending}
color="purple"
className="h-12 mt-2"
color='purple'
className='h-12 mt-2'
onPress={() => loginToJellyseerrMutation.mutate()}
>
{t("home.settings.plugins.jellyseerr.login_button")}

View File

@@ -1,20 +1,20 @@
import { Settings, useSettings } from "@/utils/atoms/settings";
import { apiAtom } from "@/providers/JellyfinProvider";
import { type Settings, useSettings } from "@/utils/atoms/settings";
import type {
CultureDto,
UserConfiguration,
UserDto,
} from "@jellyfin/sdk/lib/generated-client/models";
import { getLocalizationApi, getUserApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useAtomValue } from "jotai";
import React, {
createContext,
useContext,
ReactNode,
type ReactNode,
useEffect,
useState,
} from "react";
import { apiAtom } from "@/providers/JellyfinProvider";
import { getLocalizationApi, getUserApi } from "@jellyfin/sdk/lib/utils/api";
import {
CultureDto,
UserDto,
UserConfiguration,
} from "@jellyfin/sdk/lib/generated-client/models";
import { useQuery, useQueryClient } from "@tanstack/react-query";
interface MediaContextType {
settings: Settings | null;
@@ -40,7 +40,7 @@ export const MediaProvider = ({ children }: { children: ReactNode }) => {
const updateSetingsWrapper = (update: Partial<Settings>) => {
const updateUserConfiguration = async (
update: Partial<UserConfiguration>
update: Partial<UserConfiguration>,
) => {
if (api && user) {
try {
@@ -57,7 +57,7 @@ export const MediaProvider = ({ children }: { children: ReactNode }) => {
updateSettings(update);
let updatePayload = {
const updatePayload = {
SubtitleMode: update?.subtitleMode ?? settings?.subtitleMode,
PlayDefaultAudioTrack:
update?.playDefaultAudioTrack ?? settings?.playDefaultAudioTrack,
@@ -116,10 +116,10 @@ export const MediaProvider = ({ children }: { children: ReactNode }) => {
const userAudioPreference = user?.Configuration?.AudioLanguagePreference;
const subtitlePreference = cultures.find(
(x) => x.ThreeLetterISOLanguageName === userSubtitlePreference
(x) => x.ThreeLetterISOLanguageName === userSubtitlePreference,
);
const audioPreference = cultures.find(
(x) => x.ThreeLetterISOLanguageName === userAudioPreference
(x) => x.ThreeLetterISOLanguageName === userAudioPreference,
);
updateSettings({

View File

@@ -1,11 +1,12 @@
import React, {useMemo} from "react";
import { ViewProps } from "react-native";
import { Stepper } from "@/components/inputs/Stepper";
import DisabledSetting from "@/components/settings/DisabledSetting";
import { useSettings } from "@/utils/atoms/settings";
import type React from "react";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import type { ViewProps } from "react-native";
import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
import { useTranslation } from "react-i18next";
import DisabledSetting from "@/components/settings/DisabledSetting";
import {Stepper} from "@/components/inputs/Stepper";
interface Props extends ViewProps {}
@@ -16,18 +17,15 @@ export const MediaToggles: React.FC<Props> = ({ ...props }) => {
if (!settings) return null;
const disabled = useMemo(() => (
const disabled = useMemo(
() =>
pluginSettings?.forwardSkipTime?.locked === true &&
pluginSettings?.rewindSkipTime?.locked === true
),
[pluginSettings]
)
pluginSettings?.rewindSkipTime?.locked === true,
[pluginSettings],
);
return (
<DisabledSetting
disabled={disabled}
{...props}
>
<DisabledSetting disabled={disabled} {...props}>
<ListGroup title={t("home.settings.media_controls.media_controls_title")}>
<ListItem
title={t("home.settings.media_controls.forward_skip_length")}
@@ -40,7 +38,7 @@ export const MediaToggles: React.FC<Props> = ({ ...props }) => {
appendValue={t("home.settings.media_controls.seconds_unit")}
min={0}
max={60}
onUpdate={(forwardSkipTime) => updateSettings({forwardSkipTime})}
onUpdate={(forwardSkipTime) => updateSettings({ forwardSkipTime })}
/>
</ListItem>
@@ -55,7 +53,7 @@ export const MediaToggles: React.FC<Props> = ({ ...props }) => {
appendValue={t("home.settings.media_controls.seconds_unit")}
min={0}
max={60}
onUpdate={(rewindSkipTime) => updateSettings({rewindSkipTime})}
onUpdate={(rewindSkipTime) => updateSettings({ rewindSkipTime })}
/>
</ListItem>
</ListGroup>

View File

@@ -1,6 +1,6 @@
import { TextInput, View, Linking } from "react-native";
import { Text } from "../common/Text";
import { useTranslation } from "react-i18next";
import { Linking, TextInput, View } from "react-native";
import { Text } from "../common/Text";
interface Props {
value: string;
@@ -19,24 +19,24 @@ export const OptimizedServerForm: React.FC<Props> = ({
return (
<View>
<View className="flex flex-col rounded-xl overflow-hidden pl-4 bg-neutral-900 px-4">
<View className='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.downloads.url")}</Text>
<Text className='mr-4'>{t("home.settings.downloads.url")}</Text>
<TextInput
className="text-white"
className='text-white'
placeholder={t("home.settings.downloads.server_url_placeholder")}
value={value}
keyboardType="url"
returnKeyType="done"
autoCapitalize="none"
textContentType="URL"
keyboardType='url'
returnKeyType='done'
autoCapitalize='none'
textContentType='URL'
onChangeText={(text) => onChangeValue(text)}
/>
</View>
</View>
<Text className="px-4 text-xs text-neutral-500 mt-1">
<Text className='px-4 text-xs text-neutral-500 mt-1'>
{t("home.settings.downloads.optimized_version_hint")}{" "}
<Text className="text-blue-500" onPress={handleOpenLink}>
<Text className='text-blue-500' onPress={handleOpenLink}>
{t("home.settings.downloads.read_more_about_optimized_server")}
</Text>
</Text>

View File

@@ -10,14 +10,17 @@ import {
} from "@/utils/background-tasks";
import { Ionicons } from "@expo/vector-icons";
import { useRouter } from "expo-router";
import React, { useEffect, useMemo } from "react";
import type React from "react";
import { useEffect, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Linking, Platform, Switch, TouchableOpacity } from "react-native";
import { toast } from "sonner-native";
import { Text } from "../common/Text";
import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
const BackgroundFetch = !Platform.isTV ? require("expo-background-fetch") : null;
const BackgroundFetch = !Platform.isTV
? require("expo-background-fetch")
: null;
const TaskManager = !Platform.isTV ? require("expo-task-manager") : null;
export const OtherSettings: React.FC = () => {
@@ -66,7 +69,7 @@ export const OtherSettings: React.FC = () => {
pluginSettings?.showCustomMenuLinks?.locked === true &&
pluginSettings?.hiddenLibraries?.locked === true &&
pluginSettings?.disableHapticFeedback?.locked === true,
[pluginSettings]
[pluginSettings],
);
const orientations = [
@@ -78,19 +81,23 @@ export const OtherSettings: React.FC = () => {
const orientationTranslations = useMemo(
() => ({
[ScreenOrientation.OrientationLock.DEFAULT]: "home.settings.other.orientations.DEFAULT",
[ScreenOrientation.OrientationLock.PORTRAIT_UP]: "home.settings.other.orientations.PORTRAIT_UP",
[ScreenOrientation.OrientationLock.LANDSCAPE_LEFT]: "home.settings.other.orientations.LANDSCAPE_LEFT",
[ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT]: "home.settings.other.orientations.LANDSCAPE_RIGHT",
[ScreenOrientation.OrientationLock.DEFAULT]:
"home.settings.other.orientations.DEFAULT",
[ScreenOrientation.OrientationLock.PORTRAIT_UP]:
"home.settings.other.orientations.PORTRAIT_UP",
[ScreenOrientation.OrientationLock.LANDSCAPE_LEFT]:
"home.settings.other.orientations.LANDSCAPE_LEFT",
[ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT]:
"home.settings.other.orientations.LANDSCAPE_RIGHT",
}),
[]
[],
);
if (!settings) return null;
return (
<DisabledSetting disabled={disabled}>
<ListGroup title={t("home.settings.other.other_title")} className="">
<ListGroup title={t("home.settings.other.other_title")} className=''>
<ListItem
title={t("home.settings.other.follow_device_orientation")}
disabled={pluginSettings?.followDeviceOrientation?.locked}
@@ -98,31 +105,47 @@ export const OtherSettings: React.FC = () => {
<Switch
value={settings.followDeviceOrientation}
disabled={pluginSettings?.followDeviceOrientation?.locked}
onValueChange={(value) => updateSettings({ followDeviceOrientation: value })}
onValueChange={(value) =>
updateSettings({ followDeviceOrientation: value })
}
/>
</ListItem>
<ListItem
title={t("home.settings.other.video_orientation")}
disabled={pluginSettings?.defaultVideoOrientation?.locked || settings.followDeviceOrientation}
disabled={
pluginSettings?.defaultVideoOrientation?.locked ||
settings.followDeviceOrientation
}
>
<Dropdown
data={orientations}
disabled={pluginSettings?.defaultVideoOrientation?.locked || settings.followDeviceOrientation}
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">
<Text className="mr-1 text-[#8E8D91]">
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>
{t(
orientationTranslations[settings.defaultVideoOrientation as keyof typeof orientationTranslations]
orientationTranslations[
settings.defaultVideoOrientation as keyof typeof orientationTranslations
],
) || "Unknown Orientation"}
</Text>
<Ionicons name="chevron-expand-sharp" size={18} color="#5A5960" />
<Ionicons
name='chevron-expand-sharp'
size={18}
color='#5A5960'
/>
</TouchableOpacity>
}
label={t("home.settings.other.orientation")}
onSelected={(defaultVideoOrientation) => updateSettings({ defaultVideoOrientation })}
onSelected={(defaultVideoOrientation) =>
updateSettings({ defaultVideoOrientation })
}
/>
</ListItem>
@@ -133,7 +156,9 @@ export const OtherSettings: React.FC = () => {
<Switch
value={settings.safeAreaInControlsEnabled}
disabled={pluginSettings?.safeAreaInControlsEnabled?.locked}
onValueChange={(value) => updateSettings({ safeAreaInControlsEnabled: value })}
onValueChange={(value) =>
updateSettings({ safeAreaInControlsEnabled: value })
}
/>
</ListItem>
@@ -170,12 +195,18 @@ export const OtherSettings: React.FC = () => {
<ListItem
title={t("home.settings.other.show_custom_menu_links")}
disabled={pluginSettings?.showCustomMenuLinks?.locked}
onPress={() => Linking.openURL("https://jellyfin.org/docs/general/clients/web-config/#custom-menu-links")}
onPress={() =>
Linking.openURL(
"https://jellyfin.org/docs/general/clients/web-config/#custom-menu-links",
)
}
>
<Switch
value={settings.showCustomMenuLinks}
disabled={pluginSettings?.showCustomMenuLinks?.locked}
onValueChange={(value) => updateSettings({ showCustomMenuLinks: value })}
onValueChange={(value) =>
updateSettings({ showCustomMenuLinks: value })
}
/>
</ListItem>
<ListItem
@@ -183,16 +214,25 @@ export const OtherSettings: React.FC = () => {
title={t("home.settings.other.hide_libraries")}
showArrow
/>
<ListItem title={t("home.settings.other.default_quality")} disabled={pluginSettings?.defaultBitrate?.locked}>
<ListItem
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">
<Text className="mr-1 text-[#8E8D91]">{settings.defaultBitrate?.key}</Text>
<Ionicons name="chevron-expand-sharp" size={18} color="#5A5960" />
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>
{settings.defaultBitrate?.key}
</Text>
<Ionicons
name='chevron-expand-sharp'
size={18}
color='#5A5960'
/>
</TouchableOpacity>
}
label={t("home.settings.other.default_quality")}
@@ -206,7 +246,9 @@ export const OtherSettings: React.FC = () => {
<Switch
value={settings.disableHapticFeedback}
disabled={pluginSettings?.disableHapticFeedback?.locked}
onValueChange={(disableHapticFeedback) => updateSettings({ disableHapticFeedback })}
onValueChange={(disableHapticFeedback) =>
updateSettings({ disableHapticFeedback })
}
/>
</ListItem>
</ListGroup>

View File

@@ -1,10 +1,10 @@
import { useSettings } from "@/utils/atoms/settings";
import { useRouter } from "expo-router";
import React from "react";
import { useTranslation } from "react-i18next";
import { View } from "react-native";
import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
import { useTranslation } from "react-i18next";
export const PluginSettings = () => {
const [settings, updateSettings] = useSettings();
@@ -16,7 +16,10 @@ export const PluginSettings = () => {
if (!settings) return null;
return (
<View>
<ListGroup title={t("home.settings.plugins.plugins_title")} className="mb-4">
<ListGroup
title={t("home.settings.plugins.plugins_title")}
className='mb-4'
>
<ListItem
onPress={() => router.push("/settings/jellyseerr/page")}
title={"Jellyseerr"}
@@ -24,7 +27,7 @@ export const PluginSettings = () => {
/>
<ListItem
onPress={() => router.push("/settings/marlin-search/page")}
title="Marlin Search"
title='Marlin Search'
showArrow
/>
</ListGroup>

View File

@@ -1,17 +1,18 @@
import { useHaptic } from "@/hooks/useHaptic";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import {
BottomSheetBackdrop,
BottomSheetBackdropProps,
type BottomSheetBackdropProps,
BottomSheetModal,
BottomSheetTextInput,
BottomSheetView,
} from "@gorhom/bottom-sheet";
import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api";
import { useTranslation } from "react-i18next";
import { useHaptic } from "@/hooks/useHaptic";
import { useAtom } from "jotai";
import React, { useCallback, useRef, useState } from "react";
import { Alert, View, ViewProps } from "react-native";
import type React from "react";
import { useCallback, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Alert, View, type ViewProps } from "react-native";
import { Button } from "../Button";
import { Text } from "../common/Text";
import { ListGroup } from "../list/ListGroup";
@@ -37,7 +38,7 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
appearsOnIndex={0}
/>
),
[]
[],
);
const authorizeQuickConnect = useCallback(async () => {
@@ -51,7 +52,7 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
successHapticFeedback();
Alert.alert(
t("home.settings.quick_connect.success"),
t("home.settings.quick_connect.quick_connect_autorized")
t("home.settings.quick_connect.quick_connect_autorized"),
);
setQuickConnectCode(undefined);
bottomSheetModalRef?.current?.close();
@@ -59,14 +60,14 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
errorHapticFeedback();
Alert.alert(
t("home.settings.quick_connect.error"),
t("home.settings.quick_connect.invalid_code")
t("home.settings.quick_connect.invalid_code"),
);
}
} catch (e) {
errorHapticFeedback();
Alert.alert(
t("home.settings.quick_connect.error"),
t("home.settings.quick_connect.invalid_code")
t("home.settings.quick_connect.invalid_code"),
);
}
}
@@ -78,7 +79,7 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
<ListItem
onPress={() => bottomSheetModalRef?.current?.present()}
title={t("home.settings.quick_connect.authorize_button")}
textColor="blue"
textColor='blue'
/>
</ListGroup>
@@ -94,30 +95,30 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
backdropComponent={renderBackdrop}
>
<BottomSheetView>
<View className="flex flex-col space-y-4 px-4 pb-8 pt-2">
<View className='flex flex-col space-y-4 px-4 pb-8 pt-2'>
<View>
<Text className="font-bold text-2xl text-neutral-100">
<Text className='font-bold text-2xl text-neutral-100'>
{t("home.settings.quick_connect.quick_connect_title")}
</Text>
</View>
<View className="flex flex-col space-y-2">
<View className="p-4 border border-neutral-800 rounded-xl bg-neutral-900 w-full">
<View className='flex flex-col space-y-2'>
<View className='p-4 border border-neutral-800 rounded-xl bg-neutral-900 w-full'>
<BottomSheetTextInput
style={{ color: "white" }}
clearButtonMode="always"
clearButtonMode='always'
placeholder={t(
"home.settings.quick_connect.enter_the_quick_connect_code"
"home.settings.quick_connect.enter_the_quick_connect_code",
)}
placeholderTextColor="#9CA3AF"
placeholderTextColor='#9CA3AF'
value={quickConnectCode}
onChangeText={setQuickConnectCode}
/>
</View>
</View>
<Button
className="mt-auto"
className='mt-auto'
onPress={authorizeQuickConnect}
color="purple"
color='purple'
>
{t("home.settings.quick_connect.authorize")}
</Button>

View File

@@ -1,14 +1,14 @@
import { Text } from "@/components/common/Text";
import { Colors } from "@/constants/Colors";
import { useHaptic } from "@/hooks/useHaptic";
import { useDownload } from "@/providers/DownloadProvider";
import { useQuery } from "@tanstack/react-query";
import * as FileSystem from "expo-file-system";
import { useTranslation } from "react-i18next";
import { View } from "react-native";
import { toast } from "sonner-native";
import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
import { useTranslation } from "react-i18next";
import {Colors} from "@/constants/Colors";
export const StorageSettings = () => {
const { deleteAllFiles, appSizeUsage } = useDownload();
@@ -44,11 +44,11 @@ export const StorageSettings = () => {
return (
<View>
<View className="flex flex-col gap-y-1">
<View className="flex flex-row items-center justify-between">
<Text className="">{t("home.settings.storage.storage_title")}</Text>
<View className='flex flex-col gap-y-1'>
<View className='flex flex-row items-center justify-between'>
<Text className=''>{t("home.settings.storage.storage_title")}</Text>
{size && (
<Text className="text-neutral-500">
<Text className='text-neutral-500'>
{t("home.settings.storage.size_used", {
used: Number(size.total - size.remaining).bytesToReadable(),
total: size.total?.bytesToReadable(),
@@ -56,7 +56,7 @@ export const StorageSettings = () => {
</Text>
)}
</View>
<View className="h-3 w-full bg-gray-100/10 rounded-md overflow-hidden flex flex-row">
<View className='h-3 w-full bg-gray-100/10 rounded-md overflow-hidden flex flex-row'>
{size && (
<>
<View
@@ -77,24 +77,24 @@ export const StorageSettings = () => {
</>
)}
</View>
<View className="flex flex-row gap-x-2">
<View className='flex flex-row gap-x-2'>
{size && (
<>
<View className="flex flex-row items-center">
<View className="w-3 h-3 rounded-full bg-purple-600 mr-1"></View>
<Text className="text-white text-xs">
<View className='flex flex-row items-center'>
<View className='w-3 h-3 rounded-full bg-purple-600 mr-1'></View>
<Text className='text-white text-xs'>
{t("home.settings.storage.app_usage", {
usedSpace: calculatePercentage(size.app, size.total),
})}
</Text>
</View>
<View className="flex flex-row items-center">
<View className="w-3 h-3 rounded-full bg-purple-400 mr-1"></View>
<Text className="text-white text-xs">
<View className='flex flex-row items-center'>
<View className='w-3 h-3 rounded-full bg-purple-400 mr-1'></View>
<Text className='text-white text-xs'>
{t("home.settings.storage.device_usage", {
availableSpace: calculatePercentage(
size.total - size.remaining - size.app,
size.total
size.total,
),
})}
</Text>
@@ -105,7 +105,7 @@ export const StorageSettings = () => {
</View>
<ListGroup>
<ListItem
textColor="red"
textColor='red'
onPress={onDeleteClicked}
title={t("home.settings.storage.delete_all_downloaded_files")}
/>

View File

@@ -1,16 +1,16 @@
import { Platform, TouchableOpacity, View, ViewProps } from "react-native";
import { Platform, TouchableOpacity, View, type 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";
import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
import Dropdown from "@/components/common/Dropdown";
import { Stepper } from "@/components/inputs/Stepper";
import { useSettings } from "@/utils/atoms/settings";
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 Dropdown from "@/components/common/Dropdown";
import { Switch } from "react-native-gesture-handler";
import { Text } from "../common/Text";
import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
import { useMedia } from "./MediaContext";
interface Props extends ViewProps {}
@@ -46,7 +46,7 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
<ListGroup
title={t("home.settings.subtitles.subtitle_title")}
description={
<Text className="text-[#8E8D91] text-xs">
<Text className='text-[#8E8D91] text-xs'>
{t("home.settings.subtitles.subtitle_hint")}
</Text>
}
@@ -65,15 +65,15 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
}
titleExtractor={(item) => item?.DisplayName}
title={
<TouchableOpacity className="flex flex-row items-center justify-between py-3 pl-3">
<Text className="mr-1 text-[#8E8D91]">
<TouchableOpacity 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")}
</Text>
<Ionicons
name="chevron-expand-sharp"
name='chevron-expand-sharp'
size={18}
color="#5A5960"
color='#5A5960'
/>
</TouchableOpacity>
}
@@ -100,15 +100,15 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
keyExtractor={String}
titleExtractor={(item) => t(subtitleModeKeys[item]) || String(item)}
title={
<TouchableOpacity className="flex flex-row items-center justify-between py-3 pl-3">
<Text className="mr-1 text-[#8E8D91]">
<TouchableOpacity 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")}
</Text>
<Ionicons
name="chevron-expand-sharp"
name='chevron-expand-sharp'
size={18}
color="#5A5960"
color='#5A5960'
/>
</TouchableOpacity>
}

View File

@@ -1,13 +1,13 @@
import { View, ViewProps } from "react-native";
import { Text } from "../common/Text";
import { ListItem } from "../list/ListItem";
import { Button } from "../Button";
import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider";
import { useAtom } from "jotai";
import Constants from "expo-constants";
import Application from "expo-application";
import { ListGroup } from "../list/ListGroup";
import Constants from "expo-constants";
import { useAtom } from "jotai";
import { useTranslation } from "react-i18next";
import { View, type ViewProps } from "react-native";
import { Button } from "../Button";
import { Text } from "../common/Text";
import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
interface Props extends ViewProps {}
@@ -24,10 +24,22 @@ export const UserInfo: React.FC<Props> = ({ ...props }) => {
return (
<View {...props}>
<ListGroup title={t("home.settings.user_info.user_info_title")}>
<ListItem title={t("home.settings.user_info.user")} value={user?.Name} />
<ListItem title={t("home.settings.user_info.server")} value={api?.basePath} />
<ListItem title={t("home.settings.user_info.token")} value={api?.accessToken} />
<ListItem title={t("home.settings.user_info.app_version")} value={version} />
<ListItem
title={t("home.settings.user_info.user")}
value={user?.Name}
/>
<ListItem
title={t("home.settings.user_info.server")}
value={api?.basePath}
/>
<ListItem
title={t("home.settings.user_info.token")}
value={api?.accessToken}
/>
<ListItem
title={t("home.settings.user_info.app_version")}
value={version}
/>
</ListGroup>
</View>
);