Skip to content

Commit 220a8eb

Browse files
authored
Merge pull request #605 from gre/feat/example-expo
Add Expo example app
2 parents ddd86c4 + 23d0550 commit 220a8eb

14 files changed

Lines changed: 7943 additions & 0 deletions

example-expo/.gitignore

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2+
3+
# dependencies
4+
node_modules/
5+
6+
# Expo
7+
.expo/
8+
dist/
9+
web-build/
10+
expo-env.d.ts
11+
12+
# Native
13+
.kotlin/
14+
*.orig.*
15+
*.jks
16+
*.p8
17+
*.p12
18+
*.key
19+
*.mobileprovision
20+
21+
# Metro
22+
.metro-health-check*
23+
24+
# debug
25+
npm-debug.*
26+
yarn-debug.*
27+
yarn-error.*
28+
29+
# macOS
30+
.DS_Store
31+
*.pem
32+
33+
# local env files
34+
.env*.local
35+
36+
# typescript
37+
*.tsbuildinfo
38+
39+
# generated native folders
40+
/ios
41+
/android

example-expo/App.tsx

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
import React, {useRef, useState} from "react";
2+
import {
3+
StyleSheet,
4+
Text,
5+
View,
6+
TouchableOpacity,
7+
Image,
8+
ScrollView,
9+
Alert,
10+
Platform,
11+
} from "react-native";
12+
import ViewShot, {captureRef} from "react-native-view-shot";
13+
14+
export default function App() {
15+
const viewRef = useRef<View>(null);
16+
const viewShotRef = useRef<ViewShot>(null);
17+
const [result, setResult] = useState<string | null>(null);
18+
const [error, setError] = useState<string | null>(null);
19+
const [mode, setMode] = useState<"view-ref" | "viewshot">("view-ref");
20+
21+
const capture = async (
22+
format: "png" | "jpg",
23+
resultType: "data-uri" | "base64" | "tmpfile",
24+
) => {
25+
setResult(null);
26+
setError(null);
27+
try {
28+
let uri: string;
29+
if (mode === "viewshot" && viewShotRef.current) {
30+
uri = await viewShotRef.current.capture();
31+
} else {
32+
uri = await captureRef(viewRef.current!, {
33+
format,
34+
quality: 0.9,
35+
result: resultType,
36+
});
37+
}
38+
console.log(
39+
`Captured (${mode}, ${format}, ${resultType}):`,
40+
uri.substring(0, 80) + "...",
41+
);
42+
43+
if (resultType === "base64") {
44+
const mime = format === "jpg" ? "jpeg" : format;
45+
setResult(`data:image/${mime};base64,${uri}`);
46+
} else {
47+
setResult(uri);
48+
}
49+
} catch (err: any) {
50+
const msg = err?.message || String(err);
51+
console.error("Capture failed:", msg);
52+
setError(msg);
53+
Alert.alert("Capture Error", msg);
54+
}
55+
};
56+
57+
const content = (
58+
<View style={styles.captureContent}>
59+
<Text style={styles.title}>ViewShot Test</Text>
60+
<Text style={styles.subtitle}>
61+
Platform: {Platform.OS} | Mode: {mode}
62+
</Text>
63+
<View style={styles.card}>
64+
<Text style={styles.cardTitle}>Card 1</Text>
65+
<Text style={styles.cardText}>
66+
Testing capture with Expo + New Architecture
67+
</Text>
68+
</View>
69+
<View style={[styles.card, {backgroundColor: "#E3F2FD"}]}>
70+
<Text style={styles.cardTitle}>Card 2</Text>
71+
<Text style={styles.cardText}>
72+
This content should appear in the captured image
73+
</Text>
74+
</View>
75+
</View>
76+
);
77+
78+
return (
79+
<ScrollView style={styles.container}>
80+
<View style={styles.header}>
81+
<Text style={styles.headerTitle}>react-native-view-shot</Text>
82+
<Text style={styles.headerSubtitle}>Expo Example</Text>
83+
</View>
84+
85+
{/* Mode toggle */}
86+
<View style={styles.modeToggle}>
87+
<TouchableOpacity
88+
style={[
89+
styles.modeButton,
90+
mode === "view-ref" && styles.modeButtonActive,
91+
]}
92+
onPress={() => setMode("view-ref")}
93+
>
94+
<Text
95+
style={[
96+
styles.modeText,
97+
mode === "view-ref" && styles.modeTextActive,
98+
]}
99+
>
100+
captureRef(View)
101+
</Text>
102+
</TouchableOpacity>
103+
<TouchableOpacity
104+
style={[
105+
styles.modeButton,
106+
mode === "viewshot" && styles.modeButtonActive,
107+
]}
108+
onPress={() => setMode("viewshot")}
109+
>
110+
<Text
111+
style={[
112+
styles.modeText,
113+
mode === "viewshot" && styles.modeTextActive,
114+
]}
115+
>
116+
ViewShot.capture()
117+
</Text>
118+
</TouchableOpacity>
119+
</View>
120+
121+
{/* Capture area */}
122+
{mode === "viewshot" ? (
123+
<ViewShot
124+
ref={viewShotRef}
125+
options={{format: "png", quality: 0.9, result: "tmpfile"}}
126+
style={styles.captureArea}
127+
>
128+
{content}
129+
</ViewShot>
130+
) : (
131+
<View ref={viewRef} style={styles.captureArea}>
132+
{content}
133+
</View>
134+
)}
135+
136+
{/* Capture buttons */}
137+
<View style={styles.buttons}>
138+
<Text style={styles.sectionTitle}>Capture Options:</Text>
139+
140+
<TouchableOpacity
141+
style={[styles.btn, {backgroundColor: "#007AFF"}]}
142+
onPress={() => capture("png", "data-uri")}
143+
>
144+
<Text style={styles.btnText}>PNG (Data URI)</Text>
145+
</TouchableOpacity>
146+
147+
<TouchableOpacity
148+
style={[styles.btn, {backgroundColor: "#34C759"}]}
149+
onPress={() => capture("jpg", "data-uri")}
150+
>
151+
<Text style={styles.btnText}>JPG (Data URI)</Text>
152+
</TouchableOpacity>
153+
154+
<TouchableOpacity
155+
style={[styles.btn, {backgroundColor: "#FF9500"}]}
156+
onPress={() => capture("png", "base64")}
157+
>
158+
<Text style={styles.btnText}>PNG (Base64)</Text>
159+
</TouchableOpacity>
160+
161+
<TouchableOpacity
162+
style={[styles.btn, {backgroundColor: "#FF3B30"}]}
163+
onPress={() => capture("png", "tmpfile")}
164+
>
165+
<Text style={styles.btnText}>PNG (Tmpfile)</Text>
166+
</TouchableOpacity>
167+
</View>
168+
169+
{/* Error display */}
170+
{error && (
171+
<View style={styles.errorBox}>
172+
<Text style={styles.errorTitle}>Error:</Text>
173+
<Text style={styles.errorText}>{error}</Text>
174+
</View>
175+
)}
176+
177+
{/* Result preview */}
178+
{result && (
179+
<View style={styles.preview}>
180+
<Text style={styles.previewTitle}>Captured:</Text>
181+
<Image
182+
source={{uri: result}}
183+
style={styles.previewImage}
184+
resizeMode="contain"
185+
/>
186+
<Text style={styles.previewUri} numberOfLines={2}>
187+
{result.substring(0, 100)}...
188+
</Text>
189+
</View>
190+
)}
191+
</ScrollView>
192+
);
193+
}
194+
195+
const styles = StyleSheet.create({
196+
container: {
197+
flex: 1,
198+
backgroundColor: "#f5f5f5",
199+
},
200+
header: {
201+
paddingTop: 60,
202+
paddingBottom: 20,
203+
paddingHorizontal: 20,
204+
backgroundColor: "#fff",
205+
borderBottomWidth: 1,
206+
borderBottomColor: "#e0e0e0",
207+
},
208+
headerTitle: {
209+
fontSize: 22,
210+
fontWeight: "700",
211+
color: "#333",
212+
},
213+
headerSubtitle: {
214+
fontSize: 14,
215+
color: "#666",
216+
marginTop: 4,
217+
},
218+
modeToggle: {
219+
flexDirection: "row",
220+
margin: 16,
221+
gap: 8,
222+
},
223+
modeButton: {
224+
flex: 1,
225+
padding: 12,
226+
borderRadius: 8,
227+
borderWidth: 2,
228+
borderColor: "#007AFF",
229+
alignItems: "center",
230+
},
231+
modeButtonActive: {
232+
backgroundColor: "#007AFF",
233+
},
234+
modeText: {
235+
color: "#007AFF",
236+
fontWeight: "600",
237+
fontSize: 13,
238+
},
239+
modeTextActive: {
240+
color: "#fff",
241+
},
242+
captureArea: {
243+
marginHorizontal: 16,
244+
marginBottom: 16,
245+
backgroundColor: "#fff",
246+
borderRadius: 12,
247+
overflow: "hidden",
248+
},
249+
captureContent: {
250+
padding: 20,
251+
},
252+
title: {
253+
fontSize: 24,
254+
fontWeight: "700",
255+
textAlign: "center",
256+
marginBottom: 8,
257+
color: "#333",
258+
},
259+
subtitle: {
260+
fontSize: 14,
261+
textAlign: "center",
262+
marginBottom: 20,
263+
color: "#666",
264+
},
265+
card: {
266+
backgroundColor: "#f0f0f0",
267+
padding: 16,
268+
borderRadius: 8,
269+
marginBottom: 12,
270+
},
271+
cardTitle: {
272+
fontSize: 16,
273+
fontWeight: "600",
274+
marginBottom: 4,
275+
color: "#333",
276+
},
277+
cardText: {
278+
fontSize: 14,
279+
color: "#666",
280+
},
281+
buttons: {
282+
marginHorizontal: 16,
283+
marginBottom: 16,
284+
},
285+
sectionTitle: {
286+
fontSize: 16,
287+
fontWeight: "600",
288+
color: "#333",
289+
marginBottom: 12,
290+
},
291+
btn: {
292+
padding: 14,
293+
borderRadius: 8,
294+
alignItems: "center",
295+
marginBottom: 8,
296+
},
297+
btnText: {
298+
color: "#fff",
299+
fontSize: 15,
300+
fontWeight: "600",
301+
},
302+
errorBox: {
303+
marginHorizontal: 16,
304+
marginBottom: 16,
305+
padding: 16,
306+
backgroundColor: "#FFEBEE",
307+
borderRadius: 8,
308+
},
309+
errorTitle: {
310+
fontSize: 14,
311+
fontWeight: "600",
312+
color: "#C62828",
313+
marginBottom: 4,
314+
},
315+
errorText: {
316+
fontSize: 13,
317+
color: "#B71C1C",
318+
},
319+
preview: {
320+
marginHorizontal: 16,
321+
marginBottom: 40,
322+
alignItems: "center",
323+
},
324+
previewTitle: {
325+
fontSize: 16,
326+
fontWeight: "600",
327+
color: "#34C759",
328+
marginBottom: 12,
329+
},
330+
previewImage: {
331+
width: 280,
332+
height: 350,
333+
borderRadius: 8,
334+
borderWidth: 1,
335+
borderColor: "#ddd",
336+
backgroundColor: "#f5f5f5",
337+
marginBottom: 8,
338+
},
339+
previewUri: {
340+
fontSize: 11,
341+
color: "#999",
342+
textAlign: "center",
343+
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
344+
},
345+
});

0 commit comments

Comments
 (0)