diff options
| author | Mohamed Bassem <me@mbassem.com> | 2026-02-08 22:45:32 +0000 |
|---|---|---|
| committer | Mohamed Bassem <me@mbassem.com> | 2026-02-09 00:17:31 +0000 |
| commit | e455e46852900c6d2b3e77b7a77e1b9da41b2ca8 (patch) | |
| tree | 2d2042bd43d704b6432332c1465619b4b907dc71 /apps/mobile/app/dashboard/(tabs)/(settings) | |
| parent | 4186c4c64c68892248ce8671d9b8e67fc7f884a0 (diff) | |
| download | karakeep-e455e46852900c6d2b3e77b7a77e1b9da41b2ca8.tar.zst | |
feat(mobile): more native screens
Diffstat (limited to 'apps/mobile/app/dashboard/(tabs)/(settings)')
| -rw-r--r-- | apps/mobile/app/dashboard/(tabs)/(settings)/_layout.tsx | 18 | ||||
| -rw-r--r-- | apps/mobile/app/dashboard/(tabs)/(settings)/index.tsx | 225 |
2 files changed, 243 insertions, 0 deletions
diff --git a/apps/mobile/app/dashboard/(tabs)/(settings)/_layout.tsx b/apps/mobile/app/dashboard/(tabs)/(settings)/_layout.tsx new file mode 100644 index 00000000..8c51d5a3 --- /dev/null +++ b/apps/mobile/app/dashboard/(tabs)/(settings)/_layout.tsx @@ -0,0 +1,18 @@ +import { Stack } from "expo-router/stack"; + +export default function Layout() { + return ( + <Stack + screenOptions={{ + headerLargeTitle: true, + headerTransparent: true, + headerBlurEffect: "systemMaterial", + headerShadowVisible: false, + headerLargeTitleShadowVisible: false, + headerLargeStyle: { backgroundColor: "transparent" }, + }} + > + <Stack.Screen name="index" options={{ title: "Settings" }} /> + </Stack> + ); +} diff --git a/apps/mobile/app/dashboard/(tabs)/(settings)/index.tsx b/apps/mobile/app/dashboard/(tabs)/(settings)/index.tsx new file mode 100644 index 00000000..de17ff5a --- /dev/null +++ b/apps/mobile/app/dashboard/(tabs)/(settings)/index.tsx @@ -0,0 +1,225 @@ +import { useEffect } from "react"; +import { + ActivityIndicator, + Pressable, + ScrollView, + Switch, + View, +} from "react-native"; +import { Slider } from "react-native-awesome-slider"; +import { useSharedValue } from "react-native-reanimated"; +import Constants from "expo-constants"; +import { Link } from "expo-router"; +import { UserProfileHeader } from "@/components/settings/UserProfileHeader"; +import ChevronRight from "@/components/ui/ChevronRight"; +import { Divider } from "@/components/ui/Divider"; +import { Text } from "@/components/ui/Text"; +import { useServerVersion } from "@/lib/hooks"; +import { useSession } from "@/lib/session"; +import useAppSettings from "@/lib/settings"; +import { useQuery } from "@tanstack/react-query"; + +import { useTRPC } from "@karakeep/shared-react/trpc"; + +function SectionHeader({ title }: { title: string }) { + return ( + <Text className="px-4 pb-1 pt-4 text-xs uppercase tracking-wide text-muted-foreground"> + {title} + </Text> + ); +} + +export default function Settings() { + const { logout } = useSession(); + const { + settings, + setSettings, + isLoading: isSettingsLoading, + } = useAppSettings(); + const api = useTRPC(); + + const imageQuality = useSharedValue(0); + const imageQualityMin = useSharedValue(0); + const imageQualityMax = useSharedValue(100); + + useEffect(() => { + imageQuality.value = settings.imageQuality * 100; + }, [settings]); + + const { data, error } = useQuery(api.users.whoami.queryOptions()); + const { + data: serverVersion, + isLoading: isServerVersionLoading, + error: serverVersionError, + } = useServerVersion(); + + if (error?.data?.code === "UNAUTHORIZED") { + logout(); + } + + return ( + <ScrollView + contentInsetAdjustmentBehavior="automatic" + contentContainerStyle={{ paddingHorizontal: 16, paddingBottom: 40 }} + > + <UserProfileHeader + image={data?.image} + name={data?.name} + email={data?.email} + /> + + <SectionHeader title="Appearance" /> + <View + className="w-full rounded-xl bg-card py-2" + style={{ borderCurve: "continuous" }} + > + <View className="flex flex-row items-center justify-between gap-8 px-4 py-1"> + <Link asChild href="/dashboard/settings/theme" className="flex-1"> + <Pressable className="flex flex-row justify-between"> + <Text>Theme</Text> + <View className="flex flex-row items-center gap-2"> + <Text className="text-muted-foreground"> + { + { light: "Light", dark: "Dark", system: "System" }[ + settings.theme + ] + } + </Text> + <ChevronRight /> + </View> + </Pressable> + </Link> + </View> + <Divider orientation="horizontal" className="mx-6 my-1" /> + <View className="flex flex-row items-center justify-between gap-8 px-4 py-1"> + <Link + asChild + href="/dashboard/settings/bookmark-default-view" + className="flex-1" + > + <Pressable className="flex flex-row justify-between"> + <Text>Default Bookmark View</Text> + <View className="flex flex-row items-center gap-2"> + {isSettingsLoading ? ( + <ActivityIndicator size="small" /> + ) : ( + <Text className="text-muted-foreground"> + {settings.defaultBookmarkView === "reader" + ? "Reader" + : "Browser"} + </Text> + )} + <ChevronRight /> + </View> + </Pressable> + </Link> + </View> + </View> + + <SectionHeader title="Reading" /> + <View + className="w-full rounded-xl bg-card py-2" + style={{ borderCurve: "continuous" }} + > + <View className="flex flex-row items-center justify-between gap-8 px-4 py-1"> + <Link + asChild + href="/dashboard/settings/reader-settings" + className="flex-1" + > + <Pressable className="flex flex-row justify-between"> + <Text>Reader Text Settings</Text> + <ChevronRight /> + </Pressable> + </Link> + </View> + <Divider orientation="horizontal" className="mx-6 my-1" /> + <View className="flex flex-row items-center justify-between gap-8 px-4 py-1"> + <Text className="flex-1" numberOfLines={1}> + Show notes in bookmark card + </Text> + <Switch + className="shrink-0" + value={settings.showNotes} + onValueChange={(value) => + setSettings({ + ...settings, + showNotes: value, + }) + } + /> + </View> + </View> + + <SectionHeader title="Media" /> + <View + className="w-full rounded-xl bg-card py-2" + style={{ borderCurve: "continuous" }} + > + <View className="flex w-full flex-row items-center justify-between gap-8 px-4 py-1"> + <Text>Upload Image Quality</Text> + <View className="flex flex-1 flex-row items-center justify-center gap-2"> + <Text className="text-foreground"> + {Math.round(settings.imageQuality * 100)}% + </Text> + <Slider + onSlidingComplete={(value) => + setSettings({ + ...settings, + imageQuality: Math.round(value) / 100, + }) + } + progress={imageQuality} + minimumValue={imageQualityMin} + maximumValue={imageQualityMax} + /> + </View> + </View> + </View> + + <SectionHeader title="Account" /> + <View + className="w-full rounded-xl bg-card py-2" + style={{ borderCurve: "continuous" }} + > + <Pressable + className="flex flex-row items-center px-4 py-1" + onPress={logout} + > + <Text className="text-destructive">Log Out</Text> + </Pressable> + </View> + + <SectionHeader title="About" /> + <View + className="w-full rounded-xl bg-card py-2" + style={{ borderCurve: "continuous" }} + > + <View className="flex flex-row items-center justify-between px-4 py-1"> + <Text className="text-muted-foreground">Server</Text> + <Text className="text-sm text-muted-foreground"> + {isSettingsLoading ? "Loading..." : settings.address} + </Text> + </View> + <Divider orientation="horizontal" className="mx-6 my-1" /> + <View className="flex flex-row items-center justify-between px-4 py-1"> + <Text className="text-muted-foreground">App Version</Text> + <Text className="text-sm text-muted-foreground"> + {Constants.expoConfig?.version ?? "unknown"} + </Text> + </View> + <Divider orientation="horizontal" className="mx-6 my-1" /> + <View className="flex flex-row items-center justify-between px-4 py-1"> + <Text className="text-muted-foreground">Server Version</Text> + <Text className="text-sm text-muted-foreground"> + {isServerVersionLoading + ? "Loading..." + : serverVersionError + ? "unavailable" + : (serverVersion ?? "unknown")} + </Text> + </View> + </View> + </ScrollView> + ); +} |
