diff options
Diffstat (limited to 'src/view/com/composer/photos')
-rw-r--r-- | src/view/com/composer/photos/Gallery.tsx | 130 | ||||
-rw-r--r-- | src/view/com/composer/photos/OpenCameraBtn.tsx | 58 | ||||
-rw-r--r-- | src/view/com/composer/photos/OpenCameraBtn.web.tsx | 3 | ||||
-rw-r--r-- | src/view/com/composer/photos/SelectPhotoBtn.tsx | 84 | ||||
-rw-r--r-- | src/view/com/composer/photos/SelectedPhotos.tsx | 96 |
5 files changed, 166 insertions, 205 deletions
diff --git a/src/view/com/composer/photos/Gallery.tsx b/src/view/com/composer/photos/Gallery.tsx new file mode 100644 index 000000000..f4dfc88fa --- /dev/null +++ b/src/view/com/composer/photos/Gallery.tsx @@ -0,0 +1,130 @@ +import React, {useCallback} from 'react' +import {GalleryModel} from 'state/models/media/gallery' +import {observer} from 'mobx-react-lite' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {colors} from 'lib/styles' +import {StyleSheet, TouchableOpacity, View} from 'react-native' +import {ImageModel} from 'state/models/media/image' +import {Image} from 'expo-image' + +interface Props { + gallery: GalleryModel +} + +export const Gallery = observer(function ({gallery}: Props) { + const getImageStyle = useCallback(() => { + switch (gallery.size) { + case 1: + return styles.image250 + case 2: + return styles.image175 + default: + return styles.image85 + } + }, [gallery]) + + const imageStyle = getImageStyle() + const handleRemovePhoto = useCallback( + (image: ImageModel) => { + gallery.remove(image) + }, + [gallery], + ) + + const handleEditPhoto = useCallback( + (image: ImageModel) => { + gallery.crop(image) + }, + [gallery], + ) + + return !gallery.isEmpty ? ( + <View testID="selectedPhotosView" style={styles.gallery}> + {gallery.images.map(image => + image.compressed !== undefined ? ( + <View + key={`selected-image-${image.path}`} + style={[styles.imageContainer, imageStyle]}> + <View style={styles.imageControls}> + <TouchableOpacity + testID="cropPhotoButton" + onPress={() => { + handleEditPhoto(image) + }} + style={styles.imageControl}> + <FontAwesomeIcon + icon="pen" + size={12} + style={{color: colors.white}} + /> + </TouchableOpacity> + <TouchableOpacity + testID="removePhotoButton" + onPress={() => handleRemovePhoto(image)} + style={styles.imageControl}> + <FontAwesomeIcon + icon="xmark" + size={16} + style={{color: colors.white}} + /> + </TouchableOpacity> + </View> + + <Image + testID="selectedPhotoImage" + style={[styles.image, imageStyle]} + source={{ + uri: image.compressed.path, + }} + /> + </View> + ) : null, + )} + </View> + ) : null +}) + +const styles = StyleSheet.create({ + gallery: { + flex: 1, + flexDirection: 'row', + marginTop: 16, + }, + imageContainer: { + margin: 2, + }, + image: { + resizeMode: 'cover', + borderRadius: 8, + }, + image250: { + width: 250, + height: 250, + }, + image175: { + width: 175, + height: 175, + }, + image85: { + width: 85, + height: 85, + }, + imageControls: { + position: 'absolute', + display: 'flex', + flexDirection: 'row', + gap: 4, + top: 8, + right: 8, + zIndex: 1, + }, + imageControl: { + width: 24, + height: 24, + borderRadius: 12, + backgroundColor: 'rgba(0, 0, 0, 0.75)', + borderWidth: 0.5, + alignItems: 'center', + justifyContent: 'center', + }, +}) diff --git a/src/view/com/composer/photos/OpenCameraBtn.tsx b/src/view/com/composer/photos/OpenCameraBtn.tsx index 118728781..809c41783 100644 --- a/src/view/com/composer/photos/OpenCameraBtn.tsx +++ b/src/view/com/composer/photos/OpenCameraBtn.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, {useCallback} from 'react' import {TouchableOpacity} from 'react-native' import { FontAwesomeIcon, @@ -10,62 +10,44 @@ import {useStores} from 'state/index' import {s} from 'lib/styles' import {isDesktopWeb} from 'platform/detection' import {openCamera} from 'lib/media/picker' -import {compressIfNeeded} from 'lib/media/manip' import {useCameraPermission} from 'lib/hooks/usePermissions' -import { - POST_IMG_MAX_WIDTH, - POST_IMG_MAX_HEIGHT, - POST_IMG_MAX_SIZE, -} from 'lib/constants' +import {POST_IMG_MAX} from 'lib/constants' +import {GalleryModel} from 'state/models/media/gallery' const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10} -export function OpenCameraBtn({ - enabled, - selectedPhotos, - onSelectPhotos, -}: { - enabled: boolean - selectedPhotos: string[] - onSelectPhotos: (v: string[]) => void -}) { +type Props = { + gallery: GalleryModel +} + +export function OpenCameraBtn({gallery}: Props) { const pal = usePalette('default') const {track} = useAnalytics() const store = useStores() const {requestCameraAccessIfNeeded} = useCameraPermission() - const onPressTakePicture = React.useCallback(async () => { + const onPressTakePicture = useCallback(async () => { track('Composer:CameraOpened') - if (!enabled) { - return - } try { if (!(await requestCameraAccessIfNeeded())) { return } - const cameraRes = await openCamera(store, { - mediaType: 'photo', - width: POST_IMG_MAX_WIDTH, - height: POST_IMG_MAX_HEIGHT, + + const img = await openCamera(store, { + width: POST_IMG_MAX.width, + height: POST_IMG_MAX.height, freeStyleCropEnabled: true, }) - const img = await compressIfNeeded(cameraRes, POST_IMG_MAX_SIZE) - onSelectPhotos([...selectedPhotos, img.path]) + + gallery.add(img) } catch (err: any) { // ignore store.log.warn('Error using camera', err) } - }, [ - track, - store, - onSelectPhotos, - selectedPhotos, - enabled, - requestCameraAccessIfNeeded, - ]) + }, [gallery, track, store, requestCameraAccessIfNeeded]) if (isDesktopWeb) { - return <></> + return null } return ( @@ -76,11 +58,7 @@ export function OpenCameraBtn({ hitSlop={HITSLOP}> <FontAwesomeIcon icon="camera" - style={ - (enabled - ? pal.link - : [pal.textLight, s.dimmed]) as FontAwesomeIconStyle - } + style={pal.link as FontAwesomeIconStyle} size={24} /> </TouchableOpacity> diff --git a/src/view/com/composer/photos/OpenCameraBtn.web.tsx b/src/view/com/composer/photos/OpenCameraBtn.web.tsx new file mode 100644 index 000000000..226de1f60 --- /dev/null +++ b/src/view/com/composer/photos/OpenCameraBtn.web.tsx @@ -0,0 +1,3 @@ +export function OpenCameraBtn() { + return null +} diff --git a/src/view/com/composer/photos/SelectPhotoBtn.tsx b/src/view/com/composer/photos/SelectPhotoBtn.tsx index c0808b85c..9569e08ad 100644 --- a/src/view/com/composer/photos/SelectPhotoBtn.tsx +++ b/src/view/com/composer/photos/SelectPhotoBtn.tsx @@ -1,86 +1,36 @@ -import React from 'react' -import {Platform, TouchableOpacity} from 'react-native' +import React, {useCallback} from 'react' +import {TouchableOpacity} from 'react-native' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' import {usePalette} from 'lib/hooks/usePalette' import {useAnalytics} from 'lib/analytics' -import {useStores} from 'state/index' import {s} from 'lib/styles' import {isDesktopWeb} from 'platform/detection' -import {openPicker, cropAndCompressFlow, pickImagesFlow} from 'lib/media/picker' import {usePhotoLibraryPermission} from 'lib/hooks/usePermissions' -import { - POST_IMG_MAX_WIDTH, - POST_IMG_MAX_HEIGHT, - POST_IMG_MAX_SIZE, -} from 'lib/constants' +import {GalleryModel} from 'state/models/media/gallery' const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10} -export function SelectPhotoBtn({ - enabled, - selectedPhotos, - onSelectPhotos, -}: { - enabled: boolean - selectedPhotos: string[] - onSelectPhotos: (v: string[]) => void -}) { +type Props = { + gallery: GalleryModel +} + +export function SelectPhotoBtn({gallery}: Props) { const pal = usePalette('default') const {track} = useAnalytics() - const store = useStores() const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission() - const onPressSelectPhotos = React.useCallback(async () => { + const onPressSelectPhotos = useCallback(async () => { track('Composer:GalleryOpened') - if (!enabled) { + + if (!isDesktopWeb && !(await requestPhotoAccessIfNeeded())) { return } - if (isDesktopWeb) { - const images = await pickImagesFlow( - store, - 4 - selectedPhotos.length, - {width: POST_IMG_MAX_WIDTH, height: POST_IMG_MAX_HEIGHT}, - POST_IMG_MAX_SIZE, - ) - onSelectPhotos([...selectedPhotos, ...images]) - } else { - if (!(await requestPhotoAccessIfNeeded())) { - return - } - const items = await openPicker(store, { - multiple: true, - maxFiles: 4 - selectedPhotos.length, - mediaType: 'photo', - }) - const result = [] - for (const image of items) { - if (Platform.OS === 'android') { - result.push(image.path) - continue - } - result.push( - await cropAndCompressFlow( - store, - image.path, - image, - {width: POST_IMG_MAX_WIDTH, height: POST_IMG_MAX_HEIGHT}, - POST_IMG_MAX_SIZE, - ), - ) - } - onSelectPhotos([...selectedPhotos, ...result]) - } - }, [ - track, - store, - onSelectPhotos, - selectedPhotos, - enabled, - requestPhotoAccessIfNeeded, - ]) + + gallery.pick() + }, [track, gallery, requestPhotoAccessIfNeeded]) return ( <TouchableOpacity @@ -90,11 +40,7 @@ export function SelectPhotoBtn({ hitSlop={HITSLOP}> <FontAwesomeIcon icon={['far', 'image']} - style={ - (enabled - ? pal.link - : [pal.textLight, s.dimmed]) as FontAwesomeIconStyle - } + style={pal.link as FontAwesomeIconStyle} size={24} /> </TouchableOpacity> diff --git a/src/view/com/composer/photos/SelectedPhotos.tsx b/src/view/com/composer/photos/SelectedPhotos.tsx deleted file mode 100644 index d22f5d8c4..000000000 --- a/src/view/com/composer/photos/SelectedPhotos.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React, {useCallback} from 'react' -import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {Image} from 'expo-image' -import {colors} from 'lib/styles' - -export const SelectedPhotos = ({ - selectedPhotos, - onSelectPhotos, -}: { - selectedPhotos: string[] - onSelectPhotos: (v: string[]) => void -}) => { - const imageStyle = - selectedPhotos.length === 1 - ? styles.image250 - : selectedPhotos.length === 2 - ? styles.image175 - : styles.image85 - - const handleRemovePhoto = useCallback( - item => { - onSelectPhotos(selectedPhotos.filter(filterItem => filterItem !== item)) - }, - [selectedPhotos, onSelectPhotos], - ) - - return selectedPhotos.length !== 0 ? ( - <View testID="selectedPhotosView" style={styles.gallery}> - {selectedPhotos.length !== 0 && - selectedPhotos.map((item, index) => ( - <View - key={`selected-image-${index}`} - style={[styles.imageContainer, imageStyle]}> - <TouchableOpacity - testID="removePhotoButton" - onPress={() => handleRemovePhoto(item)} - style={styles.removePhotoButton}> - <FontAwesomeIcon - icon="xmark" - size={16} - style={{color: colors.white}} - /> - </TouchableOpacity> - - <Image - testID="selectedPhotoImage" - style={[styles.image, imageStyle]} - source={{uri: item}} - /> - </View> - ))} - </View> - ) : null -} - -const styles = StyleSheet.create({ - gallery: { - flex: 1, - flexDirection: 'row', - marginTop: 16, - }, - imageContainer: { - margin: 2, - }, - image: { - resizeMode: 'cover', - borderRadius: 8, - }, - image250: { - width: 250, - height: 250, - }, - image175: { - width: 175, - height: 175, - }, - image85: { - width: 85, - height: 85, - }, - removePhotoButton: { - position: 'absolute', - top: 8, - right: 8, - width: 24, - height: 24, - borderRadius: 12, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: colors.black, - zIndex: 1, - borderColor: colors.gray4, - borderWidth: 0.5, - }, -}) |