diff options
Diffstat (limited to 'apps/mobile/app')
| -rw-r--r-- | apps/mobile/app/dashboard/_layout.tsx | 9 | ||||
| -rw-r--r-- | apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx | 15 | ||||
| -rw-r--r-- | apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx | 20 | ||||
| -rw-r--r-- | apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx | 139 | ||||
| -rw-r--r-- | apps/mobile/app/sharing.tsx | 35 |
5 files changed, 202 insertions, 16 deletions
diff --git a/apps/mobile/app/dashboard/_layout.tsx b/apps/mobile/app/dashboard/_layout.tsx index bc743c7a..5717f711 100644 --- a/apps/mobile/app/dashboard/_layout.tsx +++ b/apps/mobile/app/dashboard/_layout.tsx @@ -67,6 +67,15 @@ export default function Dashboard() { }} /> <Stack.Screen + name="bookmarks/[slug]/manage_tags" + options={{ + headerTitle: "Manage Tags", + headerBackTitle: "Back", + headerTransparent: true, + presentation: "modal", + }} + /> + <Stack.Screen name="bookmarks/[slug]/manage_lists" options={{ headerTitle: "Manage Lists", diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx index 87330a88..98158ab1 100644 --- a/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx +++ b/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx @@ -21,7 +21,7 @@ import { Input } from "@/components/ui/Input"; import { useToast } from "@/components/ui/Toast"; import { useAssetUrl } from "@/lib/hooks"; import { api } from "@/lib/trpc"; -import { ClipboardList, Globe, Info, Trash2 } from "lucide-react-native"; +import { ClipboardList, Globe, Info, Tag, Trash2 } from "lucide-react-native"; import { useDeleteBookmark, @@ -79,6 +79,19 @@ function BottomActions({ bookmark }: { bookmark: ZBookmark }) { disabled: false, }, { + id: "tags", + icon: ( + <TailwindResolver + className="text-foreground" + comp={(styles) => <Tag color={styles?.color?.toString()} />} + /> + ), + shouldRender: true, + onClick: () => + router.push(`/dashboard/bookmarks/${bookmark.id}/manage_tags`), + disabled: false, + }, + { id: "open", icon: ( <TailwindResolver diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx index e0d87a09..e1b1bdbc 100644 --- a/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx +++ b/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx @@ -31,10 +31,22 @@ function TagList({ bookmark }: { bookmark: ZBookmark }) { <Skeleton className="h-4 w-full" /> </> ) : bookmark.tags.length > 0 ? ( - <View className="flex flex-row flex-wrap gap-2 rounded-lg bg-background p-4"> - {bookmark.tags.map((t) => ( - <TagPill key={t.id} tag={t} /> - ))} + <View className="flex flex-col gap-2"> + <View className="flex flex-row flex-wrap gap-2 rounded-lg bg-background p-4"> + {bookmark.tags.map((t) => ( + <TagPill key={t.id} tag={t} /> + ))} + </View> + + <Pressable + onPress={() => + router.push(`/dashboard/bookmarks/${bookmark.id}/manage_tags`) + } + 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 Tags</Text> + <ChevronRight color="rgb(0, 122, 255)" /> + </Pressable> </View> ) : ( <Text className="text-foreground">No tags</Text> diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx new file mode 100644 index 00000000..1712fdfe --- /dev/null +++ b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx @@ -0,0 +1,139 @@ +import React, { useMemo } from "react"; +import { Pressable, SectionList, Text, View } from "react-native"; +import { useLocalSearchParams } from "expo-router"; +import { TailwindResolver } from "@/components/TailwindResolver"; +import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView"; +import FullPageSpinner from "@/components/ui/FullPageSpinner"; +import { Input } from "@/components/ui/Input"; +import { useToast } from "@/components/ui/Toast"; +import { Check } from "lucide-react-native"; + +import { useUpdateBookmarkTags } from "@hoarder/shared-react/hooks/bookmarks"; +import { api } from "@hoarder/shared-react/trpc"; + +const ListPickerPage = () => { + const { slug: bookmarkId } = useLocalSearchParams(); + + const [search, setSearch] = React.useState(""); + + if (typeof bookmarkId !== "string") { + throw new Error("Unexpected param type"); + } + const { toast } = useToast(); + const onError = () => { + toast({ + message: "Something went wrong", + variant: "destructive", + showProgress: false, + }); + }; + const { data: allTags, isPending: isAllTagsPending } = + api.tags.list.useQuery(); + const { data: existingTags } = api.bookmarks.getBookmark.useQuery( + { + bookmarkId, + }, + { + select: (data) => data.tags.map((t) => ({ id: t.id, name: t.name })), + }, + ); + + const [optimisticTags, setOptimisticTags] = React.useState( + existingTags ?? [], + ); + + const { mutate: updateTags } = useUpdateBookmarkTags({ + onMutate: (req) => { + req.attach.forEach((t) => + setOptimisticTags((prev) => [ + ...prev, + { id: t.tagId!, name: t.tagName! }, + ]), + ); + req.detach.forEach((t) => + setOptimisticTags((prev) => prev.filter((p) => p.id != t.tagId!)), + ); + }, + onError, + }); + + const optimisticExistingTagIds = useMemo(() => { + return new Set(optimisticTags?.map((t) => t.id) ?? []); + }, [optimisticTags]); + + const filteredTags = useMemo(() => { + return allTags?.tags.filter( + (t) => + t.name.toLowerCase().startsWith(search.toLowerCase()) && + !optimisticExistingTagIds.has(t.id), + ); + }, [search, allTags, optimisticExistingTagIds]); + + if (isAllTagsPending) { + return <FullPageSpinner />; + } + + return ( + <CustomSafeAreaView> + <View className="px-3"> + <SectionList + className="h-full" + ListHeaderComponent={ + <Input + placeholder="Search Tags ..." + autoCapitalize="none" + onChangeText={setSearch} + /> + } + keyExtractor={(t) => t.id} + contentContainerStyle={{ + gap: 5, + }} + SectionSeparatorComponent={() => <View className="h-1" />} + sections={[ + { + title: "Existing Tags", + data: optimisticTags ?? [], + }, + { + title: "All Tags", + data: filteredTags ?? [], + }, + ]} + renderItem={(t) => ( + <Pressable + key={t.item.id} + onPress={() => + updateTags({ + bookmarkId, + detach: + t.section.title == "Existing Tags" + ? [{ tagId: t.item.id, tagName: t.item.name }] + : [], + attach: + t.section.title == "All Tags" + ? [{ tagId: t.item.id, tagName: t.item.name }] + : [], + }) + } + > + <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"> + {t.section.title == "Existing Tags" && ( + <TailwindResolver + className="text-accent-foreground" + comp={(s) => <Check color={s?.color} />} + /> + )} + <Text className="text-center text-lg text-accent-foreground"> + {t.item.name} + </Text> + </View> + </Pressable> + )} + /> + </View> + </CustomSafeAreaView> + ); +}; + +export default ListPickerPage; diff --git a/apps/mobile/app/sharing.tsx b/apps/mobile/app/sharing.tsx index e41535b7..3551aea9 100644 --- a/apps/mobile/app/sharing.tsx +++ b/apps/mobile/app/sharing.tsx @@ -96,17 +96,30 @@ export default function Sharing() { <Text className="text-4xl text-foreground"> {mode.type === "alreadyExists" ? "Already Hoarded!" : "Hoarded!"} </Text> - <Button - label="Add to List" - onPress={() => { - router.push( - `/dashboard/bookmarks/${mode.bookmarkId}/manage_lists`, - ); - if (autoCloseTimeoutId) { - clearTimeout(autoCloseTimeoutId); - } - }} - /> + <View className="flex flex-row gap-2"> + <Button + label="Add to List" + onPress={() => { + router.push( + `/dashboard/bookmarks/${mode.bookmarkId}/manage_lists`, + ); + if (autoCloseTimeoutId) { + clearTimeout(autoCloseTimeoutId); + } + }} + /> + <Button + label="Manage Tags" + onPress={() => { + router.push( + `/dashboard/bookmarks/${mode.bookmarkId}/manage_tags`, + ); + if (autoCloseTimeoutId) { + clearTimeout(autoCloseTimeoutId); + } + }} + /> + </View> <Pressable onPress={() => router.replace("dashboard")}> <Text className="text-muted-foreground">Dismiss</Text> </Pressable> |
