first commit

This commit is contained in:
Fredrik Burmester
2024-10-09 07:49:22 +02:00
parent 0a098bf26e
commit 6c051f6f61
10 changed files with 332 additions and 48 deletions

View File

@@ -42,11 +42,17 @@ export default function IndexLayout() {
}}
/>
<Stack.Screen
name="settings"
name="settings/index"
options={{
title: "Settings",
}}
/>
<Stack.Screen
name="settings/audio-language"
options={{
title: "Audio Language",
}}
/>
{Object.entries(nestedTabPageScreenOptions).map(([name, options]) => (
<Stack.Screen key={name} name={name} options={options} />
))}

View File

@@ -0,0 +1,61 @@
import { ScrollView, View, ViewProps } from "react-native";
import { Text } from "@/components/common/Text";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { LANGUAGES } from "@/constants/Languages";
import { ListItem } from "@/components/list/ListItem";
import { ListSection } from "@/components/list/ListSection";
import { TAB_HEIGHT } from "@/constants/Values";
import { DefaultLanguageOption, useSettings } from "@/utils/atoms/settings";
import { Ionicons } from "@expo/vector-icons";
import { Colors } from "@/constants/Colors";
interface Props extends ViewProps {}
export default function page() {
const insets = useSafeAreaInsets();
const [settings, updateSettings] = useSettings();
return (
<ScrollView
contentContainerStyle={{
paddingLeft: insets.left,
paddingRight: insets.right,
paddingBottom: 16,
}}
style={{
marginBottom: TAB_HEIGHT,
}}
>
<View className="py-4 px-4">
<ListSection title="LANGUAGES">
{LANGUAGES.sort(sortByName).map((l) => (
<ListItem
key={l.value}
title={l.label}
onPress={() => {
updateSettings({
...settings,
defaultAudioLanguage: l,
});
}}
iconAfter={
settings?.defaultAudioLanguage?.value === l.value ? (
<Ionicons name="checkmark" size={24} color={Colors.primary} />
) : null
}
/>
))}
</ListSection>
</View>
</ScrollView>
);
}
const sortByName = (a: DefaultLanguageOption, b: DefaultLanguageOption) => {
if (a.label < b.label) {
return -1;
}
if (a.label > b.label) {
return 1;
}
return 0;
};

View File

