about summary refs log tree commit diff
path: root/src/lib/media/manip.ts
diff options
context:
space:
mode:
authorMatthieu Sieben <matthieusieben@users.noreply.github.com>2024-05-12 23:18:42 +0200
committerGitHub <noreply@github.com>2024-05-12 14:18:42 -0700
commit00a57df5b16bc946c50079914962cc2819011e80 (patch)
tree4040fad00e74757d846bc503147b9e601e443c84 /src/lib/media/manip.ts
parent4458b031732149d6f9c107582b9e4ec343385518 (diff)
downloadvoidsky-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/manip.ts')
-rw-r--r--src/lib/media/manip.ts74
1 files changed, 73 insertions, 1 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
+}