diff options
| author | MohamedBassem <me@mbassem.com> | 2024-07-28 20:03:15 -0700 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-07-28 20:56:27 -0700 |
| commit | 93afb75619a02aa741b464634911b994620092be (patch) | |
| tree | 414eb0b184027c67b01112caa56c039f2d82aebf /apps/mobile | |
| parent | 92c92c161b3195bdfb571284e783f39f369a25a5 (diff) | |
| download | karakeep-93afb75619a02aa741b464634911b994620092be.tar.zst | |
feat(mobile): Add ability to manage lists
Diffstat (limited to 'apps/mobile')
| -rw-r--r-- | apps/mobile/app/_layout.tsx | 50 | ||||
| -rw-r--r-- | apps/mobile/app/sharing.tsx | 40 | ||||
| -rw-r--r-- | apps/mobile/components/bookmarks/BookmarkCard.tsx | 20 | ||||
| -rw-r--r-- | apps/mobile/components/bookmarks/ListPickerModal.tsx | 117 | ||||
| -rw-r--r-- | apps/mobile/package.json | 5 |
5 files changed, 201 insertions, 31 deletions
diff --git a/apps/mobile/app/_layout.tsx b/apps/mobile/app/_layout.tsx index a5aafb8c..f56aa810 100644 --- a/apps/mobile/app/_layout.tsx +++ b/apps/mobile/app/_layout.tsx @@ -3,6 +3,7 @@ import "expo-dev-client"; import { useEffect } from "react"; import { View } from "react-native"; +import { GestureHandlerRootView } from "react-native-gesture-handler"; import { useRouter } from "expo-router"; import { Stack } from "expo-router/stack"; import { ShareIntentProvider, useShareIntent } from "expo-share-intent"; @@ -10,6 +11,7 @@ import { StatusBar } from "expo-status-bar"; import { StyledStack } from "@/components/navigation/stack"; import { Providers } from "@/lib/providers"; import { cn } from "@/lib/utils"; +import { BottomSheetModalProvider } from "@gorhom/bottom-sheet"; import { useColorScheme } from "nativewind"; export default function RootLayout() { @@ -28,28 +30,32 @@ export default function RootLayout() { return ( <ShareIntentProvider> <Providers> - <View - className={cn( - "w-full flex-1 bg-gray-100 text-foreground dark:bg-background", - colorScheme == "dark" ? "dark" : "light", - )} - > - <StyledStack - contentClassName="bg-gray-100 dark:bg-background" - screenOptions={{ - headerShown: false, - }} - > - <Stack.Screen name="index" /> - <Stack.Screen - name="sharing" - options={{ - presentation: "modal", - }} - /> - </StyledStack> - <StatusBar style="auto" /> - </View> + <GestureHandlerRootView style={{ flex: 1 }}> + <BottomSheetModalProvider> + <View + className={cn( + "w-full flex-1 bg-gray-100 text-foreground dark:bg-background", + colorScheme == "dark" ? "dark" : "light", + )} + > + <StyledStack + contentClassName="bg-gray-100 dark:bg-background" + screenOptions={{ + headerShown: false, + }} + > + <Stack.Screen name="index" /> + <Stack.Screen + name="sharing" + options={{ + presentation: "modal", + }} + /> + </StyledStack> + <StatusBar style="auto" /> + </View> + </BottomSheetModalProvider> + </GestureHandlerRootView> </Providers> </ShareIntentProvider> ); diff --git a/apps/mobile/app/sharing.tsx b/apps/mobile/app/sharing.tsx index d1d39e5b..ee7bd609 100644 --- a/apps/mobile/app/sharing.tsx +++ b/apps/mobile/app/sharing.tsx @@ -1,10 +1,13 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { ActivityIndicator, Text, View } from "react-native"; import { useRouter } from "expo-router"; import { useShareIntentContext } from "expo-share-intent"; +import ListPickerModal from "@/components/bookmarks/ListPickerModal"; +import { Button } from "@/components/ui/Button"; import useAppSettings from "@/lib/settings"; import { api } from "@/lib/trpc"; import { useUploadAsset } from "@/lib/upload"; +import { BottomSheetModal } from "@gorhom/bottom-sheet"; import { z } from "zod"; import { BookmarkTypes, ZBookmark } from "@hoarder/shared/types/bookmarks"; @@ -80,18 +83,39 @@ export default function Sharing() { const router = useRouter(); const [mode, setMode] = useState<Mode>({ type: "idle" }); + let autoCloseTimeoutId: NodeJS.Timeout | null = null; + const addToListSheetRef = useRef<BottomSheetModal>(null); + let comp; switch (mode.type) { case "idle": { comp = <SaveBookmark setMode={setMode} />; break; } + case "alreadyExists": case "success": { - comp = <Text className="text-4xl text-foreground">Hoarded!</Text>; - break; - } - case "alreadyExists": { - comp = <Text className="text-4xl text-foreground">Already Hoarded!</Text>; + comp = ( + <View className="items-center gap-4"> + <ListPickerModal + ref={addToListSheetRef} + snapPoints={["90%"]} + bookmarkId={mode.bookmarkId} + onDismiss={() => router.replace("dashboard")} + /> + <Text className="text-4xl text-foreground"> + {mode.type === "alreadyExists" ? "Already Hoarded!" : "Hoarded!"} + </Text> + <Button + label="Add to List" + onPress={() => { + addToListSheetRef.current?.present(); + if (autoCloseTimeoutId) { + clearTimeout(autoCloseTimeoutId); + } + }} + /> + </View> + ); break; } case "error": { @@ -106,11 +130,11 @@ export default function Sharing() { return; } - const timeoutId = setTimeout(() => { + autoCloseTimeoutId = setTimeout(() => { router.replace("dashboard"); }, 2000); - return () => clearTimeout(timeoutId); + return () => clearTimeout(autoCloseTimeoutId!); }, [mode.type]); return ( diff --git a/apps/mobile/components/bookmarks/BookmarkCard.tsx b/apps/mobile/components/bookmarks/BookmarkCard.tsx index 8faa8618..3be1f9a0 100644 --- a/apps/mobile/components/bookmarks/BookmarkCard.tsx +++ b/apps/mobile/components/bookmarks/BookmarkCard.tsx @@ -1,3 +1,4 @@ +import { useRef } from "react"; import { ActivityIndicator, Image, @@ -13,6 +14,7 @@ import { Link } from "expo-router"; import * as WebBrowser from "expo-web-browser"; import useAppSettings from "@/lib/settings"; import { api } from "@/lib/trpc"; +import { BottomSheetModal } from "@gorhom/bottom-sheet"; import { MenuView } from "@react-native-menu/menu"; import { Ellipsis, Star } from "lucide-react-native"; @@ -32,6 +34,7 @@ import { TailwindResolver } from "../TailwindResolver"; import { Divider } from "../ui/Divider"; import { Skeleton } from "../ui/Skeleton"; import { useToast } from "../ui/Toast"; +import ListPickerModal from "./ListPickerModal"; function ActionBar({ bookmark }: { bookmark: ZBookmark }) { const { toast } = useToast(); @@ -70,6 +73,8 @@ function ActionBar({ bookmark }: { bookmark: ZBookmark }) { onError, }); + const manageListsSheetRef = useRef<BottomSheetModal>(null); + return ( <View className="flex flex-row gap-4"> {(isArchivePending || isDeletionPending) && <ActivityIndicator />} @@ -89,6 +94,12 @@ function ActionBar({ bookmark }: { bookmark: ZBookmark }) { )} </Pressable> + <ListPickerModal + ref={manageListsSheetRef} + snapPoints={["50%", "90%"]} + bookmarkId={bookmark.id} + /> + <MenuView onPressAction={({ nativeEvent }) => { Haptics.selectionAsync(); @@ -101,6 +112,8 @@ function ActionBar({ bookmark }: { bookmark: ZBookmark }) { bookmarkId: bookmark.id, archived: !bookmark.archived, }); + } else if (nativeEvent.event === "manage_list") { + manageListsSheetRef?.current?.present(); } }} actions={[ @@ -121,6 +134,13 @@ function ActionBar({ bookmark }: { bookmark: ZBookmark }) { ios: "trash", }), }, + { + id: "manage_list", + title: "Manage Lists", + image: Platform.select({ + ios: "list", + }), + }, ]} shouldOpenOnLongPress={false} > diff --git a/apps/mobile/components/bookmarks/ListPickerModal.tsx b/apps/mobile/components/bookmarks/ListPickerModal.tsx new file mode 100644 index 00000000..6079e53d --- /dev/null +++ b/apps/mobile/components/bookmarks/ListPickerModal.tsx @@ -0,0 +1,117 @@ +import React from "react"; +import { Pressable, Text, View } from "react-native"; +import Checkbox from "expo-checkbox"; +import { + BottomSheetFlatList, + BottomSheetModal, + BottomSheetModalProps, +} from "@gorhom/bottom-sheet"; + +import { + useAddBookmarkToList, + useBookmarkLists, + useRemoveBookmarkFromList, +} from "@hoarder/shared-react/hooks/lists"; +import { api } from "@hoarder/shared-react/trpc"; + +import PageTitle from "../ui/PageTitle"; +import { useToast } from "../ui/Toast"; + +const ListPickerModal = React.forwardRef< + BottomSheetModal, + Omit<BottomSheetModalProps, "children"> & { + bookmarkId: string; + } +>(({ bookmarkId, ...props }, ref) => { + const { toast } = useToast(); + const onError = () => { + toast({ + message: "Something went wrong", + variant: "destructive", + showProgress: false, + }); + }; + const { data: existingLists } = api.lists.getListsOfBookmark.useQuery( + { + bookmarkId, + }, + { + select: (data) => new Set(data.lists.map((l) => l.id)), + }, + ); + const { data } = useBookmarkLists(); + + const { mutate: addToList } = useAddBookmarkToList({ + onSuccess: () => { + toast({ + message: `The bookmark has been added to the list!`, + showProgress: false, + }); + }, + onError, + }); + + const { mutate: removeToList } = useRemoveBookmarkFromList({ + onSuccess: () => { + toast({ + message: `The bookmark has been removed from the list!`, + showProgress: false, + }); + }, + onError, + }); + + const toggleList = (listId: string) => { + if (!existingLists) { + return; + } + if (existingLists.has(listId)) { + removeToList({ bookmarkId, listId }); + } else { + addToList({ bookmarkId, listId }); + } + }; + + const { allPaths } = data ?? {}; + return ( + <View> + <BottomSheetModal ref={ref} {...props}> + <BottomSheetFlatList + ListHeaderComponent={<PageTitle title="Manage Lists" />} + className="h-full" + contentContainerStyle={{ + 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"> + <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"> + {l.item + .map((item) => `${item.icon} ${item.name}`) + .join(" / ")} + </Text> + <Checkbox + value={ + existingLists && + existingLists.has(l.item[l.item.length - 1].id) + } + onValueChange={() => { + toggleList(l.item[l.item.length - 1].id); + }} + /> + </Pressable> + </View> + )} + data={allPaths} + /> + </BottomSheetModal> + </View> + ); +}); +ListPickerModal.displayName = "ListPickerModal"; + +export default ListPickerModal; diff --git a/apps/mobile/package.json b/apps/mobile/package.json index ef19bfbf..da8ef432 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -13,15 +13,17 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@hoarder/trpc": "workspace:^0.1.0", + "@gorhom/bottom-sheet": "^4.6.3", "@hoarder/shared": "workspace:^0.1.0", "@hoarder/shared-react": "workspace:^0.1.0", + "@hoarder/trpc": "workspace:^0.1.0", "@react-native-menu/menu": "^0.9.1", "@tanstack/react-query": "^5.24.8", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "expo": "~50.0.11", "expo-build-properties": "^0.11.1", + "expo-checkbox": "^3.0.0", "expo-config-plugin-ios-share-extension": "^0.0.4", "expo-constants": "~15.4.5", "expo-dev-client": "^3.3.9", @@ -40,6 +42,7 @@ "nativewind": "^4.0.1", "react": "^18.2.0", "react-native": "0.73.4", + "react-native-gesture-handler": "~2.14.0", "react-native-markdown-display": "^7.0.2", "react-native-reanimated": "^3.8.0", "react-native-safe-area-context": "4.8.2", |
