migrate to expo-file-system
This commit is contained in:
@@ -1,25 +1,29 @@
|
||||
import {createDownloadResumable, deleteAsync} from 'expo-file-system'
|
||||
import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
|
||||
|
||||
import {
|
||||
downloadAndResize,
|
||||
DownloadAndResizeOpts,
|
||||
} from '../../src/lib/media/manip'
|
||||
import ImageResizer from '@bam.tech/react-native-image-resizer'
|
||||
import RNFetchBlob from 'rn-fetch-blob'
|
||||
getResizedDimensions,
|
||||
} from 'lib/media/manip'
|
||||
|
||||
describe('downloadAndResize', () => {
|
||||
const errorSpy = jest.spyOn(global.console, 'error')
|
||||
|
||||
const mockResizedImage = {
|
||||
path: jest.fn().mockReturnValue('file://resized-image.jpg'),
|
||||
path: 'file://resized-image.jpg',
|
||||
size: 100,
|
||||
width: 50,
|
||||
height: 50,
|
||||
width: 100,
|
||||
height: 100,
|
||||
mime: 'image/jpeg',
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedCreateResizedImage =
|
||||
ImageResizer.createResizedImage as jest.Mock
|
||||
mockedCreateResizedImage.mockResolvedValue(mockResizedImage)
|
||||
const mockedCreateResizedImage = manipulateAsync as jest.Mock
|
||||
mockedCreateResizedImage.mockResolvedValue({
|
||||
uri: 'file://resized-image.jpg',
|
||||
...mockResizedImage,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@@ -27,10 +31,12 @@ describe('downloadAndResize', () => {
|
||||
})
|
||||
|
||||
it('should return resized image for valid URI and options', async () => {
|
||||
const mockedFetch = RNFetchBlob.fetch as jest.Mock
|
||||
mockedFetch.mockResolvedValueOnce({
|
||||
path: jest.fn().mockReturnValue('file://downloaded-image.jpg'),
|
||||
flush: jest.fn(),
|
||||
const mockedFetch = createDownloadResumable as jest.Mock
|
||||
mockedFetch.mockReturnValue({
|
||||
cancelAsync: jest.fn(),
|
||||
downloadAsync: jest
|
||||
.fn()
|
||||
.mockResolvedValue({uri: 'file://resized-image.jpg'}),
|
||||
})
|
||||
|
||||
const opts: DownloadAndResizeOpts = {
|
||||
@@ -44,25 +50,22 @@ describe('downloadAndResize', () => {
|
||||
|
||||
const result = await downloadAndResize(opts)
|
||||
expect(result).toEqual(mockResizedImage)
|
||||
expect(RNFetchBlob.config).toHaveBeenCalledWith({
|
||||
fileCache: true,
|
||||
appendExt: 'jpeg',
|
||||
expect(createDownloadResumable).toHaveBeenCalledWith(
|
||||
opts.uri,
|
||||
expect.anything(),
|
||||
{
|
||||
cache: true,
|
||||
},
|
||||
)
|
||||
expect(manipulateAsync).toHaveBeenCalledWith(expect.anything(), [], {
|
||||
format: SaveFormat.JPEG,
|
||||
})
|
||||
expect(RNFetchBlob.fetch).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'https://example.com/image.jpg',
|
||||
)
|
||||
expect(ImageResizer.createResizedImage).toHaveBeenCalledWith(
|
||||
'file://downloaded-image.jpg',
|
||||
100,
|
||||
100,
|
||||
'JPEG',
|
||||
100,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{mode: 'cover'},
|
||||
expect(manipulateAsync).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
[{resize: {height: opts.height, width: opts.width}}],
|
||||
{format: SaveFormat.JPEG, compress: 0.9},
|
||||
)
|
||||
expect(deleteAsync).toHaveBeenCalledWith(expect.anything())
|
||||
})
|
||||
|
||||
it('should return undefined for invalid URI', async () => {
|
||||
@@ -81,10 +84,12 @@ describe('downloadAndResize', () => {
|
||||
})
|
||||
|
||||
it('should return undefined for unsupported file type', async () => {
|
||||
const mockedFetch = RNFetchBlob.fetch as jest.Mock
|
||||
mockedFetch.mockResolvedValueOnce({
|
||||
path: jest.fn().mockReturnValue('file://downloaded-image'),
|
||||
flush: jest.fn(),
|
||||
const mockedFetch = createDownloadResumable as jest.Mock
|
||||
mockedFetch.mockReturnValue({
|
||||
cancelAsync: jest.fn(),
|
||||
downloadAsync: jest
|
||||
.fn()
|
||||
.mockResolvedValue({uri: 'file://downloaded-image'}),
|
||||
})
|
||||
|
||||
const opts: DownloadAndResizeOpts = {
|
||||
@@ -98,24 +103,63 @@ describe('downloadAndResize', () => {
|
||||
|
||||
const result = await downloadAndResize(opts)
|
||||
expect(result).toEqual(mockResizedImage)
|
||||
expect(RNFetchBlob.config).toHaveBeenCalledWith({
|
||||
fileCache: true,
|
||||
appendExt: 'jpeg',
|
||||
expect(createDownloadResumable).toHaveBeenCalledWith(
|
||||
opts.uri,
|
||||
expect.anything(),
|
||||
{
|
||||
cache: true,
|
||||
},
|
||||
)
|
||||
expect(manipulateAsync).toHaveBeenCalledWith(expect.anything(), [], {
|
||||
format: SaveFormat.JPEG,
|
||||
})
|
||||
expect(RNFetchBlob.fetch).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'https://example.com/image',
|
||||
)
|
||||
expect(ImageResizer.createResizedImage).toHaveBeenCalledWith(
|
||||
'file://downloaded-image',
|
||||
100,
|
||||
100,
|
||||
'JPEG',
|
||||
100,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{mode: 'cover'},
|
||||
expect(manipulateAsync).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
[{resize: {height: opts.height, width: opts.width}}],
|
||||
{format: SaveFormat.JPEG, compress: 0.9},
|
||||
)
|
||||
expect(deleteAsync).toHaveBeenCalledWith(expect.anything())
|
||||
})
|
||||
})
|
||||
|
||||
describe('produces correct new sizes for images', () => {
|
||||
it('should not downsize whenever dimensions are below the max dimensions', () => {
|
||||
const initialDimensionsOne = {
|
||||
width: 1200,
|
||||
height: 1000,
|
||||
}
|
||||
const resizedDimensionsOne = getResizedDimensions(initialDimensionsOne)
|
||||
|
||||
const initialDimensionsTwo = {
|
||||
width: 1000,
|
||||
height: 1200,
|
||||
}
|
||||
const resizedDimensionsTwo = getResizedDimensions(initialDimensionsTwo)
|
||||
|
||||
expect(resizedDimensionsOne).toEqual(initialDimensionsOne)
|
||||
expect(resizedDimensionsTwo).toEqual(initialDimensionsTwo)
|
||||
})
|
||||
|
||||
it('should resize dimensions and maintain aspect ratio if they are above the max dimensons', () => {
|
||||
const initialDimensionsOne = {
|
||||
width: 3000,
|
||||
height: 1500,
|
||||
}
|
||||
const resizedDimensionsOne = getResizedDimensions(initialDimensionsOne)
|
||||
|
||||
const initialDimensionsTwo = {
|
||||
width: 2000,
|
||||
height: 4000,
|
||||
}
|
||||
const resizedDimensionsTwo = getResizedDimensions(initialDimensionsTwo)
|
||||
|
||||
expect(resizedDimensionsOne).toEqual({
|
||||
width: 2000,
|
||||
height: 1000,
|
||||
})
|
||||
expect(resizedDimensionsTwo).toEqual({
|
||||
width: 1000,
|
||||
height: 2000,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+13
-8
@@ -1,10 +1,10 @@
|
||||
/* global jest */
|
||||
import {configure} from '@testing-library/react-native'
|
||||
import 'react-native-gesture-handler/jestSetup'
|
||||
|
||||
// IMPORTANT: this is what's used in the native runtime
|
||||
import 'react-native-url-polyfill/auto'
|
||||
|
||||
import {configure} from '@testing-library/react-native'
|
||||
|
||||
configure({asyncUtilTimeout: 20000})
|
||||
|
||||
jest.mock('@react-native-async-storage/async-storage', () =>
|
||||
@@ -36,14 +36,19 @@ jest.mock('react-native-safe-area-context', () => {
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock('rn-fetch-blob', () => ({
|
||||
config: jest.fn().mockReturnThis(),
|
||||
cancel: jest.fn(),
|
||||
fetch: jest.fn(),
|
||||
jest.mock('expo-file-system', () => ({
|
||||
createDownloadResumable: jest.fn(),
|
||||
deleteAsync: jest.fn(),
|
||||
getInfoAsync: jest.fn().mockResolvedValue({
|
||||
size: 100,
|
||||
}),
|
||||
}))
|
||||
|
||||
jest.mock('@bam.tech/react-native-image-resizer', () => ({
|
||||
createResizedImage: jest.fn(),
|
||||
jest.mock('expo-image-manipulator', () => ({
|
||||
manipulateAsync: jest.fn().mockResolvedValue({
|
||||
uri: 'file://resized-image',
|
||||
}),
|
||||
SaveFormat: jest.requireActual('expo-image-manipulator').SaveFormat,
|
||||
}))
|
||||
|
||||
jest.mock('@segment/analytics-react-native', () => ({
|
||||
|
||||
+1
-2
@@ -113,6 +113,7 @@
|
||||
"expo-constants": "~15.4.5",
|
||||
"expo-dev-client": "~3.3.8",
|
||||
"expo-device": "~5.9.3",
|
||||
"expo-file-system": "^16.0.8",
|
||||
"expo-haptics": "^12.8.1",
|
||||
"expo-image": "~1.10.6",
|
||||
"expo-image-manipulator": "^11.8.0",
|
||||
@@ -158,7 +159,6 @@
|
||||
"react-native": "0.73.2",
|
||||
"react-native-date-picker": "^4.4.0",
|
||||
"react-native-drawer-layout": "^4.0.0-alpha.3",
|
||||
"react-native-fs": "^2.20.0",
|
||||
"react-native-gesture-handler": "~2.14.0",
|
||||
"react-native-get-random-values": "~1.11.0",
|
||||
"react-native-image-crop-picker": "^0.38.1",
|
||||
@@ -178,7 +178,6 @@
|
||||
"react-native-web-webview": "^1.0.2",
|
||||
"react-native-webview": "13.6.4",
|
||||
"react-responsive": "^9.0.2",
|
||||
"rn-fetch-blob": "^0.12.0",
|
||||
"sentry-expo": "~7.0.1",
|
||||
"statsig-react-native-expo": "^4.6.1",
|
||||
"tippy.js": "^6.3.7",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {BskyAgent, stringifyLex, jsonToLex} from '@atproto/api'
|
||||
import RNFS from 'react-native-fs'
|
||||
import {cacheDirectory, copyAsync, moveAsync} from 'expo-file-system'
|
||||
import {BskyAgent, jsonToLex, stringifyLex} from '@atproto/api'
|
||||
|
||||
const GET_TIMEOUT = 15e3 // 15s
|
||||
const POST_TIMEOUT = 60e3 // 60s
|
||||
@@ -33,9 +33,27 @@ async function fetchHandler(
|
||||
// we get around that by renaming the file ext to .bin
|
||||
// see https://github.com/facebook/react-native/issues/27099
|
||||
// -prf
|
||||
const newPath = reqBody.replace(/\.jpe?g$/, '.bin')
|
||||
await RNFS.moveFile(reqBody, newPath)
|
||||
reqBody = newPath
|
||||
|
||||
// On some platforms, moving this file is not possible. We will attempt to move it (this is optimal, since
|
||||
// we don't create duplicates) and if there is an error, we will instead copy the file to the cache directory
|
||||
const fileName = reqBody.split('/').pop() ?? ''
|
||||
const newPath = `${cacheDirectory ?? ''}${fileName.replace(
|
||||
/\.jpe?g$/,
|
||||
'.bin',
|
||||
)}`
|
||||
try {
|
||||
await moveAsync({
|
||||
from: reqBody,
|
||||
to: newPath,
|
||||
})
|
||||
reqBody = newPath
|
||||
} catch (e) {
|
||||
await copyAsync({
|
||||
from: reqBody,
|
||||
to: newPath,
|
||||
})
|
||||
reqBody = newPath
|
||||
}
|
||||
}
|
||||
// NOTE
|
||||
// React native treats bodies with {uri: string} as file uploads to pull from cache
|
||||
|
||||
+13
-7
@@ -1,6 +1,7 @@
|
||||
import {deleteAsync} from 'expo-file-system'
|
||||
import {
|
||||
AppBskyEmbedImages,
|
||||
AppBskyEmbedExternal,
|
||||
AppBskyEmbedImages,
|
||||
AppBskyEmbedRecord,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
AppBskyFeedThreadgate,
|
||||
@@ -11,13 +12,14 @@ import {
|
||||
RichText,
|
||||
} from '@atproto/api'
|
||||
import {AtUri} from '@atproto/api'
|
||||
import {isNetworkError} from 'lib/strings/errors'
|
||||
import {LinkMeta} from '../link-meta/link-meta'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {ImageModel} from 'state/models/media/image'
|
||||
import {shortenLinks} from 'lib/strings/rich-text-manip'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {ThreadgateSetting} from '#/state/queries/threadgate'
|
||||
import {isNetworkError} from 'lib/strings/errors'
|
||||
import {shortenLinks} from 'lib/strings/rich-text-manip'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {ImageModel} from 'state/models/media/image'
|
||||
import {LinkMeta} from '../link-meta/link-meta'
|
||||
|
||||
export interface ExternalEmbedDraft {
|
||||
uri: string
|
||||
@@ -39,10 +41,14 @@ export async function uploadBlob(
|
||||
})
|
||||
} else {
|
||||
// `blob` should be a path to a file in the local FS
|
||||
return agent.uploadBlob(
|
||||
const res = await agent.uploadBlob(
|
||||
blob, // this will be special-cased by the fetch monkeypatch in /src/state/lib/api.ts
|
||||
{encoding},
|
||||
)
|
||||
try {
|
||||
deleteAsync(blob)
|
||||
} catch (e) {} // Don't need to handle
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+153
-80
@@ -1,13 +1,21 @@
|
||||
import RNFetchBlob from 'rn-fetch-blob'
|
||||
import ImageResizer from '@bam.tech/react-native-image-resizer'
|
||||
import {Image as RNImage, Share as RNShare} from 'react-native'
|
||||
import {Image as RNImage} from 'react-native'
|
||||
import {Image} from 'react-native-image-crop-picker'
|
||||
import * as RNFS from 'react-native-fs'
|
||||
import uuid from 'react-native-uuid'
|
||||
import * as Sharing from 'expo-sharing'
|
||||
import {
|
||||
cacheDirectory,
|
||||
copyAsync,
|
||||
createDownloadResumable,
|
||||
deleteAsync,
|
||||
FileInfo,
|
||||
getInfoAsync,
|
||||
} from 'expo-file-system'
|
||||
import {Image as ExpoImage} from 'expo-image'
|
||||
import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
|
||||
import * as MediaLibrary from 'expo-media-library'
|
||||
import * as Sharing from 'expo-sharing'
|
||||
|
||||
import {POST_IMG_MAX} from 'lib/constants'
|
||||
import {Dimensions} from './types'
|
||||
import {isAndroid, isIOS} from 'platform/detection'
|
||||
|
||||
export async function compressIfNeeded(
|
||||
img: Image,
|
||||
@@ -53,26 +61,13 @@ export async function downloadAndResize(opts: DownloadAndResizeOpts) {
|
||||
return
|
||||
}
|
||||
|
||||
let downloadRes
|
||||
const path = createPath(appendExt)
|
||||
|
||||
try {
|
||||
const downloadResPromise = RNFetchBlob.config({
|
||||
fileCache: true,
|
||||
appendExt,
|
||||
}).fetch('GET', opts.uri)
|
||||
const to1 = setTimeout(() => downloadResPromise.cancel(), opts.timeout)
|
||||
downloadRes = await downloadResPromise
|
||||
clearTimeout(to1)
|
||||
|
||||
let localUri = downloadRes.path()
|
||||
if (!localUri.startsWith('file://')) {
|
||||
localUri = `file://${localUri}`
|
||||
}
|
||||
|
||||
return await doResize(localUri, opts)
|
||||
await downloadImage(opts.uri, path, opts.timeout)
|
||||
return await doResize(path, opts)
|
||||
} finally {
|
||||
if (downloadRes) {
|
||||
downloadRes.flush()
|
||||
}
|
||||
deleteAsync(path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,47 +76,45 @@ export async function shareImageModal({uri}: {uri: string}) {
|
||||
// TODO might need to give an error to the user in this case -prf
|
||||
return
|
||||
}
|
||||
const downloadResponse = await RNFetchBlob.config({
|
||||
fileCache: true,
|
||||
}).fetch('GET', uri)
|
||||
|
||||
// NOTE
|
||||
// assuming PNG
|
||||
// we're currently relying on the fact our CDN only serves pngs
|
||||
// -prf
|
||||
|
||||
let imagePath = downloadResponse.path()
|
||||
imagePath = normalizePath(await moveToPermanentPath(imagePath, '.png'), true)
|
||||
|
||||
// NOTE
|
||||
// for some reason expo-sharing refuses to work on iOS
|
||||
// ...and visa versa
|
||||
// -prf
|
||||
if (isIOS) {
|
||||
await RNShare.share({url: imagePath})
|
||||
} else {
|
||||
await Sharing.shareAsync(imagePath, {
|
||||
mimeType: 'image/png',
|
||||
UTI: 'image/png',
|
||||
})
|
||||
// Usually whenever we share an image it will already be available in the cache. If it isn't, then we
|
||||
// will download it.
|
||||
let imageUri = await ExpoImage.getCachePathAsync(uri)
|
||||
if (!imageUri) {
|
||||
// NOTE
|
||||
// assuming PNG
|
||||
// we're currently relying on the fact our CDN only serves pngs
|
||||
// -prf
|
||||
imageUri = await downloadImage(uri, createPath('png'), 5e3)
|
||||
}
|
||||
RNFS.unlink(imagePath)
|
||||
|
||||
const imagePath = await moveToPermanentPath(imageUri, '.png')
|
||||
|
||||
await Sharing.shareAsync(imagePath, {
|
||||
mimeType: 'image/png',
|
||||
UTI: 'image/png',
|
||||
})
|
||||
|
||||
deleteAsync(imagePath)
|
||||
}
|
||||
|
||||
export async function saveImageToMediaLibrary({uri}: {uri: string}) {
|
||||
// download the file to cache
|
||||
// NOTE
|
||||
// assuming PNG
|
||||
// we're currently relying on the fact our CDN only serves pngs
|
||||
// -prf
|
||||
const downloadResponse = await RNFetchBlob.config({
|
||||
fileCache: true,
|
||||
}).fetch('GET', uri)
|
||||
let imagePath = downloadResponse.path()
|
||||
imagePath = normalizePath(await moveToPermanentPath(imagePath, '.png'), true)
|
||||
let imageUri = await ExpoImage.getCachePathAsync(uri)
|
||||
if (!imageUri) {
|
||||
// download the file to cache
|
||||
// NOTE
|
||||
// assuming PNG
|
||||
// we're currently relying on the fact our CDN only serves pngs
|
||||
// -prf
|
||||
imageUri = await downloadImage(uri, createPath('png'), 5e3)
|
||||
}
|
||||
|
||||
const imagePath = await moveToPermanentPath(imageUri, '.png')
|
||||
|
||||
// save
|
||||
await MediaLibrary.createAssetAsync(imagePath)
|
||||
|
||||
deleteAsync(imagePath)
|
||||
}
|
||||
|
||||
export function getImageDim(path: string): Promise<Dimensions> {
|
||||
@@ -147,27 +140,48 @@ interface DoResizeOpts {
|
||||
}
|
||||
|
||||
async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
|
||||
// This is a bit of a hack, but it lets us get the original size of the image. The old image manipulation library
|
||||
// allowed us to supply a max height/width and it would handle the resizing. With expo-image-manipulator, we have
|
||||
// to supply the exact size and width that we want to resize to instead. We will calculate that ourselves based on
|
||||
// the height/width results of this first manipulation
|
||||
const imageRes = await manipulateAsync(localUri, [], {
|
||||
format: SaveFormat.JPEG,
|
||||
})
|
||||
|
||||
const newDimensions = getResizedDimensions({
|
||||
width: imageRes.width,
|
||||
height: imageRes.height,
|
||||
})
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const quality = 100 - i * 10
|
||||
const resizeRes = await ImageResizer.createResizedImage(
|
||||
const quality = 0.9 - 0.1 * i
|
||||
const resizeRes = await manipulateAsync(
|
||||
localUri,
|
||||
opts.width,
|
||||
opts.height,
|
||||
'JPEG',
|
||||
quality,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{mode: opts.mode},
|
||||
[{resize: {height: newDimensions.height, width: newDimensions.width}}],
|
||||
{
|
||||
format: SaveFormat.JPEG,
|
||||
compress: quality,
|
||||
},
|
||||
)
|
||||
if (resizeRes.size < opts.maxSize) {
|
||||
|
||||
// @ts-ignore This is valid, `getInfoAsync` will always return a size. The type is wonky
|
||||
const info: FileInfo & {size: number} = await getInfoAsync(resizeRes.uri, {
|
||||
size: true,
|
||||
})
|
||||
|
||||
// We want to clean up every resize _except_ the final result. We'll clean that one up later when we're finished
|
||||
// with it
|
||||
if (info.size < opts.maxSize) {
|
||||
await deleteAsync(imageRes.uri)
|
||||
return {
|
||||
path: normalizePath(resizeRes.path),
|
||||
path: normalizePath(resizeRes.uri),
|
||||
mime: 'image/jpeg',
|
||||
size: resizeRes.size,
|
||||
size: info.size,
|
||||
width: resizeRes.width,
|
||||
height: resizeRes.height,
|
||||
}
|
||||
} else {
|
||||
await deleteAsync(resizeRes.uri)
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
@@ -182,12 +196,29 @@ async function moveToPermanentPath(path: string, ext = ''): Promise<string> {
|
||||
https://github.com/ivpusic/react-native-image-crop-picker/issues/1199
|
||||
*/
|
||||
const filename = uuid.v4()
|
||||
const destinationPath = joinPath(cacheDirectory ?? '', `${filename}${ext}`)
|
||||
|
||||
await copyAsync({
|
||||
from: normalizePath(path),
|
||||
to: destinationPath,
|
||||
})
|
||||
|
||||
// This is just to try and clean up whenever we can. We won't always be able to, so in cases where we can't
|
||||
// we just catch the error. We can't simply move some files though such as image caches, so we will copy then
|
||||
// and attempt to clean up the original file
|
||||
// Paths that are image caches shouldn't be removed. com.hackemist.SDImageCache is used by SDWebImage and
|
||||
// image_manager_disk_cache is used by Glide
|
||||
if (
|
||||
!path.includes('com.hackemist.SDImageCache') &&
|
||||
!path.includes('image_manager_disk_cache')
|
||||
) {
|
||||
try {
|
||||
deleteAsync(path)
|
||||
} catch (e) {
|
||||
// No need to handle
|
||||
}
|
||||
}
|
||||
|
||||
const destinationPath = joinPath(
|
||||
RNFS.TemporaryDirectoryPath,
|
||||
`${filename}${ext}`,
|
||||
)
|
||||
await RNFS.moveFile(path, destinationPath)
|
||||
return normalizePath(destinationPath)
|
||||
}
|
||||
|
||||
@@ -203,11 +234,53 @@ function joinPath(a: string, b: string) {
|
||||
return a + '/' + b
|
||||
}
|
||||
|
||||
function normalizePath(str: string, allPlatforms = false): string {
|
||||
if (isAndroid || allPlatforms) {
|
||||
if (!str.startsWith('file://')) {
|
||||
return `file://${str}`
|
||||
}
|
||||
function normalizePath(str: string): string {
|
||||
if (!str.startsWith('file://')) {
|
||||
return `file://${str}`
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
function createPath(ext: string) {
|
||||
// cacheDirectory will never be null on native, so the null check here is not necessary except for typescript.
|
||||
// we use a web-only function for downloadAndResize on web
|
||||
return `${cacheDirectory ?? ''}/${uuid.v4()}.${ext}`
|
||||
}
|
||||
|
||||
async function downloadImage(uri: string, path: string, timeout: number) {
|
||||
const downloadResumable = createDownloadResumable(uri, path, {
|
||||
cache: true,
|
||||
})
|
||||
|
||||
const to1 = setTimeout(() => downloadResumable.cancelAsync(), timeout)
|
||||
const downloadRes = await downloadResumable.downloadAsync()
|
||||
clearTimeout(to1)
|
||||
|
||||
if (!downloadRes?.uri) {
|
||||
throw new Error()
|
||||
}
|
||||
|
||||
return normalizePath(downloadRes.uri)
|
||||
}
|
||||
|
||||
export function getResizedDimensions(originalDims: {
|
||||
width: number
|
||||
height: number
|
||||
}) {
|
||||
if (
|
||||
originalDims.width <= POST_IMG_MAX.width &&
|
||||
originalDims.height <= POST_IMG_MAX.height
|
||||
) {
|
||||
return originalDims
|
||||
}
|
||||
|
||||
const ratio = Math.min(
|
||||
POST_IMG_MAX.width / originalDims.width,
|
||||
POST_IMG_MAX.height / originalDims.height,
|
||||
)
|
||||
|
||||
return {
|
||||
width: Math.round(originalDims.width * ratio),
|
||||
height: Math.round(originalDims.height * ratio),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,34 @@
|
||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||
import RNFS from 'react-native-fs'
|
||||
import {CropperOptions} from './types'
|
||||
import {compressIfNeeded} from './manip'
|
||||
import {
|
||||
documentDirectory,
|
||||
getInfoAsync,
|
||||
readDirectoryAsync,
|
||||
} from 'expo-file-system'
|
||||
|
||||
import {compressIfNeeded} from './manip'
|
||||
import {CropperOptions} from './types'
|
||||
|
||||
let _imageCounter = 0
|
||||
async function getFile() {
|
||||
let files = await RNFS.readDir(
|
||||
RNFS.LibraryDirectoryPath.split('/')
|
||||
// This *should* work. In RNFS, there was a LibraryDirectoryPath constant, which is not present in
|
||||
// expo-file-system. This should work as a work around at least on simulators (probably elsewhere
|
||||
// too though). Since this is only used for e2e though, this should be safe.
|
||||
const libraryDirPath = documentDirectory?.split('/data/')[0] + '/data'
|
||||
|
||||
let paths = await readDirectoryAsync(
|
||||
libraryDirPath
|
||||
.split('/')
|
||||
.slice(0, -5)
|
||||
.concat(['Media', 'DCIM', '100APPLE'])
|
||||
.join('/'),
|
||||
)
|
||||
files = files.filter(file => file.path.endsWith('.JPG'))
|
||||
const file = files[0]
|
||||
paths = paths.filter(path => path.endsWith('.JPG'))
|
||||
const path = paths[_imageCounter++ % paths.length]
|
||||
return await compressIfNeeded({
|
||||
path: file.path,
|
||||
path,
|
||||
mime: 'image/jpeg',
|
||||
size: file.size,
|
||||
// @ts-ignore Size is available here, type is incorrect
|
||||
size: (await getInfoAsync(path, {size: true})).size ?? 0,
|
||||
width: 4288,
|
||||
height: 2848,
|
||||
})
|
||||
|
||||
@@ -11860,16 +11860,16 @@ expo-eas-client@~0.11.0:
|
||||
resolved "https://registry.yarnpkg.com/expo-eas-client/-/expo-eas-client-0.11.0.tgz#0f25aa497849cade7ebef55c0631093a87e58b07"
|
||||
integrity sha512-99W0MUGe3U4/MY1E9UeJ4uKNI39mN8/sOGA0Le8XC47MTbwbLoVegHR3C5y2fXLwLn7EpfNxAn5nlxYjY3gD2A==
|
||||
|
||||
expo-file-system@^16.0.8, expo-file-system@~16.0.8:
|
||||
version "16.0.8"
|
||||
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-16.0.8.tgz#13c79a8e06e42a8e76e9297df6920597a011d989"
|
||||
integrity sha512-yDbVT0TUKd7ewQjaY5THum2VRFx2n/biskGhkUmLh3ai21xjIVtaeIzHXyv9ir537eVgt4ReqDNWi7jcXjdUcA==
|
||||
|
||||
expo-file-system@~16.0.0:
|
||||
version "16.0.1"
|
||||
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-16.0.1.tgz#326b7c2f6e53e1a0eaafc9769578aafb3f9c9f43"
|
||||
integrity sha512-/U6ufN2wRPgg4m2a9sqbL3dThqQsysT022qulEXWnUTmNaqnzYSk9ihjDWqoqjXLi9slQLsyok5t6CNzhM7HPw==
|
||||
|
||||
expo-file-system@~16.0.8:
|
||||
version "16.0.8"
|
||||
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-16.0.8.tgz#13c79a8e06e42a8e76e9297df6920597a011d989"
|
||||
integrity sha512-yDbVT0TUKd7ewQjaY5THum2VRFx2n/biskGhkUmLh3ai21xjIVtaeIzHXyv9ir537eVgt4ReqDNWi7jcXjdUcA==
|
||||
|
||||
expo-font@~11.10.3:
|
||||
version "11.10.3"
|
||||
resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-11.10.3.tgz#a3115ebda8e09bd7cb8052619a4bbe606f0c17f4"
|
||||
|
||||
Reference in New Issue
Block a user