diff options
Diffstat (limited to 'src/view/com/composer')
-rw-r--r-- | src/view/com/composer/ComposePost.tsx | 27 | ||||
-rw-r--r-- | src/view/com/composer/photos/PhotoCarouselPicker.tsx (renamed from src/view/com/composer/PhotoCarouselPicker.tsx) | 68 | ||||
-rw-r--r-- | src/view/com/composer/photos/PhotoCarouselPicker.web.tsx | 158 |
3 files changed, 207 insertions, 46 deletions
diff --git a/src/view/com/composer/ComposePost.tsx b/src/view/com/composer/ComposePost.tsx index 2f30a1cf4..1144b5e48 100644 --- a/src/view/com/composer/ComposePost.tsx +++ b/src/view/com/composer/ComposePost.tsx @@ -37,8 +37,7 @@ import { } from '../../../lib/strings' import {getLinkMeta} from '../../../lib/link-meta' import {downloadAndResize} from '../../../lib/images' -import {UserLocalPhotosModel} from '../../../state/models/user-local-photos' -import {PhotoCarouselPicker, cropPhoto} from './PhotoCarouselPicker' +import {PhotoCarouselPicker, cropPhoto} from './photos/PhotoCarouselPicker' import {SelectedPhoto} from './SelectedPhoto' import {usePalette} from '../../lib/hooks/usePalette' @@ -77,10 +76,6 @@ export const ComposePost = observer(function ComposePost({ () => new UserAutocompleteViewModel(store), [store], ) - const localPhotos = React.useMemo<UserLocalPhotosModel>( - () => new UserLocalPhotosModel(store), - [store], - ) // HACK // there's a bug with @mattermost/react-native-paste-input where if the input @@ -95,8 +90,7 @@ export const ComposePost = observer(function ComposePost({ // initial setup useEffect(() => { autocompleteView.setup() - localPhotos.setup() - }, [autocompleteView, localPhotos]) + }, [autocompleteView]) // external link metadata-fetch flow useEffect(() => { @@ -220,7 +214,7 @@ export const ComposePost = observer(function ComposePost({ } const imgUri = uris.find(uri => /\.(jpe?g|png)$/.test(uri)) if (imgUri) { - const finalImgPath = await cropPhoto(imgUri) + const finalImgPath = await cropPhoto(store, imgUri) onSelectPhotos([...selectedPhotos, finalImgPath]) } } @@ -412,15 +406,12 @@ export const ComposePost = observer(function ComposePost({ /> )} </ScrollView> - {isSelectingPhotos && - localPhotos.photos != null && - selectedPhotos.length < 4 && ( - <PhotoCarouselPicker - selectedPhotos={selectedPhotos} - onSelectPhotos={onSelectPhotos} - localPhotos={localPhotos} - /> - )} + {isSelectingPhotos && selectedPhotos.length < 4 && ( + <PhotoCarouselPicker + selectedPhotos={selectedPhotos} + onSelectPhotos={onSelectPhotos} + /> + )} <View style={[pal.border, styles.bottomBar]}> <TouchableOpacity testID="composerSelectPhotosButton" diff --git a/src/view/com/composer/PhotoCarouselPicker.tsx b/src/view/com/composer/photos/PhotoCarouselPicker.tsx index eb5b4dcf2..7a5c9f65d 100644 --- a/src/view/com/composer/PhotoCarouselPicker.tsx +++ b/src/view/com/composer/photos/PhotoCarouselPicker.tsx @@ -8,14 +8,14 @@ import { openPicker, openCamera, openCropper, -} from '../util/images/ImageCropPicker' +} from '../../util/images/image-crop-picker/ImageCropPicker' import { UserLocalPhotosModel, PhotoIdentifier, -} from '../../../state/models/user-local-photos' -import {compressIfNeeded, scaleDownDimensions} from '../../../lib/images' -import {usePalette} from '../../lib/hooks/usePalette' -import {useStores} from '../../../state' +} from '../../../../state/models/user-local-photos' +import {compressIfNeeded, scaleDownDimensions} from '../../../../lib/images' +import {usePalette} from '../../../lib/hooks/usePalette' +import {useStores, RootStoreModel} from '../../../../state' const MAX_WIDTH = 1000 const MAX_HEIGHT = 1000 @@ -25,11 +25,10 @@ const IMAGE_PARAMS = { width: 1000, height: 1000, freeStyleCropEnabled: true, - forceJpg: true, // ios only - compressImageQuality: 1.0, } export async function cropPhoto( + store: RootStoreModel, path: string, imgWidth = MAX_WIDTH, imgHeight = MAX_HEIGHT, @@ -40,10 +39,10 @@ export async function cropPhoto( {width: imgWidth, height: imgHeight}, {width: MAX_WIDTH, height: MAX_HEIGHT}, ) - const cropperRes = await openCropper({ + const cropperRes = await openCropper(store, { mediaType: 'photo', path, - ...IMAGE_PARAMS, + freeStyleCropEnabled: true, width, height, }) @@ -54,19 +53,30 @@ export async function cropPhoto( export const PhotoCarouselPicker = ({ selectedPhotos, onSelectPhotos, - localPhotos, }: { selectedPhotos: string[] onSelectPhotos: (v: string[]) => void - localPhotos: UserLocalPhotosModel }) => { const pal = usePalette('default') const store = useStores() + const [localPhotos, setLocalPhotos] = React.useState< + UserLocalPhotosModel | undefined + >(undefined) + + // initial setup + React.useEffect(() => { + const photos = new UserLocalPhotosModel(store) + photos.setup().then(() => { + if (photos.photos) { + setLocalPhotos(photos) + } + }) + }, [store]) + const handleOpenCamera = useCallback(async () => { try { - const cameraRes = await openCamera({ + const cameraRes = await openCamera(store, { mediaType: 'photo', - cropping: true, ...IMAGE_PARAMS, }) const img = await compressIfNeeded(cameraRes, MAX_SIZE) @@ -75,12 +85,13 @@ export const PhotoCarouselPicker = ({ // ignore store.log.warn('Error using camera', err) } - }, [store.log, selectedPhotos, onSelectPhotos]) + }, [store, selectedPhotos, onSelectPhotos]) const handleSelectPhoto = useCallback( async (item: PhotoIdentifier) => { try { const imgPath = await cropPhoto( + store, item.node.image.uri, item.node.image.width, item.node.image.height, @@ -91,11 +102,11 @@ export const PhotoCarouselPicker = ({ store.log.warn('Error selecting photo', err) } }, - [store.log, selectedPhotos, onSelectPhotos], + [store, selectedPhotos, onSelectPhotos], ) const handleOpenGallery = useCallback(() => { - openPicker({ + openPicker(store, { multiple: true, maxFiles: 4 - selectedPhotos.length, mediaType: 'photo', @@ -109,10 +120,10 @@ export const PhotoCarouselPicker = ({ {width: image.width, height: image.height}, {width: MAX_WIDTH, height: MAX_HEIGHT}, ) - const cropperRes = await openCropper({ + const cropperRes = await openCropper(store, { mediaType: 'photo', path: image.path, - ...IMAGE_PARAMS, + freeStyleCropEnabled: true, width, height, }) @@ -121,7 +132,7 @@ export const PhotoCarouselPicker = ({ } onSelectPhotos([...selectedPhotos, ...result]) }) - }, [selectedPhotos, onSelectPhotos]) + }, [store, selectedPhotos, onSelectPhotos]) return ( <ScrollView @@ -150,15 +161,16 @@ export const PhotoCarouselPicker = ({ size={24} /> </TouchableOpacity> - {localPhotos.photos.map((item: PhotoIdentifier, index: number) => ( - <TouchableOpacity - testID="openSelectPhotoButton" - key={`local-image-${index}`} - style={[pal.border, styles.photoButton]} - onPress={() => handleSelectPhoto(item)}> - <Image style={styles.photo} source={{uri: item.node.image.uri}} /> - </TouchableOpacity> - ))} + {localPhotos != null && + localPhotos.photos.map((item: PhotoIdentifier, index: number) => ( + <TouchableOpacity + testID="openSelectPhotoButton" + key={`local-image-${index}`} + style={[pal.border, styles.photoButton]} + onPress={() => handleSelectPhoto(item)}> + <Image style={styles.photo} source={{uri: item.node.image.uri}} /> + </TouchableOpacity> + ))} </ScrollView> ) } diff --git a/src/view/com/composer/photos/PhotoCarouselPicker.web.tsx b/src/view/com/composer/photos/PhotoCarouselPicker.web.tsx new file mode 100644 index 000000000..bb2800026 --- /dev/null +++ b/src/view/com/composer/photos/PhotoCarouselPicker.web.tsx @@ -0,0 +1,158 @@ +import React, {useCallback} from 'react' +import {StyleSheet, TouchableOpacity, ScrollView} from 'react-native' +import { + FontAwesomeIcon, + FontAwesomeIconStyle, +} from '@fortawesome/react-native-fontawesome' +import { + openPicker, + openCamera, + openCropper, +} from '../../util/images/image-crop-picker/ImageCropPicker' +import {compressIfNeeded, scaleDownDimensions} from '../../../../lib/images' +import {usePalette} from '../../../lib/hooks/usePalette' +import {useStores, RootStoreModel} from '../../../../state' + +const MAX_WIDTH = 1000 +const MAX_HEIGHT = 1000 +const MAX_SIZE = 300000 + +const IMAGE_PARAMS = { + width: 1000, + height: 1000, + freeStyleCropEnabled: true, +} + +export async function cropPhoto( + store: RootStoreModel, + path: string, + imgWidth = MAX_WIDTH, + imgHeight = MAX_HEIGHT, +) { + // choose target dimensions based on the original + // this causes the photo cropper to start with the full image "selected" + const {width, height} = scaleDownDimensions( + {width: imgWidth, height: imgHeight}, + {width: MAX_WIDTH, height: MAX_HEIGHT}, + ) + const cropperRes = await openCropper(store, { + mediaType: 'photo', + path, + freeStyleCropEnabled: true, + width, + height, + }) + const img = await compressIfNeeded(cropperRes, MAX_SIZE) + return img.path +} + +export const PhotoCarouselPicker = ({ + selectedPhotos, + onSelectPhotos, +}: { + selectedPhotos: string[] + onSelectPhotos: (v: string[]) => void +}) => { + const pal = usePalette('default') + const store = useStores() + + const handleOpenCamera = useCallback(async () => { + try { + const cameraRes = await openCamera(store, { + mediaType: 'photo', + ...IMAGE_PARAMS, + }) + const img = await compressIfNeeded(cameraRes, MAX_SIZE) + onSelectPhotos([...selectedPhotos, img.path]) + } catch (err: any) { + // ignore + store.log.warn('Error using camera', err) + } + }, [store, selectedPhotos, onSelectPhotos]) + + const handleOpenGallery = useCallback(() => { + openPicker(store, { + multiple: true, + maxFiles: 4 - selectedPhotos.length, + mediaType: 'photo', + }).then(async items => { + const result = [] + + for (const image of items) { + // choose target dimensions based on the original + // this causes the photo cropper to start with the full image "selected" + const {width, height} = scaleDownDimensions( + {width: image.width, height: image.height}, + {width: MAX_WIDTH, height: MAX_HEIGHT}, + ) + const cropperRes = await openCropper(store, { + mediaType: 'photo', + path: image.path, + freeStyleCropEnabled: true, + width, + height, + }) + const finalImg = await compressIfNeeded(cropperRes, MAX_SIZE) + result.push(finalImg.path) + } + onSelectPhotos([...selectedPhotos, ...result]) + }) + }, [store, selectedPhotos, onSelectPhotos]) + + return ( + <ScrollView + testID="photoCarouselPickerView" + horizontal + style={[pal.view, styles.photosContainer]} + keyboardShouldPersistTaps="always" + showsHorizontalScrollIndicator={false}> + <TouchableOpacity + testID="openCameraButton" + style={[styles.galleryButton, pal.border, styles.photo]} + onPress={handleOpenCamera}> + <FontAwesomeIcon + icon="camera" + size={24} + style={pal.link as FontAwesomeIconStyle} + /> + </TouchableOpacity> + <TouchableOpacity + testID="openGalleryButton" + style={[styles.galleryButton, pal.border, styles.photo]} + onPress={handleOpenGallery}> + <FontAwesomeIcon + icon="image" + style={pal.link as FontAwesomeIconStyle} + size={24} + /> + </TouchableOpacity> + </ScrollView> + ) +} + +const styles = StyleSheet.create({ + photosContainer: { + width: '100%', + maxHeight: 96, + padding: 8, + overflow: 'hidden', + }, + galleryButton: { + borderWidth: 1, + alignItems: 'center', + justifyContent: 'center', + }, + photoButton: { + width: 75, + height: 75, + marginRight: 8, + borderWidth: 1, + borderRadius: 16, + }, + photo: { + width: 75, + height: 75, + marginRight: 8, + borderRadius: 16, + }, +}) |