aboutsummaryrefslogtreecommitdiffstats
path: root/apps/mobile/app
diff options
context:
space:
mode:
Diffstat (limited to 'apps/mobile/app')
-rw-r--r--apps/mobile/app/_layout.tsx101
-rw-r--r--apps/mobile/app/dashboard/(tabs)/_layout.tsx3
-rw-r--r--apps/mobile/app/dashboard/(tabs)/index.tsx9
-rw-r--r--apps/mobile/app/dashboard/(tabs)/lists.tsx34
-rw-r--r--apps/mobile/app/dashboard/(tabs)/settings.tsx45
-rw-r--r--apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx11
-rw-r--r--apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx287
-rw-r--r--apps/mobile/app/dashboard/bookmarks/[slug]/manage_lists.tsx7
-rw-r--r--apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx26
-rw-r--r--apps/mobile/app/dashboard/bookmarks/new.tsx8
-rw-r--r--apps/mobile/app/dashboard/lists/new.tsx9
-rw-r--r--apps/mobile/app/dashboard/search.tsx52
-rw-r--r--apps/mobile/app/dashboard/settings/bookmark-default-view.tsx9
-rw-r--r--apps/mobile/app/dashboard/settings/theme.tsx9
-rw-r--r--apps/mobile/app/error.tsx5
-rw-r--r--apps/mobile/app/sharing.tsx14
-rw-r--r--apps/mobile/app/signin.tsx28
-rw-r--r--apps/mobile/app/test-connection.tsx14
18 files changed, 371 insertions, 300 deletions
diff --git a/apps/mobile/app/_layout.tsx b/apps/mobile/app/_layout.tsx
index ca3da0cb..1e6128c7 100644
--- a/apps/mobile/app/_layout.tsx
+++ b/apps/mobile/app/_layout.tsx
@@ -3,21 +3,23 @@ import "expo-dev-client";
import { useEffect } from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
+import { KeyboardProvider } from "react-native-keyboard-controller";
import { useRouter } from "expo-router";
import { Stack } from "expo-router/stack";
import { ShareIntentProvider, useShareIntent } from "expo-share-intent";
import { StatusBar } from "expo-status-bar";
import { StyledStack } from "@/components/navigation/stack";
import { Providers } from "@/lib/providers";
-import useAppSettings from "@/lib/settings";
+import { useColorScheme, useInitialAndroidBarSync } from "@/lib/useColorScheme";
import { cn } from "@/lib/utils";
-import { useColorScheme } from "nativewind";
+import { NAV_THEME } from "@/theme";
+import { ThemeProvider as NavThemeProvider } from "@react-navigation/native";
export default function RootLayout() {
+ useInitialAndroidBarSync();
const router = useRouter();
const { hasShareIntent } = useShareIntent();
- const { colorScheme, setColorScheme } = useColorScheme();
- const { settings } = useAppSettings();
+ const { colorScheme, isDarkColorScheme } = useColorScheme();
useEffect(() => {
if (hasShareIntent) {
@@ -27,52 +29,55 @@ export default function RootLayout() {
}
}, [hasShareIntent]);
- useEffect(() => {
- setColorScheme(settings.theme);
- }, [settings.theme]);
-
return (
<>
- <StyledStack
- layout={(props) => {
- return (
- <GestureHandlerRootView style={{ flex: 1 }}>
- <ShareIntentProvider>
- <Providers>{props.children}</Providers>
- </ShareIntentProvider>
- </GestureHandlerRootView>
- );
- }}
- contentClassName={cn(
- "w-full flex-1 bg-gray-100 text-foreground dark:bg-background",
- colorScheme == "dark" ? "dark" : "light",
- )}
- screenOptions={{
- headerTitle: "",
- headerTransparent: true,
- }}
- >
- <Stack.Screen name="index" />
- <Stack.Screen
- name="signin"
- options={{
- headerShown: true,
- headerBackVisible: true,
- headerBackTitle: "Back",
- title: "",
- }}
- />
- <Stack.Screen name="sharing" />
- <Stack.Screen
- name="test-connection"
- options={{
- title: "Test Connection",
- headerShown: true,
- presentation: "modal",
- }}
- />
- </StyledStack>
- <StatusBar style="auto" />
+ <KeyboardProvider statusBarTranslucent navigationBarTranslucent>
+ <NavThemeProvider value={NAV_THEME[colorScheme]}>
+ <StyledStack
+ layout={(props) => {
+ return (
+ <GestureHandlerRootView style={{ flex: 1 }}>
+ <ShareIntentProvider>
+ <Providers>{props.children}</Providers>
+ </ShareIntentProvider>
+ </GestureHandlerRootView>
+ );
+ }}
+ contentClassName={cn(
+ "w-full flex-1 bg-gray-100 text-foreground dark:bg-background",
+ colorScheme == "dark" ? "dark" : "light",
+ )}
+ screenOptions={{
+ headerTitle: "",
+ headerTransparent: true,
+ }}
+ >
+ <Stack.Screen name="index" />
+ <Stack.Screen
+ name="signin"
+ options={{
+ headerShown: true,
+ headerBackVisible: true,
+ headerBackTitle: "Back",
+ title: "",
+ }}
+ />
+ <Stack.Screen name="sharing" />
+ <Stack.Screen
+ name="test-connection"
+ options={{
+ title: "Test Connection",
+ headerShown: true,
+ presentation: "modal",
+ }}
+ />
+ </StyledStack>
+ </NavThemeProvider>
+ </KeyboardProvider>
+ <StatusBar
+ key={`root-status-bar-${isDarkColorScheme ? "light" : "dark"}`}
+ style={isDarkColorScheme ? "light" : "dark"}
+ />
</>
);
}
diff --git a/apps/mobile/app/dashboard/(tabs)/_layout.tsx b/apps/mobile/app/dashboard/(tabs)/_layout.tsx
index f1d90ee4..7419c348 100644
--- a/apps/mobile/app/dashboard/(tabs)/_layout.tsx
+++ b/apps/mobile/app/dashboard/(tabs)/_layout.tsx
@@ -1,9 +1,11 @@
import React, { useLayoutEffect } from "react";
import { Tabs, useNavigation } from "expo-router";
import { StyledTabs } from "@/components/navigation/tabs";
+import { useColorScheme } from "@/lib/useColorScheme";
import { ClipboardList, Home, Settings } from "lucide-react-native";
export default function TabLayout() {
+ const { colors } = useColorScheme();
const navigation = useNavigation();
// Hide the header on the parent screen
useLayoutEffect(() => {
@@ -18,6 +20,7 @@ export default function TabLayout() {
sceneClassName="bg-gray-100 dark:bg-background"
screenOptions={{
headerShown: false,
+ tabBarActiveTintColor: colors.foreground,
}}
>
<Tabs.Screen
diff --git a/apps/mobile/app/dashboard/(tabs)/index.tsx b/apps/mobile/app/dashboard/(tabs)/index.tsx
index f70474a9..0a51b817 100644
--- a/apps/mobile/app/dashboard/(tabs)/index.tsx
+++ b/apps/mobile/app/dashboard/(tabs)/index.tsx
@@ -1,4 +1,4 @@
-import { Platform, Pressable, Text, View } from "react-native";
+import { Platform, Pressable, View } from "react-native";
import * as Haptics from "expo-haptics";
import * as ImagePicker from "expo-image-picker";
import { router } from "expo-router";
@@ -6,6 +6,7 @@ import UpdatingBookmarkList from "@/components/bookmarks/UpdatingBookmarkList";
import { TailwindResolver } from "@/components/TailwindResolver";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import PageTitle from "@/components/ui/PageTitle";
+import { Text } from "@/components/ui/Text";
import { useToast } from "@/components/ui/Toast";
import useAppSettings from "@/lib/settings";
import { useUploadAsset } from "@/lib/upload";
@@ -89,16 +90,16 @@ export default function Home() {
/>
</View>
<Pressable
- className="flex flex-row items-center gap-1 rounded-lg border border-input bg-background px-4 py-2.5"
+ className="flex flex-row items-center gap-1 rounded-lg border border-input bg-card px-4 py-1"
onPress={() => router.push("/dashboard/search")}
>
<TailwindResolver
- className="text-muted-foreground"
+ className="text-muted"
comp={(styles) => (
<Search size={16} color={styles?.color?.toString()} />
)}
/>
- <Text className="text-muted-foreground">Search</Text>
+ <Text className="text-muted">Search</Text>
</Pressable>
</View>
}
diff --git a/apps/mobile/app/dashboard/(tabs)/lists.tsx b/apps/mobile/app/dashboard/(tabs)/lists.tsx
index 218c1de4..a2301c36 100644
--- a/apps/mobile/app/dashboard/(tabs)/lists.tsx
+++ b/apps/mobile/app/dashboard/(tabs)/lists.tsx
@@ -1,15 +1,17 @@
import { useEffect, useState } from "react";
-import { FlatList, Pressable, Text, View } from "react-native";
+import { FlatList, Pressable, View } from "react-native";
import * as Haptics from "expo-haptics";
import { Link, router } from "expo-router";
import FullPageError from "@/components/FullPageError";
-import { TailwindResolver } from "@/components/TailwindResolver";
+import ChevronRight from "@/components/ui/ChevronRight";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
import PageTitle from "@/components/ui/PageTitle";
+import { Text } from "@/components/ui/Text";
import { api } from "@/lib/trpc";
+import { useColorScheme } from "@/lib/useColorScheme";
import { condProps } from "@/lib/utils";
-import { ChevronRight, Plus } from "lucide-react-native";
+import { Plus } from "lucide-react-native";
import { useBookmarkLists } from "@karakeep/shared-react/hooks/lists";
import { ZBookmarkListTreeNode } from "@karakeep/shared/utils/listUtils";
@@ -65,6 +67,7 @@ function traverseTree(
}
export default function Lists() {
+ const { colors } = useColorScheme();
const [refreshing, setRefreshing] = useState(false);
const { data: lists, isPending, error, refetch } = useBookmarkLists();
const [showChildrenOf, setShowChildrenOf] = useState<Record<string, boolean>>(
@@ -130,7 +133,7 @@ export default function Lists() {
}}
renderItem={(l) => (
<View
- className="mx-2 flex flex-row items-center rounded-xl border border-input bg-white px-4 py-2 dark:bg-accent"
+ className="mx-2 flex flex-row items-center rounded-xl border border-input bg-card px-4 py-2"
style={condProps({
condition: l.item.level > 0,
props: { marginLeft: l.item.level * 20 },
@@ -146,28 +149,23 @@ export default function Lists() {
}));
}}
>
- <TailwindResolver
- className="text-foreground"
- comp={(style) => (
- <ChevronRight
- color={style?.color?.toString()}
- style={{
- transform: [
- { rotate: l.item.collapsed ? "0deg" : "90deg" },
- ],
- }}
- />
- )}
+ <ChevronRight
+ color={colors.foreground}
+ style={{
+ transform: [
+ { rotate: l.item.collapsed ? "0deg" : "90deg" },
+ ],
+ }}
/>
</Pressable>
)}
<Link asChild key={l.item.id} href={l.item.href} className="flex-1">
<Pressable className="flex flex-row justify-between">
- <Text className="text-lg text-accent-foreground">
+ <Text>
{l.item.logo} {l.item.name}
</Text>
- <ChevronRight color="rgb(0, 122, 255)" />
+ <ChevronRight />
</Pressable>
</Link>
</View>
diff --git a/apps/mobile/app/dashboard/(tabs)/settings.tsx b/apps/mobile/app/dashboard/(tabs)/settings.tsx
index 7b3dab4f..6d76308d 100644
--- a/apps/mobile/app/dashboard/(tabs)/settings.tsx
+++ b/apps/mobile/app/dashboard/(tabs)/settings.tsx
@@ -1,16 +1,17 @@
import { useEffect } from "react";
-import { ActivityIndicator, Pressable, Text, View } from "react-native";
+import { ActivityIndicator, Pressable, View } from "react-native";
import { Slider } from "react-native-awesome-slider";
import { useSharedValue } from "react-native-reanimated";
import { Link } from "expo-router";
import { Button } from "@/components/ui/Button";
+import ChevronRight from "@/components/ui/ChevronRight";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import { Divider } from "@/components/ui/Divider";
import PageTitle from "@/components/ui/PageTitle";
+import { Text } from "@/components/ui/Text";
import { useSession } from "@/lib/session";
import useAppSettings from "@/lib/settings";
import { api } from "@/lib/trpc";
-import { ChevronRight } from "lucide-react-native";
export default function Dashboard() {
const { logout } = useSession();
@@ -38,56 +39,50 @@ export default function Dashboard() {
<CustomSafeAreaView>
<PageTitle title="Settings" />
<View className="flex h-full w-full items-center gap-3 px-4 py-2">
- <View className="flex w-full gap-3 rounded-lg bg-white px-4 py-2 dark:bg-accent">
- <Text className="text-lg text-accent-foreground">
- {isSettingsLoading ? "Loading ..." : settings.address}
- </Text>
+ <View className="flex w-full gap-3 rounded-lg bg-card px-4 py-2">
+ <Text>{isSettingsLoading ? "Loading ..." : settings.address}</Text>
<Divider orientation="horizontal" />
- <Text className="text-lg text-accent-foreground">
- {isLoading ? "Loading ..." : data?.email}
- </Text>
+ <Text>{isLoading ? "Loading ..." : data?.email}</Text>
</View>
<Text className="w-full p-1 text-2xl font-bold text-foreground">
App Settings
</Text>
- <View className="flex w-full flex-row items-center justify-between gap-8 rounded-lg bg-white px-4 py-2 dark:bg-accent">
+ <View className="flex w-full flex-row items-center justify-between gap-8 rounded-lg bg-card px-4 py-2">
<Link asChild href="/dashboard/settings/theme" className="flex-1">
<Pressable className="flex flex-row justify-between">
- <Text className="text-lg text-accent-foreground">Theme</Text>
+ <Text>Theme</Text>
<View className="flex flex-row items-center gap-2">
- <Text className="text-lg text-muted-foreground">
+ <Text className="text-muted-foreground">
{
{ light: "Light", dark: "Dark", system: "System" }[
settings.theme
]
}
</Text>
- <ChevronRight color="rgb(0, 122, 255)" />
+ <ChevronRight />
</View>
</Pressable>
</Link>
</View>
- <View className="flex w-full flex-row items-center justify-between gap-8 rounded-lg bg-white px-4 py-2 dark:bg-accent">
+ <View className="flex w-full flex-row items-center justify-between gap-8 rounded-lg bg-card px-4 py-2">
<Link
asChild
href="/dashboard/settings/bookmark-default-view"
className="flex-1"
>
<Pressable className="flex flex-row justify-between">
- <Text className="text-lg text-accent-foreground">
- Default Bookmark View
- </Text>
+ <Text>Default Bookmark View</Text>
<View className="flex flex-row items-center gap-2">
{isSettingsLoading ? (
<ActivityIndicator size="small" />
) : (
- <Text className="text-lg text-muted-foreground">
+ <Text className="text-muted-foreground">
{settings.defaultBookmarkView === "reader"
? "Reader"
: "Browser"}
</Text>
)}
- <ChevronRight color="rgb(0, 122, 255)" />
+ <ChevronRight />
</View>
</Pressable>
</Link>
@@ -95,8 +90,8 @@ export default function Dashboard() {
<Text className="w-full p-1 text-2xl font-bold text-foreground">
Upload Settings
</Text>
- <View className="flex w-full flex-row items-center justify-between gap-8 rounded-lg bg-white px-4 py-2 dark:bg-accent">
- <Text className="text-lg text-accent-foreground">Image Quality</Text>
+ <View className="flex w-full flex-row items-center justify-between gap-8 rounded-lg bg-card px-4 py-2">
+ <Text>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)}%
@@ -115,7 +110,13 @@ export default function Dashboard() {
</View>
</View>
<Divider orientation="horizontal" />
- <Button className="w-full" label="Log Out" onPress={logout} />
+ <Button
+ androidRootClassName="w-full"
+ onPress={logout}
+ variant="destructive"
+ >
+ <Text>Log Out</Text>
+ </Button>
</View>
</CustomSafeAreaView>
);
diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx
index eafcfc19..3b1300ca 100644
--- a/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx
+++ b/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx
@@ -25,6 +25,7 @@ import { Button } from "@/components/ui/Button";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
import { Input } from "@/components/ui/Input";
+import { Text } from "@/components/ui/Text";
import { useToast } from "@/components/ui/Toast";
import { useAssetUrl } from "@/lib/hooks";
import useAppSettings from "@/lib/settings";
@@ -296,15 +297,17 @@ function BookmarkTextView({ bookmark }: { bookmark: ZBookmark }) {
<View className="flex-1">
{isEditing && (
<View className="absolute right-0 top-0 z-10 m-4 flex flex-row gap-1">
- <Button label="Save" variant="default" onPress={Keyboard.dismiss} />
+ <Button onPress={Keyboard.dismiss}>
+ <Text>Save</Text>
+ </Button>
<Button
- label="Discard"
- variant="destructive"
onPress={() => {
setContent(initialText);
setIsEditing(false);
}}
- />
+ >
+ <Text>Discard</Text>
+ </Button>
</View>
)}
<ScrollView className="flex bg-background p-2">
diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx
index af124160..1781ec74 100644
--- a/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx
+++ b/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx
@@ -1,26 +1,22 @@
import React from "react";
+import { Alert, Pressable, View } from "react-native";
import {
- Alert,
- Keyboard,
- Pressable,
- Text,
- TouchableWithoutFeedback,
- View,
-} from "react-native";
-import Animated, {
- useAnimatedKeyboard,
- useAnimatedStyle,
-} from "react-native-reanimated";
+ KeyboardAwareScrollView,
+ KeyboardGestureArea,
+} from "react-native-keyboard-controller";
+import { useSafeAreaInsets } from "react-native-safe-area-context";
import { router, Stack, useLocalSearchParams } from "expo-router";
import TagPill from "@/components/bookmarks/TagPill";
import FullPageError from "@/components/FullPageError";
import { Button } from "@/components/ui/Button";
+import ChevronRight from "@/components/ui/ChevronRight";
import { Divider } from "@/components/ui/Divider";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
import { Input } from "@/components/ui/Input";
import { Skeleton } from "@/components/ui/Skeleton";
+import { Text } from "@/components/ui/Text";
import { useToast } from "@/components/ui/Toast";
-import { ChevronRight } from "lucide-react-native";
+import { cn } from "@/lib/utils";
import {
useAutoRefreshingBookmarkQuery,
@@ -30,9 +26,21 @@ import {
import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks";
import { isBookmarkStillTagging } from "@karakeep/shared/utils/bookmarkUtils";
+function InfoSection({
+ className,
+ ...props
+}: React.ComponentProps<typeof View>) {
+ return (
+ <View
+ className={cn("flex gap-2 rounded-lg bg-card p-3", className)}
+ {...props}
+ />
+ );
+}
+
function TagList({ bookmark }: { bookmark: ZBookmark }) {
return (
- <View className="flex gap-2 rounded-lg bg-white py-3 dark:bg-accent">
+ <InfoSection>
{isBookmarkStillTagging(bookmark) ? (
<View className="flex gap-4 pb-3">
<Skeleton className="h-4 w-full" />
@@ -41,7 +49,7 @@ function TagList({ bookmark }: { bookmark: ZBookmark }) {
) : (
bookmark.tags.length > 0 && (
<>
- <View className="flex flex-row flex-wrap gap-2 rounded-lg bg-background p-2">
+ <View className="flex flex-row flex-wrap gap-2 rounded-lg p-2">
{bookmark.tags.map((t) => (
<TagPill key={t.id} tag={t} />
))}
@@ -50,94 +58,104 @@ function TagList({ bookmark }: { bookmark: ZBookmark }) {
</>
)
)}
- <Pressable
- onPress={() =>
- router.push(`/dashboard/bookmarks/${bookmark.id}/manage_tags`)
- }
- className="flex w-full flex-row justify-between gap-3 px-4"
- >
- <Text className="text-lg text-accent-foreground">Manage Tags</Text>
- <ChevronRight color="rgb(0, 122, 255)" />
- </Pressable>
- </View>
+ <View>
+ <Pressable
+ onPress={() =>
+ router.push(`/dashboard/bookmarks/${bookmark.id}/manage_tags`)
+ }
+ className="flex w-full flex-row justify-between gap-3"
+ >
+ <Text>Manage Tags</Text>
+ <ChevronRight />
+ </Pressable>
+ </View>
+ </InfoSection>
);
}
function ManageLists({ bookmark }: { bookmark: ZBookmark }) {
return (
- <View className="flex gap-4">
- <Pressable
- onPress={() =>
- router.push(`/dashboard/bookmarks/${bookmark.id}/manage_lists`)
- }
- className="flex w-full flex-row justify-between gap-3 rounded-lg bg-white px-4 py-2 dark:bg-accent"
- >
- <Text className="text-lg text-accent-foreground">Manage Lists</Text>
- <ChevronRight color="rgb(0, 122, 255)" />
- </Pressable>
- </View>
+ <InfoSection>
+ <View>
+ <Pressable
+ onPress={() =>
+ router.push(`/dashboard/bookmarks/${bookmark.id}/manage_lists`)
+ }
+ className="flex w-full flex-row justify-between gap-3 rounded-lg"
+ >
+ <Text>Manage Lists</Text>
+ <ChevronRight />
+ </Pressable>
+ </View>
+ </InfoSection>
);
}
function TitleEditor({
- bookmarkId,
title,
+ setTitle,
+ isPending,
}: {
- bookmarkId: string;
- title: string;
+ title: string | null | undefined;
+ setTitle: (title: string | null) => void;
+ isPending: boolean;
}) {
- const { mutate, isPending } = useUpdateBookmark();
return (
- <View className="flex gap-4">
+ <InfoSection>
<Input
editable={!isPending}
- multiline={true}
+ multiline={false}
numberOfLines={1}
- loading={isPending}
placeholder="Title"
- textAlignVertical="top"
- onEndEditing={(ev) =>
- mutate({
- bookmarkId,
- title: ev.nativeEvent.text ? ev.nativeEvent.text : null,
- })
- }
+ onChangeText={(text) => setTitle(text)}
defaultValue={title ?? ""}
/>
- </View>
+ </InfoSection>
);
}
-function NotesEditor({ bookmark }: { bookmark: ZBookmark }) {
- const { mutate, isPending } = useUpdateBookmark();
+function NotesEditor({
+ notes,
+ setNotes,
+ isPending,
+}: {
+ notes: string | null | undefined;
+ setNotes: (title: string | null) => void;
+ isPending: boolean;
+}) {
return (
- <View className="flex gap-4">
+ <InfoSection>
<Input
editable={!isPending}
multiline={true}
- numberOfLines={3}
- loading={isPending}
placeholder="Notes"
+ inputClasses="h-24"
+ onChangeText={(text) => setNotes(text)}
textAlignVertical="top"
- onEndEditing={(ev) =>
- mutate({
- bookmarkId: bookmark.id,
- note: ev.nativeEvent.text,
- })
- }
- defaultValue={bookmark.note ?? ""}
+ defaultValue={notes ?? ""}
/>
- </View>
+ </InfoSection>
);
}
const ViewBookmarkPage = () => {
+ const insets = useSafeAreaInsets();
const { slug } = useLocalSearchParams();
const { toast } = useToast();
if (typeof slug !== "string") {
throw new Error("Unexpected param type");
}
+ const { mutate: editBookmark, isPending: isEditPending } = useUpdateBookmark({
+ onSuccess: () => {
+ toast({
+ message: "The bookmark has been updated!",
+ showProgress: false,
+ });
+ setEditedBookmark({});
+ },
+ });
+
const { mutate: deleteBookmark, isPending: isDeletionPending } =
useDeleteBookmark({
onSuccess: () => {
@@ -149,12 +167,6 @@ const ViewBookmarkPage = () => {
},
});
- const keyboard = useAnimatedKeyboard();
-
- const animatedStyles = useAnimatedStyle(() => ({
- marginBottom: keyboard.height.value,
- }));
-
const {
data: bookmark,
isPending,
@@ -163,6 +175,11 @@ const ViewBookmarkPage = () => {
bookmarkId: slug,
});
+ const [editedBookmark, setEditedBookmark] = React.useState<{
+ title?: string | null;
+ note?: string;
+ }>({});
+
if (isPending) {
return <FullPageSpinner />;
}
@@ -188,6 +205,27 @@ const ViewBookmarkPage = () => {
);
};
+ const onDone = () => {
+ const doDone = () => {
+ if (router.canGoBack()) {
+ router.back();
+ } else {
+ router.replace("dashboard");
+ }
+ };
+ if (Object.keys(editedBookmark).length === 0) {
+ doDone();
+ return;
+ }
+ Alert.alert("You have unsaved changes", "Do you still want to leave?", [
+ { text: "Cancel", style: "cancel" },
+ {
+ text: "Leave",
+ onPress: doDone,
+ },
+ ]);
+ };
+
let title = null;
switch (bookmark.content.type) {
case BookmarkTypes.LINK:
@@ -201,56 +239,77 @@ const ViewBookmarkPage = () => {
break;
}
return (
- <View>
- <Stack.Screen
- options={{
- headerShown: true,
- headerTransparent: false,
- headerTitle: title ?? "Untitled",
- headerRight: () => (
- <Pressable
- onPress={() => {
- if (router.canGoBack()) {
- router.back();
- } else {
- router.replace("dashboard");
- }
- }}
+ <KeyboardGestureArea interpolator="ios">
+ <KeyboardAwareScrollView
+ className="p-4"
+ bottomOffset={8}
+ keyboardDismissMode="interactive"
+ contentContainerStyle={{ paddingBottom: insets.bottom }}
+ >
+ <Stack.Screen
+ options={{
+ headerShown: true,
+ headerTransparent: false,
+ headerTitle: title ?? "Untitled",
+ headerRight: () => (
+ <Pressable onPress={onDone}>
+ <Text>Done</Text>
+ </Pressable>
+ ),
+ }}
+ />
+ <View className="gap-4">
+ <TitleEditor
+ title={title}
+ setTitle={(title) =>
+ setEditedBookmark((prev) => ({ ...prev, title }))
+ }
+ isPending={isEditPending}
+ />
+ <TagList bookmark={bookmark} />
+ <ManageLists bookmark={bookmark} />
+ <NotesEditor
+ notes={bookmark.note}
+ setNotes={(note) =>
+ setEditedBookmark((prev) => ({ ...prev, note: note ?? "" }))
+ }
+ isPending={isEditPending}
+ />
+ <View className="flex justify-between gap-3">
+ <Button
+ onPress={() =>
+ editBookmark({
+ bookmarkId: bookmark.id,
+ ...editedBookmark,
+ })
+ }
+ disabled={isEditPending}
>
- <Text className="text-foreground">Done</Text>
- </Pressable>
- ),
- }}
- />
- <Animated.ScrollView className="p-4" style={[animatedStyles]}>
- <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
- <View className="h-screen gap-8 px-2">
- <TitleEditor bookmarkId={bookmark.id} title={title ?? ""} />
- <TagList bookmark={bookmark} />
- <ManageLists bookmark={bookmark} />
- <NotesEditor bookmark={bookmark} />
+ <Text>Save</Text>
+ </Button>
<Button
- onPress={handleDeleteBookmark}
variant="destructive"
+ onPress={handleDeleteBookmark}
disabled={isDeletionPending}
- label="Delete"
- />
- <View className="gap-2">
- <Text className="items-center text-center">
- Created {bookmark.createdAt.toLocaleString()}
- </Text>
- {bookmark.modifiedAt &&
- bookmark.modifiedAt.getTime() !==
- bookmark.createdAt.getTime() && (
- <Text className="items-center text-center">
- Modified {bookmark.modifiedAt.toLocaleString()}
- </Text>
- )}
- </View>
+ >
+ <Text>Delete</Text>
+ </Button>
+ </View>
+ <View className="gap-2">
+ <Text className="items-center text-center">
+ Created {bookmark.createdAt.toLocaleString()}
+ </Text>
+ {bookmark.modifiedAt &&
+ bookmark.modifiedAt.getTime() !==
+ bookmark.createdAt.getTime() && (
+ <Text className="items-center text-center">
+ Modified {bookmark.modifiedAt.toLocaleString()}
+ </Text>
+ )}
</View>
- </TouchableWithoutFeedback>
- </Animated.ScrollView>
- </View>
+ </View>
+ </KeyboardAwareScrollView>
+ </KeyboardGestureArea>
);
};
diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/manage_lists.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_lists.tsx
index 9f2149ae..7250d06b 100644
--- a/apps/mobile/app/dashboard/bookmarks/[slug]/manage_lists.tsx
+++ b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_lists.tsx
@@ -1,8 +1,9 @@
import React from "react";
-import { FlatList, Pressable, Text, View } from "react-native";
+import { FlatList, Pressable, View } from "react-native";
import Checkbox from "expo-checkbox";
import { useLocalSearchParams } from "expo-router";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
+import { Text } from "@/components/ui/Text";
import { useToast } from "@/components/ui/Toast";
import {
@@ -75,13 +76,13 @@ const ListPickerPage = () => {
gap: 5,
}}
renderItem={(l) => (
- <View className="mx-2 flex flex-row items-center rounded-xl border border-input bg-white px-4 py-2 dark:bg-accent">
+ <View className="mx-2 flex flex-row items-center rounded-xl border border-input bg-card px-4 py-2">
<Pressable
key={l.item[l.item.length - 1].id}
onPress={() => toggleList(l.item[l.item.length - 1].id)}
className="flex w-full flex-row justify-between"
>
- <Text className="text-lg text-accent-foreground">
+ <Text>
{l.item.map((item) => `${item.icon} ${item.name}`).join(" / ")}
</Text>
<Checkbox
diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx
index 38296626..ea6c2f4d 100644
--- a/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx
+++ b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx
@@ -1,16 +1,11 @@
import React, { useMemo } from "react";
-import {
- Pressable,
- SectionList,
- Text,
- TouchableOpacity,
- View,
-} from "react-native";
+import { Pressable, SectionList, TouchableOpacity, View } from "react-native";
import { Stack, useLocalSearchParams } from "expo-router";
-import { TailwindResolver } from "@/components/TailwindResolver";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
+import { Text } from "@/components/ui/Text";
import { useToast } from "@/components/ui/Toast";
+import { useColorScheme } from "@/lib/useColorScheme";
import { Check, Plus } from "lucide-react-native";
import {
@@ -22,6 +17,7 @@ import { api } from "@karakeep/shared-react/trpc";
const NEW_TAG_ID = "new-tag";
const ListPickerPage = () => {
+ const { colors } = useColorScheme();
const { slug: bookmarkId } = useLocalSearchParams();
const [search, setSearch] = React.useState("");
@@ -211,20 +207,14 @@ const ListPickerPage = () => {
})
}
>
- <View className="mx-2 flex flex-row items-center gap-2 rounded-xl border border-input bg-white px-4 py-2 dark:bg-accent">
+ <View className="mx-2 flex flex-row items-center gap-2 rounded-xl border border-input bg-card px-4 py-2">
{t.section.title == "Existing Tags" && (
- <TailwindResolver
- className="text-accent-foreground"
- comp={(s) => <Check color={s?.color} />}
- />
+ <Check color={colors.foreground} />
)}
{t.section.title == "All Tags" && t.item.id == NEW_TAG_ID && (
- <TailwindResolver
- className="text-accent-foreground"
- comp={(s) => <Plus color={s?.color} />}
- />
+ <Plus color={colors.foreground} />
)}
- <Text className="text-center text-lg text-accent-foreground">
+ <Text>
{t.item.id == NEW_TAG_ID
? `Create new tag '${t.item.name}'`
: t.item.name}
diff --git a/apps/mobile/app/dashboard/bookmarks/new.tsx b/apps/mobile/app/dashboard/bookmarks/new.tsx
index d24c1597..50f8f2a7 100644
--- a/apps/mobile/app/dashboard/bookmarks/new.tsx
+++ b/apps/mobile/app/dashboard/bookmarks/new.tsx
@@ -1,9 +1,10 @@
import React, { useState } from "react";
-import { Text, View } from "react-native";
+import { View } from "react-native";
import { router } from "expo-router";
import { Button } from "@/components/ui/Button";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import { Input } from "@/components/ui/Input";
+import { Text } from "@/components/ui/Text";
import { useToast } from "@/components/ui/Toast";
import { useCreateBookmark } from "@karakeep/shared-react/hooks/bookmarks";
@@ -61,13 +62,16 @@ const NoteEditorPage = () => {
)}
<Input
onChangeText={setText}
+ className="bg-card"
multiline
placeholder="What's on your mind?"
autoFocus
autoCapitalize={"none"}
textAlignVertical="top"
/>
- <Button onPress={onSubmit} label="Save" />
+ <Button onPress={onSubmit}>
+ <Text>Save</Text>
+ </Button>
</View>
</CustomSafeAreaView>
);
diff --git a/apps/mobile/app/dashboard/lists/new.tsx b/apps/mobile/app/dashboard/lists/new.tsx
index 2cd690f5..55315e70 100644
--- a/apps/mobile/app/dashboard/lists/new.tsx
+++ b/apps/mobile/app/dashboard/lists/new.tsx
@@ -1,9 +1,10 @@
import React, { useState } from "react";
-import { Text, View } from "react-native";
+import { View } from "react-native";
import { router } from "expo-router";
import { Button } from "@/components/ui/Button";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import { Input } from "@/components/ui/Input";
+import { Text } from "@/components/ui/Text";
import { useToast } from "@/components/ui/Toast";
import { useCreateBookmarkList } from "@karakeep/shared-react/hooks/lists";
@@ -40,14 +41,16 @@ const NewListPage = () => {
<View className="flex flex-row items-center gap-1">
<Text className="shrink p-2">🚀</Text>
<Input
- className="flex-1"
+ className="flex-1 bg-card"
onChangeText={setText}
placeholder="List Name"
autoFocus
autoCapitalize={"none"}
/>
</View>
- <Button disabled={isPending} onPress={onSubmit} label="Save" />
+ <Button disabled={isPending} onPress={onSubmit}>
+ <Text>Save</Text>
+ </Button>
</View>
</CustomSafeAreaView>
);
diff --git a/apps/mobile/app/dashboard/search.tsx b/apps/mobile/app/dashboard/search.tsx
index 5cc97575..66423870 100644
--- a/apps/mobile/app/dashboard/search.tsx
+++ b/apps/mobile/app/dashboard/search.tsx
@@ -1,18 +1,12 @@
import { useMemo, useRef, useState } from "react";
-import {
- FlatList,
- Keyboard,
- Pressable,
- Text,
- TextInput,
- View,
-} from "react-native";
-import { router } from "expo-router";
+import { FlatList, Keyboard, Pressable, TextInput, View } from "react-native";
+import { router, Stack } from "expo-router";
import BookmarkList from "@/components/bookmarks/BookmarkList";
import FullPageError from "@/components/FullPageError";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
-import { Input } from "@/components/ui/Input";
+import { SearchInput } from "@/components/ui/SearchInput";
+import { Text } from "@/components/ui/Text";
import { api } from "@/lib/trpc";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { keepPreviousData } from "@tanstack/react-query";
@@ -102,24 +96,26 @@ export default function Search() {
return (
<CustomSafeAreaView>
- <View className="flex flex-row items-center gap-3 p-3">
- <Input
- ref={inputRef}
- placeholder="Search"
- className="flex-1"
- value={search}
- onChangeText={setSearch}
- onFocus={handleOnFocus}
- onBlur={handleOnBlur}
- onSubmitEditing={() => handleSearchSubmit(search)}
- returnKeyType="search"
- autoFocus
- autoCapitalize="none"
- />
- <Pressable onPress={() => router.back()}>
- <Text className="text-foreground">Cancel</Text>
- </Pressable>
- </View>
+ <Stack.Screen
+ options={{
+ headerShown: true,
+ }}
+ />
+ <SearchInput
+ containerClassName="m-3"
+ ref={inputRef}
+ placeholder="Search"
+ className="flex-1"
+ value={search}
+ onChangeText={setSearch}
+ onFocus={handleOnFocus}
+ onBlur={handleOnBlur}
+ onSubmitEditing={() => handleSearchSubmit(search)}
+ returnKeyType="search"
+ autoFocus
+ autoCapitalize="none"
+ onCancel={router.back}
+ />
{isInputFocused ? (
<FlatList
diff --git a/apps/mobile/app/dashboard/settings/bookmark-default-view.tsx b/apps/mobile/app/dashboard/settings/bookmark-default-view.tsx
index c8c522cf..5f4463ae 100644
--- a/apps/mobile/app/dashboard/settings/bookmark-default-view.tsx
+++ b/apps/mobile/app/dashboard/settings/bookmark-default-view.tsx
@@ -1,7 +1,8 @@
-import { Pressable, Text, View } from "react-native";
+import { Pressable, View } from "react-native";
import { useRouter } from "expo-router";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import { Divider } from "@/components/ui/Divider";
+import { Text } from "@/components/ui/Text";
import { useToast } from "@/components/ui/Toast";
import useAppSettings from "@/lib/settings";
import { Check } from "lucide-react-native";
@@ -41,9 +42,7 @@ export default function BookmarkDefaultViewSettings() {
className="flex flex-row justify-between"
key={mode}
>
- <Text className="text-lg text-accent-foreground">
- {{ browser: "Browser", reader: "Reader" }[mode]}
- </Text>
+ <Text>{{ browser: "Browser", reader: "Reader" }[mode]}</Text>
{isChecked && <Check color="rgb(0, 122, 255)" />}
</Pressable>,
<Divider
@@ -59,7 +58,7 @@ export default function BookmarkDefaultViewSettings() {
return (
<CustomSafeAreaView>
<View className="flex h-full w-full items-center px-4 py-2">
- <View className="w-full rounded-lg bg-white px-4 py-2 dark:bg-accent">
+ <View className="w-full rounded-lg bg-card bg-card px-4 py-2">
{options}
</View>
</View>
diff --git a/apps/mobile/app/dashboard/settings/theme.tsx b/apps/mobile/app/dashboard/settings/theme.tsx
index f7feacdb..a4f0494a 100644
--- a/apps/mobile/app/dashboard/settings/theme.tsx
+++ b/apps/mobile/app/dashboard/settings/theme.tsx
@@ -1,6 +1,7 @@
-import { Pressable, Text, View } from "react-native";
+import { Pressable, View } from "react-native";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import { Divider } from "@/components/ui/Divider";
+import { Text } from "@/components/ui/Text";
import useAppSettings from "@/lib/settings";
import { Check } from "lucide-react-native";
@@ -16,7 +17,7 @@ export default function ThemePage() {
className="flex flex-row justify-between"
key={theme}
>
- <Text className="text-lg text-accent-foreground">
+ <Text>
{
{ light: "Light Mode", dark: "Dark Mode", system: "System" }[
theme
@@ -38,9 +39,7 @@ export default function ThemePage() {
return (
<CustomSafeAreaView>
<View className="flex h-full w-full items-center px-4 py-2">
- <View className="w-full rounded-lg bg-white px-4 py-2 dark:bg-accent">
- {options}
- </View>
+ <View className="w-full rounded-lg bg-card px-4 py-2">{options}</View>
</View>
</CustomSafeAreaView>
);
diff --git a/apps/mobile/app/error.tsx b/apps/mobile/app/error.tsx
index d0e4a7df..6e975306 100644
--- a/apps/mobile/app/error.tsx
+++ b/apps/mobile/app/error.tsx
@@ -1,9 +1,10 @@
-import { Text, View } from "react-native";
+import { View } from "react-native";
+import { Text } from "@/components/ui/Text";
export default function ErrorPage() {
return (
<View className="flex-1 items-center justify-center gap-4">
- <Text className="text-4xl">Error!</Text>
+ <Text variant="largeTitle">Error!</Text>
</View>
);
}
diff --git a/apps/mobile/app/sharing.tsx b/apps/mobile/app/sharing.tsx
index 506b5100..1e5df4b8 100644
--- a/apps/mobile/app/sharing.tsx
+++ b/apps/mobile/app/sharing.tsx
@@ -1,8 +1,9 @@
import { useEffect, useRef, useState } from "react";
-import { ActivityIndicator, Pressable, Text, View } from "react-native";
+import { ActivityIndicator, Pressable, View } from "react-native";
import { useRouter } from "expo-router";
import { useShareIntentContext } from "expo-share-intent";
import { Button } from "@/components/ui/Button";
+import { Text } from "@/components/ui/Text";
import useAppSettings from "@/lib/settings";
import { api } from "@/lib/trpc";
import { useUploadAsset } from "@/lib/upload";
@@ -73,7 +74,7 @@ function SaveBookmark({ setMode }: { setMode: (mode: Mode) => void }) {
return (
<View className="flex flex-row gap-3">
- <Text className="text-4xl text-foreground">Hoarding</Text>
+ <Text variant="largeTitle">Hoarding</Text>
<ActivityIndicator />
</View>
);
@@ -95,18 +96,19 @@ export default function Sharing() {
case "success": {
comp = (
<View className="items-center gap-4">
- <Text className="text-4xl text-foreground">
+ <Text variant="largeTitle">
{mode.type === "alreadyExists" ? "Already Hoarded!" : "Hoarded!"}
</Text>
<Button
- label="Manage"
onPress={() => {
router.replace(`/dashboard/bookmarks/${mode.bookmarkId}/info`);
if (autoCloseTimeoutId.current) {
clearTimeout(autoCloseTimeoutId.current);
}
}}
- />
+ >
+ <Text>Manage</Text>
+ </Button>
<Pressable onPress={() => router.replace("dashboard")}>
<Text className="text-muted-foreground">Dismiss</Text>
</Pressable>
@@ -115,7 +117,7 @@ export default function Sharing() {
break;
}
case "error": {
- comp = <Text className="text-4xl text-foreground">Error!</Text>;
+ comp = <Text variant="largeTitle">Error!</Text>;
break;
}
}
diff --git a/apps/mobile/app/signin.tsx b/apps/mobile/app/signin.tsx
index 0d160398..215b6a67 100644
--- a/apps/mobile/app/signin.tsx
+++ b/apps/mobile/app/signin.tsx
@@ -4,18 +4,17 @@ import {
KeyboardAvoidingView,
Platform,
Pressable,
- Text,
TouchableWithoutFeedback,
View,
} from "react-native";
import { Redirect, useRouter } from "expo-router";
import Logo from "@/components/Logo";
import { TailwindResolver } from "@/components/TailwindResolver";
-import { Button, buttonVariants } from "@/components/ui/Button";
+import { Button } from "@/components/ui/Button";
import { Input } from "@/components/ui/Input";
+import { Text } from "@/components/ui/Text";
import useAppSettings from "@/lib/settings";
import { api } from "@/lib/trpc";
-import { cn } from "@/lib/utils";
import { Bug } from "lucide-react-native";
enum LoginType {
@@ -134,6 +133,7 @@ export default function Signin() {
<Text className="font-bold">Server Address</Text>
<Input
className="w-full"
+ inputClasses="bg-card"
placeholder="Server Address"
value={formState.serverAddress}
autoCapitalize="none"
@@ -150,6 +150,7 @@ export default function Signin() {
<Text className="font-bold">Email</Text>
<Input
className="w-full"
+ inputClasses="bg-card"
placeholder="Email"
keyboardType="email-address"
autoCapitalize="none"
@@ -163,6 +164,7 @@ export default function Signin() {
<Text className="font-bold">Password</Text>
<Input
className="w-full"
+ inputClasses="bg-card"
placeholder="Password"
secureTextEntry
value={formState.password}
@@ -181,6 +183,7 @@ export default function Signin() {
<Text className="font-bold">API Key</Text>
<Input
className="w-full"
+ inputClasses="bg-card"
placeholder="API Key"
secureTextEntry
value={formState.apiKey}
@@ -193,18 +196,17 @@ export default function Signin() {
<View className="flex flex-row items-center justify-between gap-2">
<Button
- className="flex-1"
- label="Sign In"
+ size="lg"
+ androidRootClassName="flex-1"
onPress={onSignin}
disabled={
userNamePasswordRequestIsPending || apiKeyValueRequestIsPending
}
- />
- <Pressable
- className={cn(
- buttonVariants({ variant: "default" }),
- !settings.address && "bg-gray-500",
- )}
+ >
+ <Text>Sign In</Text>
+ </Button>
+ <Button
+ size="icon"
onPress={() => router.push("/test-connection")}
disabled={!settings.address}
>
@@ -212,9 +214,9 @@ export default function Signin() {
comp={(styles) => (
<Bug size={20} color={styles?.color?.toString()} />
)}
- className="text-background"
+ className="text-white"
/>
- </Pressable>
+ </Button>
</View>
<Pressable onPress={toggleLoginType}>
<Text className="mt-2 text-center text-gray-500">
diff --git a/apps/mobile/app/test-connection.tsx b/apps/mobile/app/test-connection.tsx
index 5639c6bd..a9ec6e5e 100644
--- a/apps/mobile/app/test-connection.tsx
+++ b/apps/mobile/app/test-connection.tsx
@@ -1,9 +1,10 @@
import React from "react";
-import { Platform, Text, View } from "react-native";
+import { Platform, View } from "react-native";
import * as Clipboard from "expo-clipboard";
import { Button } from "@/components/ui/Button";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import { Input } from "@/components/ui/Input";
+import { Text } from "@/components/ui/Text";
import useAppSettings from "@/lib/settings";
import { cn } from "@/lib/utils";
import { z } from "zod";
@@ -79,19 +80,22 @@ export default function TestConnection() {
<View className="m-4 flex flex-col gap-2 p-2">
<Button
className="w-full"
- label="Copy Diagnostics Result"
onPress={async () => {
await Clipboard.setStringAsync(text);
}}
- />
+ >
+ <Text>Copy Diagnostics Result</Text>
+ </Button>
<Button
className="w-full"
- label="Retry"
+ variant="secondary"
onPress={() => {
setText("");
setRandomId(Math.random());
}}
- />
+ >
+ <Text>Retry</Text>
+ </Button>
<View
className={cn(
"w-full rounded-md p-2",