diff --git a/package.json b/package.json index a3b0bc37..5c5f3057 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "expo-device": "~5.2.1", "expo-image": "^1.2.1", "expo-image-manipulator": "^11.1.1", - "expo-image-picker": "~14.1.1", + "expo-image-picker": "^14.1.1", "expo-localization": "~14.1.1", "expo-media-library": "~15.2.3", "expo-sharing": "~11.2.2", diff --git a/src/lib/media/picker.tsx b/src/lib/media/picker.tsx index af4a3e4d..7c6dfcc4 100644 --- a/src/lib/media/picker.tsx +++ b/src/lib/media/picker.tsx @@ -1,12 +1,16 @@ import { - openPicker as openPickerFn, openCamera as openCameraFn, openCropper as openCropperFn, - ImageOrVideo, + Image as RNImage, } from 'react-native-image-crop-picker' import {RootStoreModel} from 'state/index' -import {PickerOpts, CameraOpts, CropperOptions} from './types' -import {Image as RNImage} from 'react-native-image-crop-picker' +import {CameraOpts, CropperOptions} from './types' +import { + ImagePickerOptions, + launchImageLibraryAsync, + MediaTypeOptions, +} from 'expo-image-picker' +import {getDataUriSize} from './util' /** * NOTE @@ -19,27 +23,22 @@ import {Image as RNImage} from 'react-native-image-crop-picker' export async function openPicker( _store: RootStoreModel, - opts?: PickerOpts, -): Promise { - const items = await openPickerFn({ - mediaType: 'photo', // TODO: eventually add other media types - multiple: opts?.multiple, - maxFiles: opts?.maxFiles, - forceJpg: true, // ios only - compressImageQuality: 0.8, + opts?: ImagePickerOptions, +) { + const response = await launchImageLibraryAsync({ + exif: false, + mediaTypes: MediaTypeOptions.Images, + quality: 1, + ...opts, }) - const toMedia = (item: ImageOrVideo) => ({ - path: item.path, - mime: item.mime, - size: item.size, - width: item.width, - height: item.height, - }) - if (Array.isArray(items)) { - return items.map(toMedia) - } - return [toMedia(items)] + return (response.assets ?? []).map(image => ({ + mime: 'image/jpeg', + height: image.height, + width: image.width, + path: image.uri, + size: getDataUriSize(image.uri), + })) } export async function openCamera( @@ -55,6 +54,7 @@ export async function openCamera( forceJpg: true, // ios only compressImageQuality: 0.8, }) + return { path: item.path, mime: item.mime, @@ -67,11 +67,10 @@ export async function openCamera( export async function openCropper( _store: RootStoreModel, opts: CropperOptions, -): Promise { +) { const item = await openCropperFn({ ...opts, forceJpg: true, // ios only - compressImageQuality: 0.8, }) return { diff --git a/src/state/models/media/gallery.ts b/src/state/models/media/gallery.ts index 89f0d012..67f8d2ea 100644 --- a/src/state/models/media/gallery.ts +++ b/src/state/models/media/gallery.ts @@ -102,10 +102,14 @@ export class GalleryModel { async pick() { const images = await openPicker(this.rootStore, { - multiple: true, - maxFiles: 4 - this.images.length, + selectionLimit: 4 - this.size, + allowsMultipleSelection: true, }) - await Promise.all(images.map(image => this.add(image))) + return await Promise.all( + images.map(image => { + this.add(image) + }), + ) } } diff --git a/src/state/models/media/image.ts b/src/state/models/media/image.ts index 6edf88d9..ec93bf5b 100644 --- a/src/state/models/media/image.ts +++ b/src/state/models/media/image.ts @@ -135,7 +135,7 @@ export class ImageModel implements RNImage { // Only for mobile async crop() { try { - const cropped = await openCropper(this.rootStore, { + const cropped = await openCropper({ mediaType: 'photo', path: this.path, freeStyleCropEnabled: true, diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx index c26592fa..f37a0f71 100644 --- a/src/view/com/modals/EditProfile.tsx +++ b/src/view/com/modals/EditProfile.tsx @@ -65,7 +65,7 @@ export function Component({ } const onSelectNewAvatar = useCallback( async (img: RNImage | null) => { - if (!img) { + if (img === null) { setNewUserAvatar(null) setUserAvatar(null) return @@ -81,6 +81,7 @@ export function Component({ }, [track, setNewUserAvatar, setUserAvatar, setError], ) + const onSelectNewBanner = useCallback( async (img: RNImage | null) => { if (!img) { @@ -99,6 +100,7 @@ export function Component({ }, [track, setNewUserBanner, setUserBanner, setError], ) + const onPressSave = useCallback(async () => { track('EditProfile:Save') setProcessing(true) diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index a2e607e4..f3679326 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -66,6 +66,7 @@ export function UserAvatar({ if (!(await requestCameraAccessIfNeeded())) { return } + onSelectNewAvatar?.( await openCamera(store, { width: 1000, @@ -83,20 +84,21 @@ export function UserAvatar({ if (!(await requestPhotoAccessIfNeeded())) { return } + const items = await openPicker(store, { + aspect: [1, 1], + }) + const item = items[0] + + const croppedImage = await openCropper(store, { mediaType: 'photo', - multiple: false, + cropperCircleOverlay: true, + height: item.height, + width: item.width, + path: item.path, }) - onSelectNewAvatar?.( - await openCropper(store, { - mediaType: 'photo', - path: items[0].path, - width: 1000, - height: 1000, - cropperCircleOverlay: true, - }), - ) + onSelectNewAvatar?.(croppedImage) }, }, { diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx index 51cfbccb..6e08be50 100644 --- a/src/view/com/util/UserBanner.tsx +++ b/src/view/com/util/UserBanner.tsx @@ -55,10 +55,8 @@ export function UserBanner({ if (!(await requestPhotoAccessIfNeeded())) { return } - const items = await openPicker(store, { - mediaType: 'photo', - multiple: false, - }) + const items = await openPicker(store) + onSelectNewBanner?.( await openCropper(store, { mediaType: 'photo', diff --git a/yarn.lock b/yarn.lock index 9a2e64fe..54006d89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8731,7 +8731,7 @@ expo-image-manipulator@^11.1.1: dependencies: expo-image-loader "~4.1.0" -expo-image-picker@~14.1.1: +expo-image-picker@^14.1.1: version "14.1.1" resolved "https://registry.yarnpkg.com/expo-image-picker/-/expo-image-picker-14.1.1.tgz#181f1348ba6a43df7b87cee4a601d45c79b7c2d7" integrity sha512-SvWtnkLW7jp5Ntvk3lVcRQmhFYja8psmiR7O6P/+7S6f4llt3vaFwb4I3+pUXqJxxpi7BHc2+95qOLf0SFOIag==