1import { requireNativeModule } from "expo";
2import { useEffect, useEffectEvent, useId } from "react";
3import { Alert } from "react-native";
4
5import type { ResolvedFilePreviewSource } from "./FilePreviewModal.types";
6
7const NativeControls = requireNativeModule<{
8 presentFile(
9 uri: string,
10 name: string,
11 sourceIdentifier: string,
12 identifier: string,
13 ): Promise<void>;
14 presentFiles(
15 uris: readonly string[],
16 names: readonly string[],
17 selectedIndex: number,
18 identifier: string,
19 ): Promise<void>;
20 dismissFile(identifier: string): Promise<void>;
21}>("T3NativeControls");
22
23function NativeFilePreview(props: {
24 readonly source: ResolvedFilePreviewSource;
25 readonly onRequestClose: () => void;
26 readonly onOpenError?: (error: unknown) => void;
27}) {
28 const { uri, name, sourceIdentifier, gallery } = props.source;
29 const identifier = useId();
30 const onRequestClose = useEffectEvent(props.onRequestClose);
31 const onOpenError = useEffectEvent((error: unknown) => {
32 if (props.onOpenError) props.onOpenError(error);
33 else Alert.alert("Could not open preview", "The file could not be loaded. Please try again.");
34 });
35
36 useEffect(() => {
37 let canceled = false;
38 const presentation =
39 gallery && gallery.length > 1
40 ? NativeControls.presentFiles(
41 gallery.map((item) => item.uri),
42 gallery.map((item) => item.name ?? "Preview"),
43 Math.max(
44 0,
45 gallery.findIndex((item) => item.uri === uri),
46 ),
47 identifier,
48 )
49 : NativeControls.presentFile(uri, name ?? "Preview", sourceIdentifier ?? "", identifier);
50 void presentation
51 .catch((error: unknown) => {
52 if (!canceled) onOpenError(error);
53 })
54 .finally(() => {
55 if (!canceled) onRequestClose();
56 });
57 return () => {
58 canceled = true;
59 void NativeControls.dismissFile(identifier).catch(() => undefined);
60 };
61 }, [uri, name, sourceIdentifier, identifier, gallery]);
62
63 return null;
64}
65
66export function FilePreview(props: {
67 readonly source: ResolvedFilePreviewSource;
68 readonly onRequestClose: () => void;
69 readonly onOpenError?: (error: unknown) => void;
70}) {
71 return <NativeFilePreview {...props} />;
72}