diff options
author | Matthieu Sieben <matthieusieben@users.noreply.github.com> | 2024-05-12 23:18:42 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-12 14:18:42 -0700 |
commit | 00a57df5b16bc946c50079914962cc2819011e80 (patch) | |
tree | 4040fad00e74757d846bc503147b9e601e443c84 /src/lib/media | |
parent | 4458b031732149d6f9c107582b9e4ec343385518 (diff) | |
download | voidsky-00a57df5b16bc946c50079914962cc2819011e80.tar.zst |
✅ Fix "Download CAR file" on mobile (#3816)
* download CAR file using AtpAgent instead of building URL * add loader icon on download car button * actually save to disk on android * style nits * bottom margin nit * localize toast * remove fallback so back button works correctly * keep throwing an error if mime type isn't used * be more explicit with toasts * send errors to sentry when encountered --------- Co-authored-by: Hailey <me@haileyok.com>
Diffstat (limited to 'src/lib/media')
-rw-r--r-- | src/lib/media/manip.ts | 74 | ||||
-rw-r--r-- | src/lib/media/manip.web.ts | 25 |
2 files changed, 96 insertions, 3 deletions
diff --git a/src/lib/media/manip.ts b/src/lib/media/manip.ts index 9cd4abc62..71d5c701f 100644 --- a/src/lib/media/manip.ts +++ b/src/lib/media/manip.ts @@ -1,12 +1,23 @@ import {Image as RNImage, Share as RNShare} from 'react-native' import {Image} from 'react-native-image-crop-picker' import uuid from 'react-native-uuid' -import {cacheDirectory, copyAsync, deleteAsync} from 'expo-file-system' +import { + cacheDirectory, + copyAsync, + deleteAsync, + documentDirectory, + EncodingType, + makeDirectoryAsync, + StorageAccessFramework, + writeAsStringAsync, +} from 'expo-file-system' import * as MediaLibrary from 'expo-media-library' import * as Sharing from 'expo-sharing' import ImageResizer from '@bam.tech/react-native-image-resizer' +import {Buffer} from 'buffer' import RNFetchBlob from 'rn-fetch-blob' +import {logger} from '#/logger' import {isAndroid, isIOS} from 'platform/detection' import {Dimensions} from './types' @@ -240,3 +251,64 @@ function normalizePath(str: string, allPlatforms = false): string { } return str } + +export async function saveBytesToDisk( + filename: string, + bytes: Uint8Array, + type: string, +) { + const encoded = Buffer.from(bytes).toString('base64') + return await saveToDevice(filename, encoded, type) +} + +export async function saveToDevice( + filename: string, + encoded: string, + type: string, +) { + try { + if (isIOS) { + const tmpFileUrl = await withTempFile(filename, encoded) + await Sharing.shareAsync(tmpFileUrl, {UTI: type}) + safeDeleteAsync(tmpFileUrl) + return true + } else { + const permissions = + await StorageAccessFramework.requestDirectoryPermissionsAsync() + + if (!permissions.granted) { + return false + } + + const fileUrl = await StorageAccessFramework.createFileAsync( + permissions.directoryUri, + filename, + type, + ) + + await writeAsStringAsync(fileUrl, encoded, { + encoding: EncodingType.Base64, + }) + return true + } + } catch (e) { + logger.error('Error occurred while saving file', {message: e}) + return false + } +} + +async function withTempFile( + filename: string, + encoded: string, +): Promise<string> { + // Using a directory so that the file name is not a random string + // documentDirectory will always be available on native, so we assert as a string. + const tmpDirUri = joinPath(documentDirectory as string, String(uuid.v4())) + await makeDirectoryAsync(tmpDirUri, {intermediates: true}) + + const tmpFileUrl = joinPath(tmpDirUri, filename) + await writeAsStringAsync(tmpFileUrl, encoded, { + encoding: EncodingType.Base64, + }) + return tmpFileUrl +} diff --git a/src/lib/media/manip.web.ts b/src/lib/media/manip.web.ts index 522aa2e51..25315ebbd 100644 --- a/src/lib/media/manip.web.ts +++ b/src/lib/media/manip.web.ts @@ -1,6 +1,7 @@ -import {Dimensions} from './types' import {Image as RNImage} from 'react-native-image-crop-picker' -import {getDataUriSize, blobToDataUri} from './util' + +import {Dimensions} from './types' +import {blobToDataUri, getDataUriSize} from './util' export async function compressIfNeeded( img: RNImage, @@ -138,3 +139,23 @@ function createResizedImage( img.src = dataUri }) } + +export async function saveBytesToDisk( + filename: string, + bytes: Uint8Array, + type: string, +) { + const blob = new Blob([bytes], {type}) + const url = URL.createObjectURL(blob) + await downloadUrl(url, filename) + // Firefox requires a small delay + setTimeout(() => URL.revokeObjectURL(url), 100) + return true +} + +async function downloadUrl(href: string, filename: string) { + const a = document.createElement('a') + a.href = href + a.download = filename + a.click() +} |