From 8db89bb09964b9aceb709e6bab5ff632f1cc3c1c Mon Sep 17 00:00:00 2001
From: xuatz <xzlow10@gmail.com>
Date: Sun, 27 Jul 2025 06:18:13 +0900
Subject: feat(mobile): add context aware sharing option in mobile app (#1785)

* v1 inside menu

* v2 outside menu with share icon
---
 apps/mobile/components/bookmarks/BookmarkCard.tsx | 81 ++++++++++++++++++++++-
 1 file changed, 80 insertions(+), 1 deletion(-)

(limited to 'apps/mobile/components/bookmarks/BookmarkCard.tsx')

diff --git a/apps/mobile/components/bookmarks/BookmarkCard.tsx b/apps/mobile/components/bookmarks/BookmarkCard.tsx
index 52d39c5c..7329aa46 100644
--- a/apps/mobile/components/bookmarks/BookmarkCard.tsx
+++ b/apps/mobile/components/bookmarks/BookmarkCard.tsx
@@ -6,15 +6,19 @@ import {
   Platform,
   Pressable,
   ScrollView,
+  Share,
   Text,
   View,
 } from "react-native";
+import * as Clipboard from "expo-clipboard";
+import * as FileSystem from "expo-file-system";
 import * as Haptics from "expo-haptics";
 import { router, useRouter } from "expo-router";
+import * as Sharing from "expo-sharing";
 import useAppSettings from "@/lib/settings";
 import { api } from "@/lib/trpc";
 import { MenuView } from "@react-native-menu/menu";
-import { Ellipsis, Star } from "lucide-react-native";
+import { Ellipsis, Share2, Star } from "lucide-react-native";
 
 import type { ZBookmark } from "@karakeep/shared/types/bookmarks";
 import {
@@ -37,6 +41,7 @@ import TagPill from "./TagPill";
 
 function ActionBar({ bookmark }: { bookmark: ZBookmark }) {
   const { toast } = useToast();
+  const { settings } = useAppSettings();
 
   const onError = () => {
     toast({
@@ -86,6 +91,71 @@ function ActionBar({ bookmark }: { bookmark: ZBookmark }) {
       ],
     );
 
+  const handleShare = async () => {
+    try {
+      switch (bookmark.content.type) {
+        case BookmarkTypes.LINK:
+          await Share.share({
+            url: bookmark.content.url,
+            message: bookmark.content.url,
+          });
+          break;
+
+        case BookmarkTypes.TEXT:
+          await Clipboard.setStringAsync(bookmark.content.text);
+          toast({
+            message: "Text copied to clipboard",
+            showProgress: false,
+          });
+          break;
+
+        case BookmarkTypes.ASSET:
+          if (bookmark.content.assetType === "image") {
+            if (await Sharing.isAvailableAsync()) {
+              const assetUrl = `${settings.address}/api/assets/${bookmark.content.assetId}`;
+              const fileUri = `${FileSystem.documentDirectory}temp_image.jpg`;
+
+              const downloadResult = await FileSystem.downloadAsync(
+                assetUrl,
+                fileUri,
+                {
+                  headers: {
+                    Authorization: `Bearer ${settings.apiKey}`,
+                  },
+                },
+              );
+
+              if (downloadResult.status === 200) {
+                await Sharing.shareAsync(downloadResult.uri);
+                // Clean up the temporary file
+                await FileSystem.deleteAsync(downloadResult.uri, {
+                  idempotent: true,
+                });
+              } else {
+                throw new Error("Failed to download image");
+              }
+            }
+          } else {
+            // For PDFs, share the URL
+            const assetUrl = `${settings.address}/api/assets/${bookmark.content.assetId}`;
+            await Share.share({
+              url: assetUrl,
+              message:
+                bookmark.title || bookmark.content.fileName || "PDF Document",
+            });
+          }
+          break;
+      }
+    } catch (error) {
+      console.error("Share error:", error);
+      toast({
+        message: "Failed to share",
+        variant: "destructive",
+        showProgress: false,
+      });
+    }
+  };
+
   return (
     <View className="flex flex-row gap-4">
       {(isArchivePending || isDeletionPending) && <ActivityIndicator />}
@@ -105,6 +175,15 @@ function ActionBar({ bookmark }: { bookmark: ZBookmark }) {
         )}
       </Pressable>
 
+      <Pressable
+        onPress={() => {
+          Haptics.selectionAsync();
+          handleShare();
+        }}
+      >
+        <Share2 color="gray" />
+      </Pressable>
+
       <MenuView
         onPressAction={({ nativeEvent }) => {
           Haptics.selectionAsync();
-- 
cgit v1.2.3-70-g09d2

