import { useNavigation } from "@react-navigation/native"; import type { MediaActionId } from "@t3tools/client-runtime/media-actions"; import type { MediaReference } from "@t3tools/client-runtime/media-reference"; import type { AssetResource, EnvironmentId, ThreadId } from "@t3tools/contracts"; import { normalizeNativeMarkdownUrl } from "@t3tools/mobile-markdown-text/links"; import { useEffect, useRef, useState } from "react"; import { Alert } from "react-native"; import { useRefreshAssetUrl } from "../state/assets"; import { downloadAndShareAttachment, shareLocalAttachment } from "./attachmentDownload"; import type { DraftComposerFileAttachment } from "./composerImages"; import { copyTextWithHaptic } from "./copyTextWithHaptic"; import { loadLocalAttachmentPreview } from "./localAttachmentPreview"; /** Authored source metadata is kept separate from temporary preview/download URLs. */ export type MediaActionsSource = { readonly reference?: MediaReference; readonly name: string; readonly mimeType: string; /** Anchors the iOS share sheet to the view that opened the menu. */ readonly sourceIdentifier?: string; } & ( | { readonly uri: string } | { readonly attachment: DraftComposerFileAttachment } | { readonly environmentId: EnvironmentId; readonly threadId?: ThreadId; readonly resource: AssetResource; } ); export function useMediaActions(source: MediaActionsSource | undefined, onOpenFile?: () => void) { const navigation = useNavigation(); const refresh = useRefreshAssetUrl( source && "environmentId" in source ? source.environmentId : null, source && "resource" in source ? source.resource : null, ); const controller = useRef(null); const [sharing, setSharing] = useState(false); useEffect(() => () => controller.current?.abort(), []); const share = () => { if (!source || controller.current) return; const request = new AbortController(); controller.current = request; setSharing(true); void (async () => { if ("attachment" in source) { const preview = await loadLocalAttachmentPreview(source.attachment, request.signal); if (!preview) return; try { await preview.share(request.signal, source.sourceIdentifier); } finally { preview.dispose(); } return; } const uri = "uri" in source ? normalizeNativeMarkdownUrl(source.uri) : await refresh(); if (request.signal.aborted) return; if (uri === null) throw new Error("The file could not be loaded. Reconnect and try again."); const input = { attachment: { name: source.name, mimeType: source.mimeType }, signal: request.signal, sourceIdentifier: source.sourceIdentifier, }; if (/^(file|content):/i.test(uri)) await shareLocalAttachment({ ...input, uri }); else await downloadAndShareAttachment({ ...input, url: uri }); })() .catch((error: unknown) => { if (!request.signal.aborted) { Alert.alert( "Could not share file", error instanceof Error ? error.message : "Try again.", ); } }) .finally(() => { if (controller.current === request) { controller.current = null; if (!request.signal.aborted) setSharing(false); } }); }; const reference = source?.reference; const relativePath = reference?.kind === "file" ? reference.relativePath : undefined; const threadId = source && "threadId" in source ? source.threadId : undefined; const actions: { id: MediaActionId; title: string; run: () => void; disabled?: boolean }[] = source ? [ ...(reference?.kind === "file" ? [ { id: "copy-full-path" as const, title: "Copy full path", run: () => copyTextWithHaptic(reference.path), }, ] : []), ...(relativePath ? [ { id: "copy-relative-path" as const, title: "Copy relative path", run: () => copyTextWithHaptic(relativePath), }, ] : []), ...(reference?.kind === "url" ? [ { id: "copy-url" as const, title: "Copy URL", run: () => copyTextWithHaptic(reference.url), }, ] : []), ...(relativePath && "environmentId" in source && threadId !== undefined ? [ { id: "open-file" as const, title: "Open in file viewer", run: () => { onOpenFile?.(); navigation.navigate("ThreadFile", { environmentId: String(source.environmentId), threadId: String(threadId), path: relativePath.split("/"), }); }, }, ] : []), { id: "save" as const, title: sharing ? "Opening share sheet…" : "Save or share", run: share, disabled: sharing, }, ] : []; return { title: reference?.kind === "file" ? reference.path : reference?.url, actions, sharing, share, }; }