diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-08-26 15:47:05 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-26 13:47:05 +0100 |
| commit | ed86f7ef012fb558fe8a8974e1e162ce75cbfd15 (patch) | |
| tree | a3470b0e1a01aede90b75bc61eeba2545e51fe83 /apps/mobile/app/dashboard/bookmarks | |
| parent | ec56ea33b5e37d02e87e480da305038a5ce7de49 (diff) | |
| download | karakeep-ed86f7ef012fb558fe8a8974e1e162ce75cbfd15.tar.zst | |
feat(mobile): Retheme the mobile app (#1872)
* Add nativewindui
* migrate to nativewindui text
* Replace buttons with nativewindui buttons
* Use nativewindui search input
* fix the divider color
* More changes
* fix manage tag icon
* fix styling of bookmark card
* fix ios compilation
* fix search clear
* fix tag pill border color
* Store theme setting in app settings
* fix setting color appearance
* fix coloring of search input
* fix following system theme
* add a save button to info
* fix the grey colors on android
* fix icon active tint color
* drop the use of TextField
Diffstat (limited to 'apps/mobile/app/dashboard/bookmarks')
5 files changed, 198 insertions, 141 deletions
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> ); |
