From ae26963b45082b842803076a5b55532031f9f69a Mon Sep 17 00:00:00 2001 From: Piotr P Date: Sat, 27 Apr 2024 14:21:13 +0200 Subject: [PATCH 1/6] eslint autofixes --- src/lib/media/picker.tsx | 3 ++- src/lib/media/picker.web.tsx | 3 ++- src/view/com/util/UserBanner.tsx | 22 +++++++++++----------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/lib/media/picker.tsx b/src/lib/media/picker.tsx index bf531c98..37e01e67 100644 --- a/src/lib/media/picker.tsx +++ b/src/lib/media/picker.tsx @@ -1,8 +1,9 @@ import { + Image as RNImage, openCamera as openCameraFn, openCropper as openCropperFn, - Image as RNImage, } from 'react-native-image-crop-picker' + import {CameraOpts, CropperOptions} from './types' export {openPicker} from './picker.shared' diff --git a/src/lib/media/picker.web.tsx b/src/lib/media/picker.web.tsx index 995a0c95..7e2562e2 100644 --- a/src/lib/media/picker.web.tsx +++ b/src/lib/media/picker.web.tsx @@ -1,7 +1,8 @@ /// -import {CameraOpts, CropperOptions} from './types' import {Image as RNImage} from 'react-native-image-crop-picker' + +import {CameraOpts, CropperOptions} from './types' export {openPicker} from './picker.shared' import {unstable__openModal} from '#/state/modals' diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx index 4d73b853..f08044ec 100644 --- a/src/view/com/util/UserBanner.tsx +++ b/src/view/com/util/UserBanner.tsx @@ -1,29 +1,29 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {ModerationUI} from '@atproto/api' +import {Image as RNImage} from 'react-native-image-crop-picker' import {Image} from 'expo-image' -import {useLingui} from '@lingui/react' +import {ModerationUI} from '@atproto/api' import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {usePalette} from 'lib/hooks/usePalette' +import { + useCameraPermission, + usePhotoLibraryPermission, +} from 'lib/hooks/usePermissions' import {colors} from 'lib/styles' import {useTheme} from 'lib/ThemeContext' -import {useTheme as useAlfTheme, tokens} from '#/alf' -import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' -import { - usePhotoLibraryPermission, - useCameraPermission, -} from 'lib/hooks/usePermissions' -import {usePalette} from 'lib/hooks/usePalette' import {isAndroid, isNative} from 'platform/detection' -import {Image as RNImage} from 'react-native-image-crop-picker' import {EventStopper} from 'view/com/util/EventStopper' -import * as Menu from '#/components/Menu' +import {tokens, useTheme as useAlfTheme} from '#/alf' import { Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled, Camera_Stroke2_Corner0_Rounded as Camera, } from '#/components/icons/Camera' import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' +import * as Menu from '#/components/Menu' +import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' export function UserBanner({ type, From ebd333b331d7103322d4e0a1c6f5035c0b22bdd9 Mon Sep 17 00:00:00 2001 From: Piotr P Date: Sat, 27 Apr 2024 14:21:52 +0200 Subject: [PATCH 2/6] Fix cropper crashing after clicking cancel --- src/lib/media/picker.web.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/media/picker.web.tsx b/src/lib/media/picker.web.tsx index 7e2562e2..70e28838 100644 --- a/src/lib/media/picker.web.tsx +++ b/src/lib/media/picker.web.tsx @@ -13,15 +13,13 @@ export async function openCamera(_opts: CameraOpts): Promise { export async function openCropper(opts: CropperOptions): Promise { // TODO handle more opts - return new Promise((resolve, reject) => { + return new Promise(resolve => { unstable__openModal({ name: 'crop-image', uri: opts.path, onSelect: (img?: RNImage) => { if (img) { resolve(img) - } else { - reject(new Error('Canceled')) } }, }) From bc956803b800715ae2ebbc2105b44c9dc335863b Mon Sep 17 00:00:00 2001 From: Piotr P Date: Sat, 27 Apr 2024 14:23:11 +0200 Subject: [PATCH 3/6] allow for custom cropper aspect ration based on image --- src/lib/media/picker.web.tsx | 4 +++ src/state/modals/index.tsx | 1 + .../com/modals/crop-image/CropImage.web.tsx | 30 +++++++++++++++---- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/lib/media/picker.web.tsx b/src/lib/media/picker.web.tsx index 70e28838..fde6a64a 100644 --- a/src/lib/media/picker.web.tsx +++ b/src/lib/media/picker.web.tsx @@ -17,6 +17,10 @@ export async function openCropper(opts: CropperOptions): Promise { unstable__openModal({ name: 'crop-image', uri: opts.path, + dimensions: + opts.height && opts.width + ? {width: opts.width, height: opts.height} + : undefined, onSelect: (img?: RNImage) => { if (img) { resolve(img) diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx index 0f61a971..cf82bcd0 100644 --- a/src/state/modals/index.tsx +++ b/src/state/modals/index.tsx @@ -47,6 +47,7 @@ export interface EditImageModal { export interface CropImageModal { name: 'crop-image' uri: string + dimensions?: {width: number; height: number} onSelect: (img?: RNImage) => void } diff --git a/src/view/com/modals/crop-image/CropImage.web.tsx b/src/view/com/modals/crop-image/CropImage.web.tsx index 79ff5a02..a359ed44 100644 --- a/src/view/com/modals/crop-image/CropImage.web.tsx +++ b/src/view/com/modals/crop-image/CropImage.web.tsx @@ -14,11 +14,13 @@ import {Dimensions} from 'lib/media/types' import {getDataUriSize} from 'lib/media/util' import {gradients, s} from 'lib/styles' import {Text} from 'view/com/util/text/Text' +import {calculateDimensions} from './cropImageUtil' enum AspectRatio { Square = 'square', Wide = 'wide', Tall = 'tall', + Custom = 'custom', } const DIMS: Record = { @@ -31,17 +33,24 @@ export const snapPoints = ['0%'] export function Component({ uri, + dimensions, onSelect, }: { uri: string + dimensions?: Dimensions onSelect: (img?: RNImage) => void }) { const {closeModal} = useModalControls() const pal = usePalette('default') const {_} = useLingui() - const [as, setAs] = React.useState(AspectRatio.Square) + const defaultAspectStyle = dimensions + ? AspectRatio.Custom + : AspectRatio.Square + const [as, setAs] = React.useState(defaultAspectStyle) const [scale, setScale] = React.useState(1) const editorRef = React.useRef(null) + const imageEditorWidth = dimensions ? dimensions.width : DIMS[as].width + const imageEditorHeight = dimensions ? dimensions.height : DIMS[as].height const doSetAs = (v: AspectRatio) => () => setAs(v) @@ -57,8 +66,8 @@ export function Component({ path: dataUri, mime: 'image/jpeg', size: getDataUriSize(dataUri), - width: DIMS[as].width, - height: DIMS[as].height, + width: imageEditorWidth, + height: imageEditorHeight, }) } else { onSelect(undefined) @@ -73,7 +82,18 @@ export function Component({ cropperStyle = styles.cropperWide } else if (as === AspectRatio.Tall) { cropperStyle = styles.cropperTall + } else if (as === AspectRatio.Custom) { + const cropperDimensions = calculateDimensions( + 550, + imageEditorHeight, + imageEditorWidth, + ) + cropperStyle = { + width: cropperDimensions.width, + height: cropperDimensions.height, + } } + return ( @@ -81,8 +101,8 @@ export function Component({ ref={editorRef} style={styles.imageEditor} image={uri} - width={DIMS[as].width} - height={DIMS[as].height} + width={imageEditorWidth} + height={imageEditorHeight} scale={scale} border={0} /> From beddddcb579b7bd0a6f4d7d2a014a92a5399f3c6 Mon Sep 17 00:00:00 2001 From: Piotr P Date: Sat, 27 Apr 2024 14:23:29 +0200 Subject: [PATCH 4/6] hide alternative ratio buttons when using custom ratio --- .../com/modals/crop-image/CropImage.web.tsx | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/src/view/com/modals/crop-image/CropImage.web.tsx b/src/view/com/modals/crop-image/CropImage.web.tsx index a359ed44..10cae2f1 100644 --- a/src/view/com/modals/crop-image/CropImage.web.tsx +++ b/src/view/com/modals/crop-image/CropImage.web.tsx @@ -117,36 +117,40 @@ export function Component({ maximumValue={3} containerStyle={styles.slider} /> - - - - - - - - - + {as === AspectRatio.Custom ? null : ( + <> + + + + + + + + + + + )} Date: Sat, 27 Apr 2024 14:59:00 +0200 Subject: [PATCH 5/6] add missing cropImageUtil file --- src/view/com/modals/crop-image/cropImageUtil.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/view/com/modals/crop-image/cropImageUtil.ts diff --git a/src/view/com/modals/crop-image/cropImageUtil.ts b/src/view/com/modals/crop-image/cropImageUtil.ts new file mode 100644 index 00000000..303d15ba --- /dev/null +++ b/src/view/com/modals/crop-image/cropImageUtil.ts @@ -0,0 +1,13 @@ +export const calculateDimensions = ( + maxWidth: number, + originalHeight: number, + originalWidth: number, +) => { + const aspectRatio = originalWidth / originalHeight + const newHeight = maxWidth / aspectRatio + const newWidth = maxWidth + return { + width: newWidth, + height: newHeight, + } +} From fe82257801f49b3d7aceca9dd611ef4005da48ce Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Mon, 6 May 2024 15:44:19 -0700 Subject: [PATCH 6/6] Dont leave promise unresolved --- src/lib/media/picker.web.tsx | 4 +++- src/view/com/util/UserAvatar.tsx | 23 +++++++++++++++-------- src/view/com/util/UserBanner.tsx | 23 +++++++++++++++-------- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/lib/media/picker.web.tsx b/src/lib/media/picker.web.tsx index fde6a64a..8782e145 100644 --- a/src/lib/media/picker.web.tsx +++ b/src/lib/media/picker.web.tsx @@ -13,7 +13,7 @@ export async function openCamera(_opts: CameraOpts): Promise { export async function openCropper(opts: CropperOptions): Promise { // TODO handle more opts - return new Promise(resolve => { + return new Promise((resolve, reject) => { unstable__openModal({ name: 'crop-image', uri: opts.path, @@ -24,6 +24,8 @@ export async function openCropper(opts: CropperOptions): Promise { onSelect: (img?: RNImage) => { if (img) { resolve(img) + } else { + reject(new Error('Canceled')) } }, }) diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index 118e2ce2..45327669 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -8,6 +8,7 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' +import {logger} from '#/logger' import {usePalette} from 'lib/hooks/usePalette' import { useCameraPermission, @@ -282,15 +283,21 @@ let EditableUserAvatar = ({ return } - const croppedImage = await openCropper({ - mediaType: 'photo', - cropperCircleOverlay: true, - height: item.height, - width: item.width, - path: item.path, - }) + try { + const croppedImage = await openCropper({ + mediaType: 'photo', + cropperCircleOverlay: true, + height: item.height, + width: item.width, + path: item.path, + }) - onSelectNewAvatar(croppedImage) + onSelectNewAvatar(croppedImage) + } catch (e: any) { + if (!String(e).includes('Canceled')) { + logger.error('Failed to crop banner', {error: e}) + } + } }, [onSelectNewAvatar, requestPhotoAccessIfNeeded]) const onRemoveAvatar = React.useCallback(() => { diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx index f08044ec..93ea3275 100644 --- a/src/view/com/util/UserBanner.tsx +++ b/src/view/com/util/UserBanner.tsx @@ -6,6 +6,7 @@ import {ModerationUI} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {logger} from '#/logger' import {usePalette} from 'lib/hooks/usePalette' import { useCameraPermission, @@ -64,14 +65,20 @@ export function UserBanner({ return } - onSelectNewBanner?.( - await openCropper({ - mediaType: 'photo', - path: items[0].path, - width: 3000, - height: 1000, - }), - ) + try { + onSelectNewBanner?.( + await openCropper({ + mediaType: 'photo', + path: items[0].path, + width: 3000, + height: 1000, + }), + ) + } catch (e: any) { + if (!String(e).includes('Canceled')) { + logger.error('Failed to crop banner', {error: e}) + } + } }, [onSelectNewBanner, requestPhotoAccessIfNeeded]) const onRemoveBanner = React.useCallback(() => {