diff options
Diffstat (limited to 'src/lib/media')
-rw-r--r-- | src/lib/media/manip.ts | 25 | ||||
-rw-r--r-- | src/lib/media/manip.web.ts | 14 | ||||
-rw-r--r-- | src/lib/media/picker.e2e.tsx | 17 | ||||
-rw-r--r-- | src/lib/media/picker.shared.ts | 11 | ||||
-rw-r--r-- | src/lib/media/picker.tsx | 48 | ||||
-rw-r--r-- | src/lib/media/picker.web.tsx | 26 | ||||
-rw-r--r-- | src/lib/media/types.ts | 7 |
7 files changed, 77 insertions, 71 deletions
diff --git a/src/lib/media/manip.ts b/src/lib/media/manip.ts index f6ef8347d..ff5b71ace 100644 --- a/src/lib/media/manip.ts +++ b/src/lib/media/manip.ts @@ -1,5 +1,4 @@ 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, @@ -20,17 +19,17 @@ import RNFetchBlob from 'rn-fetch-blob' import {POST_IMG_MAX} from '#/lib/constants' import {logger} from '#/logger' import {isAndroid, isIOS} from '#/platform/detection' -import {Dimensions} from './types' +import {type PickerImage} from './picker.shared' +import {type Dimensions} from './types' export async function compressIfNeeded( - img: Image, + img: PickerImage, maxSize: number = 1000000, -): Promise<Image> { - const origUri = `file://${img.path}` +): Promise<PickerImage> { if (img.size < maxSize) { return img } - const resizedImage = await doResize(origUri, { + const resizedImage = await doResize(normalizePath(img.path), { width: img.width, height: img.height, mode: 'stretch', @@ -166,7 +165,10 @@ interface DoResizeOpts { maxSize: number } -async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> { +async function doResize( + localUri: string, + opts: DoResizeOpts, +): Promise<PickerImage> { // We need to get the dimensions of the image before we resize it. Previously, the library we used allowed us to enter // a "max size", and it would do the "best possible size" calculation for us. // Now instead, we have to supply the final dimensions to the manipulation function instead. @@ -181,6 +183,7 @@ async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> { let minQualityPercentage = 0 let maxQualityPercentage = 101 // exclusive let newDataUri + const intermediateUris = [] while (maxQualityPercentage - minQualityPercentage > 1) { const qualityPercentage = Math.round( @@ -195,6 +198,8 @@ async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> { }, ) + intermediateUris.push(resizeRes.uri) + const fileInfo = await getInfoAsync(resizeRes.uri) if (!fileInfo.exists) { throw new Error( @@ -214,8 +219,12 @@ async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> { } else { maxQualityPercentage = qualityPercentage } + } - safeDeleteAsync(resizeRes.uri) + for (const intermediateUri of intermediateUris) { + if (newDataUri?.path !== normalizePath(intermediateUri)) { + safeDeleteAsync(intermediateUri) + } } if (newDataUri) { diff --git a/src/lib/media/manip.web.ts b/src/lib/media/manip.web.ts index ffef7314d..ffcf0c533 100644 --- a/src/lib/media/manip.web.ts +++ b/src/lib/media/manip.web.ts @@ -1,12 +1,11 @@ -import {Image as RNImage} from 'react-native-image-crop-picker' - -import {Dimensions} from './types' +import {type PickerImage} from './picker.shared' +import {type Dimensions} from './types' import {blobToDataUri, getDataUriSize} from './util' export async function compressIfNeeded( - img: RNImage, + img: PickerImage, maxSize: number, -): Promise<RNImage> { +): Promise<PickerImage> { if (img.size < maxSize) { return img } @@ -69,7 +68,10 @@ interface DoResizeOpts { maxSize: number } -async function doResize(dataUri: string, opts: DoResizeOpts): Promise<RNImage> { +async function doResize( + dataUri: string, + opts: DoResizeOpts, +): Promise<PickerImage> { let newDataUri let minQualityPercentage = 0 diff --git a/src/lib/media/picker.e2e.tsx b/src/lib/media/picker.e2e.tsx index fc6fcde45..a2a9357ec 100644 --- a/src/lib/media/picker.e2e.tsx +++ b/src/lib/media/picker.e2e.tsx @@ -1,15 +1,12 @@ import { - Image as RNImage, - openCropper as openCropperFn, -} from 'react-native-image-crop-picker' -import { documentDirectory, getInfoAsync, readDirectoryAsync, } from 'expo-file-system' +import ExpoImageCropTool, {type OpenCropperOptions} from 'expo-image-crop-tool' import {compressIfNeeded} from './manip' -import {CropperOptions} from './types' +import {type PickerImage} from './picker.shared' async function getFile() { const imagesDir = documentDirectory! @@ -37,18 +34,18 @@ async function getFile() { }) } -export async function openPicker(): Promise<RNImage[]> { +export async function openPicker(): Promise<PickerImage[]> { return [await getFile()] } -export async function openCamera(): Promise<RNImage> { +export async function openCamera(): Promise<PickerImage> { return await getFile() } -export async function openCropper(opts: CropperOptions) { - const item = await openCropperFn({ +export async function openCropper(opts: OpenCropperOptions) { + const item = await ExpoImageCropTool.openCropperAsync({ ...opts, - forceJpg: true, // ios only + format: 'jpeg', }) return { diff --git a/src/lib/media/picker.shared.ts b/src/lib/media/picker.shared.ts index a45bf5c0f..21e680832 100644 --- a/src/lib/media/picker.shared.ts +++ b/src/lib/media/picker.shared.ts @@ -1,14 +1,21 @@ import { - ImagePickerOptions, + type ImagePickerOptions, launchImageLibraryAsync, MediaTypeOptions, } from 'expo-image-picker' -// TODO: replace global i18n instance with one returned from useLingui -sfn import {t} from '@lingui/macro' import * as Toast from '#/view/com/util/Toast' import {getDataUriSize} from './util' +export type PickerImage = { + mime: string + height: number + width: number + path: string + size: number +} + export async function openPicker(opts?: ImagePickerOptions) { const response = await launchImageLibraryAsync({ exif: false, diff --git a/src/lib/media/picker.tsx b/src/lib/media/picker.tsx index 37e01e67f..6095730d5 100644 --- a/src/lib/media/picker.tsx +++ b/src/lib/media/picker.tsx @@ -1,36 +1,34 @@ -import { - Image as RNImage, - openCamera as openCameraFn, - openCropper as openCropperFn, -} from 'react-native-image-crop-picker' +import ExpoImageCropTool, {type OpenCropperOptions} from 'expo-image-crop-tool' +import {type ImagePickerOptions, launchCameraAsync} from 'expo-image-picker' -import {CameraOpts, CropperOptions} from './types' -export {openPicker} from './picker.shared' +export {openPicker, type PickerImage as RNImage} from './picker.shared' -export async function openCamera(opts: CameraOpts): Promise<RNImage> { - const item = await openCameraFn({ - width: opts.width, - height: opts.height, - freeStyleCropEnabled: opts.freeStyleCropEnabled, - cropperCircleOverlay: opts.cropperCircleOverlay, - cropping: false, - forceJpg: true, // ios only - compressImageQuality: 0.8, - }) +export async function openCamera(customOpts: ImagePickerOptions) { + const opts: ImagePickerOptions = { + mediaTypes: 'images', + ...customOpts, + } + const res = await launchCameraAsync(opts) + + if (!res || !res.assets) { + throw new Error('Camera was closed before taking a photo') + } + + const asset = res?.assets[0] return { - path: item.path, - mime: item.mime, - size: item.size, - width: item.width, - height: item.height, + path: asset.uri, + mime: asset.mimeType ?? 'image/jpeg', + size: asset.fileSize ?? 0, + width: asset.width, + height: asset.height, } } -export async function openCropper(opts: CropperOptions) { - const item = await openCropperFn({ +export async function openCropper(opts: OpenCropperOptions) { + const item = await ExpoImageCropTool.openCropperAsync({ ...opts, - forceJpg: true, // ios only + format: 'jpeg', }) return { diff --git a/src/lib/media/picker.web.tsx b/src/lib/media/picker.web.tsx index a53ffc961..b7d0d6f06 100644 --- a/src/lib/media/picker.web.tsx +++ b/src/lib/media/picker.web.tsx @@ -1,29 +1,29 @@ /// <reference lib="dom" /> -import {Image as RNImage} from 'react-native-image-crop-picker' +import {type OpenCropperOptions} from 'expo-image-crop-tool' -import {CameraOpts, CropperOptions} from './types' -export {openPicker} from './picker.shared' import {unstable__openModal} from '#/state/modals' +import {type PickerImage} from './picker.shared' +import {type CameraOpts} from './types' -export async function openCamera(_opts: CameraOpts): Promise<RNImage> { +export {openPicker, type PickerImage as RNImage} from './picker.shared' + +export async function openCamera(_opts: CameraOpts): Promise<PickerImage> { // const mediaType = opts.mediaType || 'photo' TODO throw new Error('TODO') } -export async function openCropper(opts: CropperOptions): Promise<RNImage> { +export async function openCropper( + opts: OpenCropperOptions, +): Promise<PickerImage> { // TODO handle more opts return new Promise((resolve, reject) => { unstable__openModal({ name: 'crop-image', - uri: opts.path, - dimensions: - opts.width && opts.height - ? {width: opts.width, height: opts.height} - : undefined, - aspect: opts.webAspectRatio, - circular: opts.webCircularCrop, - onSelect: (img?: RNImage) => { + uri: opts.imageUri, + aspect: opts.aspectRatio, + circular: opts.shape === 'circle', + onSelect: (img?: PickerImage) => { if (img) { resolve(img) } else { diff --git a/src/lib/media/types.ts b/src/lib/media/types.ts index ec94256ea..c083093ac 100644 --- a/src/lib/media/types.ts +++ b/src/lib/media/types.ts @@ -1,5 +1,3 @@ -import {openCropper} from 'react-native-image-crop-picker' - export interface Dimensions { width: number height: number @@ -17,8 +15,3 @@ export interface CameraOpts { freeStyleCropEnabled?: boolean cropperCircleOverlay?: boolean } - -export type CropperOptions = Parameters<typeof openCropper>[0] & { - webAspectRatio?: number - webCircularCrop?: boolean -} |