diff options
| -rw-r--r-- | apps/mobile/components/bookmarks/BookmarkCard.tsx | 145 | ||||
| -rw-r--r-- | apps/mobile/package.json | 4 | ||||
| -rw-r--r-- | pnpm-lock.yaml | 24 |
3 files changed, 116 insertions, 57 deletions
diff --git a/apps/mobile/components/bookmarks/BookmarkCard.tsx b/apps/mobile/components/bookmarks/BookmarkCard.tsx index 2189b385..07d7f4fe 100644 --- a/apps/mobile/components/bookmarks/BookmarkCard.tsx +++ b/apps/mobile/components/bookmarks/BookmarkCard.tsx @@ -1,12 +1,21 @@ -import { Image, Pressable, ScrollView, Text, View } from "react-native"; +import { + ActivityIndicator, + Image, + Platform, + Pressable, + ScrollView, + Text, + View, +} from "react-native"; import Markdown from "react-native-markdown-display"; +import * as Haptics from "expo-haptics"; import * as WebBrowser from "expo-web-browser"; import { api } from "@/lib/trpc"; -import { Archive, ArchiveRestore, Star, Trash } from "lucide-react-native"; +import { MenuView } from "@react-native-menu/menu"; +import { Ellipsis, Star } from "lucide-react-native"; import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; -import { ActionButton } from "../ui/ActionButton"; import { Divider } from "../ui/Divider"; import { Skeleton } from "../ui/Skeleton"; import { useToast } from "../ui/Toast"; @@ -36,46 +45,59 @@ function ActionBar({ bookmark }: { bookmark: ZBookmark }) { const { toast } = useToast(); const apiUtils = api.useUtils(); + const onError = () => { + toast({ + message: "Something went wrong", + variant: "destructive", + showProgress: false, + }); + }; + const { mutate: deleteBookmark, isPending: isDeletionPending } = api.bookmarks.deleteBookmark.useMutation({ onSuccess: () => { + toast({ + message: 'The bookmark has been deleted!', + showProgress: false, + }); + apiUtils.bookmarks.getBookmarks.invalidate(); + }, + onError, + }); + + const { mutate: favouriteBookmark, variables } = + api.bookmarks.updateBookmark.useMutation({ + onSuccess: () => { apiUtils.bookmarks.getBookmarks.invalidate(); + apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: bookmark.id }); }, - onError: () => { + onError, + }); + + const { mutate: archiveBookmark, isPending: isArchivePending } = + api.bookmarks.updateBookmark.useMutation({ + onSuccess: (resp) => { toast({ - message: "Something went wrong", - variant: "destructive", + message: `The bookmark has been ${resp.archived ? "archived" : "un-archived"}!`, showProgress: false, }); + apiUtils.bookmarks.getBookmarks.invalidate(); + apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: bookmark.id }); }, + onError, }); - const { - mutate: updateBookmark, - variables, - isPending: isUpdatePending, - } = api.bookmarks.updateBookmark.useMutation({ - onSuccess: () => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: bookmark.id }); - }, - onError: () => { - toast({ - message: "Something went wrong", - variant: "destructive", - showProgress: false, - }); - }, - }); return ( <View className="flex flex-row gap-4"> + {(isArchivePending || isDeletionPending) && <ActivityIndicator />} <Pressable - onPress={() => - updateBookmark({ + onPress={() => { + Haptics.selectionAsync(); + favouriteBookmark({ bookmarkId: bookmark.id, favourited: !bookmark.favourited, - }) - } + }); + }} > {(variables ? variables.favourited : bookmark.favourited) ? ( <Star fill="#ebb434" color="#ebb434" /> @@ -83,31 +105,46 @@ function ActionBar({ bookmark }: { bookmark: ZBookmark }) { <Star color="gray" /> )} </Pressable> - <ActionButton - loading={isUpdatePending} - onPress={() => - updateBookmark({ - bookmarkId: bookmark.id, - archived: !bookmark.archived, - }) - } - > - {bookmark.archived ? ( - <ArchiveRestore color="gray" /> - ) : ( - <Archive color="gray" /> - )} - </ActionButton> - <ActionButton - loading={isDeletionPending} - onPress={() => - deleteBookmark({ - bookmarkId: bookmark.id, - }) - } + + <MenuView + onPressAction={({ nativeEvent }) => { + Haptics.selectionAsync(); + if (nativeEvent.event === "delete") { + deleteBookmark({ + bookmarkId: bookmark.id, + }); + } else if (nativeEvent.event === "archive") { + archiveBookmark({ + bookmarkId: bookmark.id, + archived: !bookmark.archived, + }); + } + }} + actions={[ + { + id: "archive", + title: bookmark.archived ? "Un-archive" : "Archive", + image: Platform.select({ + ios: "folder", + android: "ic_menu_folder", + }), + }, + { + id: "delete", + title: "Delete", + attributes: { + destructive: true, + }, + image: Platform.select({ + ios: "trash", + android: "ic_menu_delete", + }), + }, + ]} + shouldOpenOnLongPress={false} > - <Trash color="gray" /> - </ActionButton> + <Ellipsis onPress={() => Haptics.selectionAsync()} color="gray" /> + </MenuView> </View> ); } @@ -236,9 +273,5 @@ export default function BookmarkCard({ break; } - return ( - <View className="border-b border-gray-300 bg-white"> - {comp} - </View> - ); + return <View className="border-b border-gray-300 bg-white">{comp}</View>; } diff --git a/apps/mobile/package.json b/apps/mobile/package.json index ead5e796..71dff6cc 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@hoarder/trpc": "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", @@ -20,6 +21,7 @@ "expo-config-plugin-ios-share-extension": "^0.0.4", "expo-constants": "~15.4.5", "expo-dev-client": "^3.3.9", + "expo-haptics": "^12.8.1", "expo-image": "^1.10.6", "expo-linking": "~6.2.2", "expo-router": "~3.4.8", @@ -42,11 +44,11 @@ "zustand": "^4.5.1" }, "devDependencies": { + "@babel/core": "^7.20.0", "@hoarder/eslint-config": "workspace:^0.2.0", "@hoarder/prettier-config": "workspace:^0.1.0", "@hoarder/tailwind-config": "workspace:^0.1.0", "@hoarder/tsconfig": "workspace:^0.1.0", - "@babel/core": "^7.20.0", "@types/react": "^18.2.55", "ajv": "latest", "eslint": "^8.57.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b300e96..5babfe60 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,6 +140,9 @@ importers: '@hoarder/trpc': specifier: 0.1.0 version: link:../../packages/trpc + '@react-native-menu/menu': + specifier: ^0.9.1 + version: 0.9.1(react-native@0.73.4)(react@18.2.0) '@tanstack/react-query': specifier: ^5.24.8 version: 5.24.8(react@18.2.0) @@ -161,6 +164,9 @@ importers: expo-dev-client: specifier: ^3.3.9 version: 3.3.9(expo@50.0.11) + expo-haptics: + specifier: ^12.8.1 + version: 12.8.1(expo@50.0.11) expo-image: specifier: ^1.10.6 version: 1.10.6(expo@50.0.11) @@ -4801,6 +4807,16 @@ packages: - utf-8-validate dev: false + /@react-native-menu/menu@0.9.1(react-native@0.73.4)(react@18.2.0): + resolution: {integrity: sha512-PdZoN1P72ESQ7UTzm5YI4W+87KU5mv7vVJ3GsQVRtnrzzuVskfp5E9ds1biGClcTieXAdIZ7hvPD1HHHZPobdg==} + peerDependencies: + react: '*' + react-native: '*' + dependencies: + react: 18.2.0 + react-native: 0.73.4(@babel/core@7.23.9)(@babel/preset-env@7.24.0)(react@18.2.0) + dev: false + /@react-native/assets-registry@0.73.1: resolution: {integrity: sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==} engines: {node: '>=18'} @@ -9511,6 +9527,14 @@ packages: fontfaceobserver: 2.3.0 dev: false + /expo-haptics@12.8.1(expo@50.0.11): + resolution: {integrity: sha512-ntLsHkfle8K8w9MW8pZEw92ZN3sguaGUSSIxv30fPKNeQFu7Cq/h47Qv3tONv2MO3wU48N9FbKnant6XlfptpA==} + peerDependencies: + expo: '*' + dependencies: + expo: 50.0.11(@babel/core@7.23.9)(@react-native/babel-preset@0.73.21) + dev: false + /expo-image@1.10.6(expo@50.0.11): resolution: {integrity: sha512-vcnAIym1eU8vQgV1re1E7rVQZStJimBa4aPDhjFfzMzbddAF7heJuagyewiUkTzbZUwYzPaZAie6VJPyWx9Ueg==} peerDependencies: |
