Full.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /* eslint-disable react-native/no-inline-styles */
  2. import React, { Fragment, useState, useRef, useCallback } from 'react';
  3. import {
  4. SafeAreaView,
  5. StyleSheet,
  6. Switch,
  7. TextInput,
  8. Picker,
  9. ScrollView,
  10. View,
  11. Text,
  12. StatusBar,
  13. Image,
  14. } from 'react-native';
  15. import { Buffer } from 'buffer';
  16. import * as ART from '@react-native-community/art';
  17. import Slider from '@react-native-community/slider';
  18. import omit from 'lodash/omit';
  19. import { captureRef, captureScreen } from 'react-native-view-shot';
  20. import { Surface } from 'gl-react-native';
  21. import GL from 'gl-react';
  22. import MapView from 'react-native-maps';
  23. import WebView from 'react-native-webview';
  24. import Video from 'react-native-video';
  25. import SvgUri from 'react-native-svg-uri';
  26. import Btn from './Btn';
  27. const catsSource = {
  28. uri: 'https://i.imgur.com/5EOyTDQ.jpg',
  29. };
  30. const shaders = GL.Shaders.create({
  31. helloGL: {
  32. frag: `
  33. precision highp float;
  34. varying vec2 uv;
  35. uniform float blue;
  36. void main () {
  37. gl_FragColor = vec4(uv.x, uv.y, blue, 1.0);
  38. }`,
  39. },
  40. });
  41. const HelloGL = GL.createComponent(
  42. ({ blue }) => <GL.Node shader={shaders.helloGL} uniforms={{ blue }} />,
  43. { displayName: 'HelloGL' },
  44. );
  45. const App = () => {
  46. const fullRef = useRef();
  47. const headerRef = useRef();
  48. const formRef = useRef();
  49. const emptyRef = useRef();
  50. const complexRef = useRef();
  51. const svgRef = useRef();
  52. const glRef = useRef();
  53. const mapViewRef = useRef();
  54. const webviewRef = useRef();
  55. const videoRef = useRef();
  56. const transformParentRef = useRef();
  57. const transformRef = useRef();
  58. const surfaceRef = useRef();
  59. const [previewSource, setPreviewSource] = useState(catsSource);
  60. const [result, setResult] = useState({ error: null, res: null });
  61. const [config, setConfig] = useState({
  62. format: 'png',
  63. quality: 0.9,
  64. result: 'tmpfile',
  65. snapshotContentContainer: false,
  66. });
  67. const onCapture = useCallback(
  68. res => {
  69. if (config.result === 'base64') {
  70. const b = Buffer.from(res, 'base64');
  71. console.log('buffer of length ' + b.length);
  72. }
  73. setPreviewSource({
  74. uri: config.result === 'base64' ? 'data:image/' + config.format + ';base64,' + res : res,
  75. });
  76. setResult({
  77. error: null,
  78. res,
  79. });
  80. },
  81. [config],
  82. );
  83. const onCaptureFailure = useCallback(error => {
  84. console.warn(error);
  85. setPreviewSource(null);
  86. setResult({
  87. error,
  88. res: null,
  89. });
  90. }, []);
  91. const capture = useCallback(
  92. ref => {
  93. (ref ? captureRef(ref, config) : captureScreen(config))
  94. .then(res =>
  95. config.result !== 'tmpfile'
  96. ? res
  97. : new Promise((success, failure) =>
  98. // just a test to ensure res can be used in Image.getSize
  99. Image.getSize(res, (width, height) => success(res), failure),
  100. ),
  101. )
  102. .then(onCapture, onCaptureFailure);
  103. },
  104. [config, onCapture, onCaptureFailure],
  105. );
  106. return (
  107. <Fragment>
  108. <StatusBar barStyle="dark-content" />
  109. <SafeAreaView style={styles.root}>
  110. <ScrollView
  111. contentInsetAdjustmentBehavior="automatic"
  112. ref={fullRef}
  113. contentContainerStyle={styles.container}
  114. >
  115. <View ref={headerRef} style={styles.header}>
  116. <Text style={styles.title}>😃 ViewShot Example 😜</Text>
  117. <View style={styles.p1}>
  118. <Text style={styles.text}>This is a </Text>
  119. <Text style={styles.code}>react-native-view-shot</Text>
  120. <Text style={styles.text}> showcase.</Text>
  121. </View>
  122. <View style={styles.preview}>
  123. {result.error ? (
  124. <Text style={styles.previewError}>
  125. {'' + (result.error.message || result.error)}
  126. </Text>
  127. ) : (
  128. <Image
  129. fadeDuration={0}
  130. resizeMode="contain"
  131. style={styles.previewImage}
  132. source={previewSource}
  133. />
  134. )}
  135. </View>
  136. <Text numberOfLines={1} style={styles.previewUriText}>
  137. {result.res ? result.res.slice(0, 200) : ''}
  138. </Text>
  139. </View>
  140. <View ref={formRef} style={styles.form}>
  141. <View style={styles.btns}>
  142. <Btn label="😻 Reset" onPress={() => setPreviewSource(catsSource)} />
  143. <Btn label="📷 Head Section" onPress={() => capture(headerRef)} />
  144. <Btn label="📷 Form" onPress={() => capture(formRef)} />
  145. <Btn label="📷 Experimental Section" onPress={() => capture(complexRef)} />
  146. <Btn label="📷 All (ScrollView)" onPress={() => capture(fullRef)} />
  147. <Btn label="📷 SVG" onPress={() => capture(svgRef)} />
  148. <Btn label="📷 Transform" onPress={() => capture(transformParentRef)} />
  149. <Btn label="📷 Transform Child" onPress={() => capture(transformRef)} />
  150. <Btn label="📷 GL React" onPress={() => capture(glRef)} />
  151. <Btn label="📷 MapView" onPress={() => capture(mapViewRef)} />
  152. <Btn label="📷 WebView" onPress={() => capture(webviewRef)} />
  153. <Btn label="📷 Video" onPress={() => capture(videoRef)} />
  154. <Btn label="📷 Native Screenshot" onPress={() => capture()} />
  155. <Btn label="📷 Empty View (should crash)" onPress={() => capture(emptyRef)} />
  156. </View>
  157. <View style={styles.field}>
  158. <Text style={styles.label}>Format</Text>
  159. <Picker
  160. style={styles.input}
  161. selectedValue={config.format}
  162. onValueChange={format => setConfig({ ...config, format })}
  163. >
  164. <Picker.Item label="PNG" value="png" />
  165. <Picker.Item label="JPEG" value="jpg" />
  166. <Picker.Item label="WEBM (android only)" value="webm" />
  167. <Picker.Item label="RAW (android only)" value="raw" />
  168. <Picker.Item label="INVALID" value="_invalid_" />
  169. </Picker>
  170. </View>
  171. <View style={styles.field}>
  172. <Text style={styles.label}>Quality</Text>
  173. <Slider
  174. style={styles.input}
  175. value={config.quality}
  176. onValueChange={quality => setConfig({ ...config, quality })}
  177. />
  178. <Text>{(config.quality * 100).toFixed(0)}%</Text>
  179. </View>
  180. <View style={styles.field}>
  181. <Text style={styles.label}>Size</Text>
  182. <Switch
  183. style={styles.switch}
  184. value={config.width !== undefined}
  185. onValueChange={checked =>
  186. setConfig(
  187. omit(
  188. {
  189. ...config,
  190. width: 300,
  191. height: 300,
  192. },
  193. checked ? [] : ['width', 'height'],
  194. ),
  195. )
  196. }
  197. />
  198. {config.width !== undefined ? (
  199. <TextInput
  200. style={styles.inputText}
  201. value={'' + config.width}
  202. keyboardType="number-pad"
  203. onChangeText={txt =>
  204. !isNaN(txt) && setConfig({ ...config, width: parseInt(txt, 10) })
  205. }
  206. />
  207. ) : (
  208. <Text style={styles.inputText}>(auto)</Text>
  209. )}
  210. <Text>x</Text>
  211. {config.height !== undefined ? (
  212. <TextInput
  213. style={styles.inputText}
  214. value={'' + config.height}
  215. keyboardType="number-pad"
  216. onChangeText={txt =>
  217. !isNaN(txt) && setConfig({ ...config, height: parseInt(txt, 10) })
  218. }
  219. />
  220. ) : (
  221. <Text style={styles.inputText}>(auto)</Text>
  222. )}
  223. </View>
  224. <View style={styles.field}>
  225. <Text style={styles.label}>Result</Text>
  226. <Picker
  227. style={styles.input}
  228. selectedValue={config.result}
  229. onValueChange={r => setConfig({ ...config, result: r })}
  230. >
  231. <Picker.Item label="tmpfile" value="tmpfile" />
  232. <Picker.Item label="base64" value="base64" />
  233. <Picker.Item label="zip-base64 (Android Only)" value="zip-base64" />
  234. <Picker.Item label="data URI" value="data-uri" />
  235. <Picker.Item label="INVALID" value="_invalid_" />
  236. </Picker>
  237. </View>
  238. <View style={styles.field}>
  239. <Text style={styles.label}>snapshotContentContainer</Text>
  240. <Switch
  241. style={styles.switch}
  242. value={config.snapshotContentContainer}
  243. onValueChange={snapshotContentContainer =>
  244. setConfig({ ...config, snapshotContentContainer })
  245. }
  246. />
  247. </View>
  248. </View>
  249. <View ref={emptyRef} collapsable={false} />
  250. <View style={styles.experimental} ref={complexRef} collapsable={false}>
  251. <Text style={styles.experimentalTitle}>Experimental Stuff</Text>
  252. <View ref={transformParentRef} collapsable={false} style={styles.experimentalRoot}>
  253. <View collapsable={false} style={styles.experimentalTransform}>
  254. <Text ref={transformRef}>Transform</Text>
  255. <ART.Surface ref={surfaceRef} width={20} height={20}>
  256. <ART.Text fill="#000000" font={{ fontFamily: 'Arial', fontSize: 6 }}>
  257. Sample Text
  258. </ART.Text>
  259. <ART.Shape
  260. d="M2.876,10.6499757 L16.375,18.3966817 C16.715,18.5915989 17.011,18.4606545 17.125,18.3956822 C17.237,18.3307098 17.499,18.1367923 17.499,17.746958 L17.499,2.25254636 C17.499,1.86271212 17.237,1.66879457 17.125,1.6038222 C17.011,1.53884983 16.715,1.4079055 16.375,1.60282262 L2.876,9.34952866 C2.537,9.54544536 2.5,9.86930765 2.5,10.000252 C2.5,10.1301967 2.537,10.4550586 2.876,10.6499757 M16.749,20 C16.364,20 15.98,19.8990429 15.629,19.6971288 L2.13,11.9504227 L2.129,11.9504227 C1.422,11.5445953 1,10.8149056 1,10.000252 C1,9.18459879 1.422,8.45590864 2.129,8.04908162 L15.629,0.302375584 C16.332,-0.10245228 17.173,-0.10045313 17.876,0.306373884 C18.579,0.713200898 18.999,1.44089148 18.999,2.25254636 L18.999,17.746958 C18.999,18.5586129 18.579,19.2863035 17.876,19.6931305 C17.523,19.8970438 17.136,20 16.749,20"
  261. fill="blue"
  262. stroke="black"
  263. strokeWidth={0}
  264. />
  265. </ART.Surface>
  266. </View>
  267. <View style={styles.experimentalTransformV2}>
  268. <ART.Surface width={20} height={20}>
  269. <ART.Shape
  270. x={0}
  271. y={10}
  272. d="M2.876,10.6499757 L16.375,18.3966817 C16.715,18.5915989 17.011,18.4606545 17.125,18.3956822 C17.237,18.3307098 17.499,18.1367923 17.499,17.746958 L17.499,2.25254636 C17.499,1.86271212 17.237,1.66879457 17.125,1.6038222 C17.011,1.53884983 16.715,1.4079055 16.375,1.60282262 L2.876,9.34952866 C2.537,9.54544536 2.5,9.86930765 2.5,10.000252 C2.5,10.1301967 2.537,10.4550586 2.876,10.6499757 M16.749,20 C16.364,20 15.98,19.8990429 15.629,19.6971288 L2.13,11.9504227 L2.129,11.9504227 C1.422,11.5445953 1,10.8149056 1,10.000252 C1,9.18459879 1.422,8.45590864 2.129,8.04908162 L15.629,0.302375584 C16.332,-0.10245228 17.173,-0.10045313 17.876,0.306373884 C18.579,0.713200898 18.999,1.44089148 18.999,2.25254636 L18.999,17.746958 C18.999,18.5586129 18.579,19.2863035 17.876,19.6931305 C17.523,19.8970438 17.136,20 16.749,20"
  273. fill="red"
  274. />
  275. </ART.Surface>
  276. </View>
  277. </View>
  278. <View ref={svgRef} collapsable={false}>
  279. <SvgUri width={200} height={200} source={require('./homer-simpson.svg')} />
  280. </View>
  281. <View ref={glRef} collapsable={false}>
  282. <Surface width={300} height={300}>
  283. <HelloGL blue={0.5} />
  284. </Surface>
  285. </View>
  286. <MapView
  287. ref={mapViewRef}
  288. initialRegion={{
  289. latitude: 37.78825,
  290. longitude: -122.4324,
  291. latitudeDelta: 0.0922,
  292. longitudeDelta: 0.0421,
  293. }}
  294. style={{ width: 300, height: 300 }}
  295. />
  296. <View ref={webviewRef} collapsable={false} style={{ width: 300, height: 300 }}>
  297. <WebView
  298. source={{
  299. uri: 'https://github.com/gre/react-native-view-shot',
  300. }}
  301. />
  302. </View>
  303. <Video
  304. ref={videoRef}
  305. style={{ width: 300, height: 300 }}
  306. source={require('./broadchurch.mp4')}
  307. volume={0}
  308. repeat
  309. />
  310. </View>
  311. </ScrollView>
  312. </SafeAreaView>
  313. </Fragment>
  314. );
  315. };
  316. const styles = StyleSheet.create({
  317. root: {
  318. backgroundColor: '#f6f6f6',
  319. },
  320. container: {
  321. paddingVertical: 20,
  322. },
  323. title: {
  324. fontSize: 20,
  325. textAlign: 'center',
  326. margin: 10,
  327. },
  328. experimental: {
  329. padding: 10,
  330. flexDirection: 'column',
  331. alignItems: 'center',
  332. },
  333. experimentalTitle: {
  334. fontSize: 16,
  335. margin: 10,
  336. },
  337. experimentalTransform: {
  338. transform: [{ rotate: '180deg' }],
  339. backgroundColor: 'powderblue',
  340. },
  341. experimentalTransformV2: {
  342. // transform: [{ rotate: '180deg' }],
  343. backgroundColor: 'skyblue',
  344. },
  345. p1: {
  346. marginBottom: 10,
  347. flexDirection: 'row',
  348. flexWrap: 'wrap',
  349. justifyContent: 'center',
  350. alignItems: 'center',
  351. },
  352. text: {
  353. color: '#333',
  354. },
  355. code: {
  356. fontWeight: 'bold',
  357. color: '#000',
  358. },
  359. field: {
  360. flexDirection: 'row',
  361. alignItems: 'center',
  362. paddingVertical: 4,
  363. paddingHorizontal: 10,
  364. },
  365. label: {
  366. minWidth: 80,
  367. fontStyle: 'italic',
  368. color: '#888',
  369. },
  370. switch: {
  371. marginRight: 50,
  372. },
  373. input: {
  374. flex: 1,
  375. marginHorizontal: 5,
  376. },
  377. inputText: {
  378. flex: 1,
  379. marginHorizontal: 5,
  380. color: 'red',
  381. textAlign: 'center',
  382. },
  383. preview: {
  384. flexDirection: 'row',
  385. alignItems: 'center',
  386. justifyContent: 'space-around',
  387. },
  388. previewImage: {
  389. width: 375,
  390. height: 300,
  391. },
  392. previewUriText: {
  393. fontSize: 12,
  394. fontStyle: 'italic',
  395. color: '#666',
  396. textAlign: 'center',
  397. padding: 10,
  398. paddingBottom: 0,
  399. },
  400. previewError: {
  401. width: 375,
  402. height: 300,
  403. paddingTop: 20,
  404. textAlign: 'center',
  405. fontSize: 20,
  406. fontWeight: 'bold',
  407. color: '#fff',
  408. backgroundColor: '#c00',
  409. },
  410. header: {
  411. backgroundColor: '#f6f6f6',
  412. borderColor: '#000',
  413. borderWidth: 1,
  414. paddingBottom: 20,
  415. },
  416. form: {
  417. backgroundColor: '#fff',
  418. },
  419. btns: {
  420. flexDirection: 'row',
  421. flexWrap: 'wrap',
  422. alignItems: 'center',
  423. justifyContent: 'center',
  424. paddingVertical: 10,
  425. margin: 4,
  426. },
  427. experimentalRoot: {
  428. flex: 1,
  429. flexDirection: 'row',
  430. },
  431. });
  432. App.navigationOptions = {
  433. title: 'Full Example',
  434. };
  435. export default App;