@@ -1,13 +1,18 @@
import { Button } from "@/components/Button";
import { Text } from "@/components/common/Text";
import { ListItem } from "@/components/ListItem";
import { ListInputItem } from "@/components/list/ListInputItem";
import { ListItem } from "@/components/list/ListItem";
import { ListSection } from "@/components/list/ListSection";
import { SettingToggles } from "@/components/settings/SettingToggles";
import { useDownload } from "@/providers/DownloadProvider";
import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider";
import { useSettings } from "@/utils/atoms/settings";
import { clearLogs, readFromLog } from "@/utils/log";
import { Ionicons } from "@expo/vector-icons";
import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query";
import * as Haptics from "expo-haptics";
import { useRouter } from "expo-router";
import { useAtom } from "jotai";
import { Alert, ScrollView, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
@@ -16,6 +21,7 @@ import { toast } from "sonner-native";
export default function settings() {
const { logout } = useJellyfin();
const { deleteAllFiles } = useDownload();
const [settings, updateSettings] = useSettings();
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
@@ -57,6 +63,8 @@ export default function settings() {
);
};
const router = useRouter();
return (
<ScrollView
contentContainerStyle={{
@@ -73,15 +81,46 @@ export default function settings() {
>
registerBackgroundFetchAsync
</Button> */}
<View>
<Text className="font-bold text-lg mb-2">User Info</Text>
<ListSection title="USER INFO">
<ListItem title="User" text={user?.Name} />
<ListItem title="Server" text={api?.basePath} />
<ListItem title="Token" text={api?.accessToken} />
</ListSection>
<View className="flex flex-col rounded-xl overflow-hidden border-neutral-800 divide-y-2 divide-solid divide-neutral-800 ">
<ListItem title="User" subTitle={user?.Name} />
<ListItem title="Server" subTitle={api?.basePath} />
<ListItem title="Token" subTitle={api?.accessToken} />
</View>
</View>
<ListSection title="MEDIA">
<ListItem
title="Audio language"
iconAfter={
<Ionicons name="chevron-forward" size={20} color="white" />
}
onPress={() => router.push("/settings/audio-language")}
/>
<ListItem
title="Subtitle language"
iconAfter={
<Ionicons name="chevron-forward" size={20} color="white" />
}
onPress={() => router.push("/settings/subtitle-language")}
/>
<ListInputItem
textInputProps={{
placeholder: "30",
clearButtonMode: "never",
returnKeyType: "done",
}}
defaultValue={(settings?.forwardSkipTime || "").toString()}
title={"Forward skip"}
onChange={(val) => {
// 1. validate positive number
// 2. save settings
if (val.length === 0) return;
if (val.match(/^\d+$/)) {
} else {
toast.error("Invalid number");
}
}}
/>
</ListSection>
<View>
<Text className="font-bold text-lg mb-2">Quick connect</Text>

View File

@@ -0,0 +1,61 @@
import { ScrollView, View, ViewProps } from "react-native";
import { Text } from "@/components/common/Text";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { LANGUAGES } from "@/constants/Languages";
import { ListItem } from "@/components/list/ListItem";
import { ListSection } from "@/components/list/ListSection";
import { TAB_HEIGHT } from "@/constants/Values";
import { DefaultLanguageOption, useSettings } from "@/utils/atoms/settings";
import { Ionicons } from "@expo/vector-icons";
import { Colors } from "@/constants/Colors";
interface Props extends ViewProps {}
export default function page() {
const insets = useSafeAreaInsets();
const [settings, updateSettings] = useSettings();
return (
<ScrollView
contentContainerStyle={{
paddingLeft: insets.left,
paddingRight: insets.right,
paddingBottom: 16,
}}
style={{
marginBottom: TAB_HEIGHT,
}}
>
<View className="py-4 px-4">
<ListSection title="LANGUAGES">
{LANGUAGES.sort(sortByName).map((l) => (
<ListItem
key={l.value}
title={l.label}
onPress={() => {
updateSettings({
...settings,
defaultSubtitleLanguage: l,
});
}}
iconAfter={
settings?.defaultSubtitleLanguage?.value === l.value ? (
<Ionicons name="checkmark" size={24} color={Colors.primary} />
) : null
}
/>
))}
</ListSection>
</View>
</ScrollView>
);
}
const sortByName = (a: DefaultLanguageOption, b: DefaultLanguageOption) => {
if (a.label < b.label) {
return -1;
}
if (a.label > b.label) {
return 1;
}
return 0;
};

View File

@@ -1,35 +0,0 @@
import { PropsWithChildren, ReactNode } from "react";
import { View, ViewProps } from "react-native";
import { Text } from "./common/Text";
interface Props extends ViewProps {
title?: string | null | undefined;
subTitle?: string | null | undefined;
children?: ReactNode;
iconAfter?: ReactNode;
}
export const ListItem: React.FC<PropsWithChildren<Props>> = ({
title,
subTitle,
iconAfter,
children,
...props
}) => {
return (
<View
className="flex flex-row items-center justify-between bg-neutral-900 p-4"
{...props}
>
<View className="flex flex-col overflow-visible">
<Text className="font-bold ">{title}</Text>
{subTitle && (
<Text uiTextView selectable className="text-xs">
{subTitle}
</Text>
)}
</View>
{iconAfter}
</View>
);
};

View File

@@ -0,0 +1,22 @@
import { ScrollView, View, ViewProps } from "react-native";
import { Text } from "@/components/common/Text";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { TAB_HEIGHT } from "@/constants/Values";
interface Props extends ViewProps {}
export default function page() {
const insets = useSafeAreaInsets();
return (
<ScrollView
contentContainerStyle={{
paddingLeft: insets.left,
paddingRight: insets.right,
paddingBottom: 16,
}}
style={{
marginBottom: TAB_HEIGHT,
}}
></ScrollView>
);
}

View File

@@ -0,0 +1,64 @@
import { PropsWithChildren, ReactNode, useEffect, useState } from "react";
import {
Pressable,
TextInput,
TextInputProps,
TouchableOpacity,
TouchableOpacityProps,
View,
ViewProps,
} from "react-native";
import { Text } from "../common/Text";
interface Props extends ViewProps {
title?: string | null | undefined;
text?: string | null | undefined;
children?: ReactNode;
iconAfter?: ReactNode;
iconBefore?: ReactNode;
textInputProps?: TextInputProps;
defaultValue?: string;
onChange: (text: string) => void;
}
export const ListInputItem: React.FC<PropsWithChildren<Props>> = ({
title,
text,
iconAfter,
iconBefore,
children,
onChange,
textInputProps,
defaultValue,
...props
}) => {
const [value, setValue] = useState<string>(defaultValue || "");
useEffect(() => {
onChange(value);
}, [value]);
return (
<View
className={`flex flex-row items-center justify-between px-4 h-12 bg-neutral-900`}
{...props}
>
{iconBefore && <View className="mr-2">{iconBefore}</View>}
<View>
<Text className="">{title}</Text>
</View>
<View className="ml-auto">
<TextInput
inputMode="numeric"
keyboardType="decimal-pad"
style={{ color: "white" }}
value={value}
onChangeText={setValue}
className=""
{...textInputProps}
/>
</View>
{iconAfter && <View className="ml-2">{iconAfter}</View>}
</View>
);
};

View File

@@ -0,0 +1,44 @@
import { PropsWithChildren, ReactNode, useState } from "react";
import {
Pressable,
TouchableOpacity,
TouchableOpacityProps,
View,
ViewProps,
} from "react-native";
import { Text } from "../common/Text";
interface Props extends TouchableOpacityProps {
title?: string | null | undefined;
text?: string | null | undefined;
children?: ReactNode;
iconAfter?: ReactNode;
iconBefore?: ReactNode;
}
export const ListItem: React.FC<PropsWithChildren<Props>> = ({
title,
text,
iconAfter,
iconBefore,
children,
...props
}) => {
return (
<TouchableOpacity
className={`flex flex-row items-center justify-between px-4 h-12 bg-neutral-900`}
{...props}
>
{iconBefore && <View className="mr-2">{iconBefore}</View>}
<View>
<Text className="">{title}</Text>
</View>
<View className="ml-auto">
<Text selectable className="">
{text}
</Text>
</View>
{iconAfter && <View className="ml-2">{iconAfter}</View>}
</TouchableOpacity>
);
};

View File

@@ -0,0 +1,24 @@
import { View, ViewProps } from "react-native";
import { Text } from "@/components/common/Text";
import { Children, PropsWithChildren } from "react";
interface Props extends ViewProps {
title: string;
}
export const ListSection: React.FC<PropsWithChildren<Props>> = ({
children,
title,
...props
}) => {
return (
<View {...props}>
<Text className="ml-4 mb-1 text-xs text-neutral-500 uppercase">
{title}
</Text>
<View className="flex flex-col rounded-xl overflow-hidden border-neutral-800 divide-y-2 divide-solid divide-neutral-800">
{children}
</View>
</View>
);
};

View File

@@ -10,6 +10,7 @@ import {
registerBackgroundFetchAsync,
unregisterBackgroundFetchAsync,
} from "@/utils/background-tasks";
import { getStatistics } from "@/utils/optimize-server";
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import * as BackgroundFetch from "expo-background-fetch";
@@ -18,7 +19,6 @@ import * as TaskManager from "expo-task-manager";
import { useAtom } from "jotai";
import { useEffect, useState } from "react";
import {
ActivityIndicator,
Linking,
Switch,
TouchableOpacity,
@@ -32,8 +32,6 @@ import { Input } from "../common/Input";
import { Text } from "../common/Text";
import { Loader } from "../Loader";
import { MediaToggles } from "./MediaToggles";
import axios from "axios";
import { getStatistics } from "@/utils/optimize-server";
interface Props extends ViewProps {}