mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import RNFetchBlob from 'rn-fetch-blob'
2import ImageResizer from '@bam.tech/react-native-image-resizer'
3import {Image as RNImage, Share as RNShare} from 'react-native'
4import {Image} from 'react-native-image-crop-picker'
5import * as RNFS from 'react-native-fs'
6import uuid from 'react-native-uuid'
7import * as Sharing from 'expo-sharing'
8import * as MediaLibrary from 'expo-media-library'
9import {Dimensions} from './types'
10import {isAndroid, isIOS} from 'platform/detection'
11
12export async function compressIfNeeded(
13 img: Image,
14 maxSize: number = 1000000,
15): Promise<Image> {
16 const origUri = `file://${img.path}`
17 if (img.size < maxSize) {
18 return img
19 }
20 const resizedImage = await doResize(origUri, {
21 width: img.width,
22 height: img.height,
23 mode: 'stretch',
24 maxSize,
25 })
26 const finalImageMovedPath = await moveToPermanentPath(resizedImage.path)
27 const finalImg = {
28 ...resizedImage,
29 path: finalImageMovedPath,
30 }
31 return finalImg
32}
33
34export interface DownloadAndResizeOpts {
35 uri: string
36 width: number
37 height: number
38 mode: 'contain' | 'cover' | 'stretch'
39 maxSize: number
40 timeout: number
41}
42
43export async function downloadAndResize(opts: DownloadAndResizeOpts) {
44 let appendExt = 'jpeg'
45 try {
46 const urip = new URL(opts.uri)
47 const ext = urip.pathname.split('.').pop()
48 if (ext === 'png') {
49 appendExt = 'png'
50 }
51 } catch (e: any) {
52 console.error('Invalid URI', opts.uri, e)
53 return
54 }
55
56 let downloadRes
57 try {
58 const downloadResPromise = RNFetchBlob.config({
59 fileCache: true,
60 appendExt,
61 }).fetch('GET', opts.uri)
62 const to1 = setTimeout(() => downloadResPromise.cancel(), opts.timeout)
63 downloadRes = await downloadResPromise
64 clearTimeout(to1)
65
66 let localUri = downloadRes.path()
67 if (!localUri.startsWith('file://')) {
68 localUri = `file://${localUri}`
69 }
70
71 return await doResize(localUri, opts)
72 } finally {
73 if (downloadRes) {
74 downloadRes.flush()
75 }
76 }
77}
78
79export async function shareImageModal({uri}: {uri: string}) {
80 if (!(await Sharing.isAvailableAsync())) {
81 // TODO might need to give an error to the user in this case -prf
82 return
83 }
84 const downloadResponse = await RNFetchBlob.config({
85 fileCache: true,
86 }).fetch('GET', uri)
87
88 // NOTE
89 // assuming PNG
90 // we're currently relying on the fact our CDN only serves pngs
91 // -prf
92
93 let imagePath = downloadResponse.path()
94 imagePath = normalizePath(await moveToPermanentPath(imagePath, '.png'), true)
95
96 // NOTE
97 // for some reason expo-sharing refuses to work on iOS
98 // ...and visa versa
99 // -prf
100 if (isIOS) {
101 await RNShare.share({url: imagePath})
102 } else {
103 await Sharing.shareAsync(imagePath, {
104 mimeType: 'image/png',
105 UTI: 'image/png',
106 })
107 }
108 RNFS.unlink(imagePath)
109}
110
111export async function saveImageToMediaLibrary({uri}: {uri: string}) {
112 // download the file to cache
113 // NOTE
114 // assuming PNG
115 // we're currently relying on the fact our CDN only serves pngs
116 // -prf
117 const downloadResponse = await RNFetchBlob.config({
118 fileCache: true,
119 }).fetch('GET', uri)
120 let imagePath = downloadResponse.path()
121 imagePath = normalizePath(await moveToPermanentPath(imagePath, '.png'), true)
122
123 // save
124 await MediaLibrary.createAssetAsync(imagePath)
125}
126
127export function getImageDim(path: string): Promise<Dimensions> {
128 return new Promise((resolve, reject) => {
129 RNImage.getSize(
130 path,
131 (width, height) => {
132 resolve({width, height})
133 },
134 reject,
135 )
136 })
137}
138
139// internal methods
140// =
141
142interface DoResizeOpts {
143 width: number
144 height: number
145 mode: 'contain' | 'cover' | 'stretch'
146 maxSize: number
147}
148
149async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
150 for (let i = 0; i < 9; i++) {
151 const quality = 100 - i * 10
152 const resizeRes = await ImageResizer.createResizedImage(
153 localUri,
154 opts.width,
155 opts.height,
156 'JPEG',
157 quality,
158 undefined,
159 undefined,
160 undefined,
161 {mode: opts.mode},
162 )
163 if (resizeRes.size < opts.maxSize) {
164 return {
165 path: normalizePath(resizeRes.path),
166 mime: 'image/jpeg',
167 size: resizeRes.size,
168 width: resizeRes.width,
169 height: resizeRes.height,
170 }
171 }
172 }
173 throw new Error(
174 `This image is too big! We couldn't compress it down to ${opts.maxSize} bytes`,
175 )
176}
177
178async function moveToPermanentPath(path: string, ext = ''): Promise<string> {
179 /*
180 Since this package stores images in a temp directory, we need to move the file to a permanent location.
181 Relevant: IOS bug when trying to open a second time:
182 https://github.com/ivpusic/react-native-image-crop-picker/issues/1199
183 */
184 const filename = uuid.v4()
185
186 const destinationPath = joinPath(
187 RNFS.TemporaryDirectoryPath,
188 `${filename}${ext}`,
189 )
190 await RNFS.moveFile(path, destinationPath)
191 return normalizePath(destinationPath)
192}
193
194function joinPath(a: string, b: string) {
195 if (a.endsWith('/')) {
196 if (b.startsWith('/')) {
197 return a.slice(0, -1) + b
198 }
199 return a + b
200 } else if (b.startsWith('/')) {
201 return a + b
202 }
203 return a + '/' + b
204}
205
206function normalizePath(str: string, allPlatforms = false): string {
207 if (isAndroid || allPlatforms) {
208 if (!str.startsWith('file://')) {
209 return `file://${str}`
210 }
211 }
212 return str
213}