diff options
author | Paul Frazee <pfrazee@gmail.com> | 2023-01-19 17:36:49 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-19 17:36:49 -0600 |
commit | 570b76a71d2f3a3d8d8d6bbefa7aedcf86ecffad (patch) | |
tree | 5f1740c08d258fed4858965fc6d6ded10cfe99da /src | |
parent | 15589d216fa695aa42786c98cb9e6952db4a2b77 (diff) | |
download | voidsky-570b76a71d2f3a3d8d8d6bbefa7aedcf86ecffad.tar.zst |
Add the ability to paste images into the composer (#56)
Diffstat (limited to 'src')
-rw-r--r-- | src/view/com/composer/ComposePost.tsx | 53 | ||||
-rw-r--r-- | src/view/com/composer/PhotoCarouselPicker.tsx | 41 |
2 files changed, 69 insertions, 25 deletions
diff --git a/src/view/com/composer/ComposePost.tsx b/src/view/com/composer/ComposePost.tsx index a8def6405..3614267b1 100644 --- a/src/view/com/composer/ComposePost.tsx +++ b/src/view/com/composer/ComposePost.tsx @@ -7,11 +7,14 @@ import { SafeAreaView, ScrollView, StyleSheet, - TextInput, TouchableOpacity, TouchableWithoutFeedback, View, } from 'react-native' +import PasteInput, { + PastedFile, + PasteInputRef, +} from '@mattermost/react-native-paste-input' import LinearGradient from 'react-native-linear-gradient' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {UserAutocompleteViewModel} from '../../../state/models/user-autocomplete-view' @@ -33,7 +36,7 @@ import {detectLinkables, extractEntities} from '../../../lib/strings' import {getLinkMeta} from '../../../lib/link-meta' import {downloadAndResize} from '../../../lib/images' import {UserLocalPhotosModel} from '../../../state/models/user-local-photos' -import {PhotoCarouselPicker} from './PhotoCarouselPicker' +import {PhotoCarouselPicker, cropPhoto} from './PhotoCarouselPicker' import {SelectedPhoto} from './SelectedPhoto' import {usePalette} from '../../lib/hooks/usePalette' @@ -54,7 +57,7 @@ export const ComposePost = observer(function ComposePost({ }) { const pal = usePalette('default') const store = useStores() - const textInput = useRef<TextInput>(null) + const textInput = useRef<PasteInputRef>(null) const [isProcessing, setIsProcessing] = useState(false) const [processingState, setProcessingState] = useState('') const [error, setError] = useState('') @@ -78,6 +81,16 @@ export const ComposePost = observer(function ComposePost({ [store], ) + // HACK + // there's a bug with @mattermost/react-native-paste-input where if the input + // is focused during unmount, an exception will throw (seems that a blur method isnt implemented) + // manually blurring before closing gets around that + // -prf + const hackfixOnClose = () => { + textInput.current?.blur() + onClose() + } + // initial setup useEffect(() => { autocompleteView.setup() @@ -134,7 +147,8 @@ export const ComposePost = observer(function ComposePost({ isLoading: false, // done }) } - }, [extLink]) + return cleanup + }, [store, extLink]) useEffect(() => { // HACK @@ -196,9 +210,21 @@ export const ComposePost = observer(function ComposePost({ } } } - const onPressCancel = () => { - onClose() + const onPaste = async (err: string | undefined, files: PastedFile[]) => { + if (err) { + return setError(err) + } + if (selectedPhotos.length >= 4) { + return + } + const imgFile = files.find(file => /\.(jpe?g|png)$/.test(file.fileName)) + if (!imgFile) { + return + } + const finalImgPath = await cropPhoto(imgFile.uri) + onSelectPhotos([...selectedPhotos, finalImgPath]) } + const onPressCancel = () => hackfixOnClose() const onPressPublish = async () => { if (isProcessing) { return @@ -229,7 +255,7 @@ export const ComposePost = observer(function ComposePost({ } store.me.mainFeed.loadLatest() onPost?.() - onClose() + hackfixOnClose() Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`) } const onSelectAutocompleteItem = (item: string) => { @@ -254,7 +280,11 @@ export const ComposePost = observer(function ComposePost({ let i = 0 return detectLinkables(text).map(v => { if (typeof v === 'string') { - return v + return ( + <Text key={i++} style={styles.textInputFormatting}> + {v} + </Text> + ) } else { return ( <Text key={i++} style={[pal.link, styles.textInputFormatting]}> @@ -263,7 +293,7 @@ export const ComposePost = observer(function ComposePost({ ) } }) - }, [text]) + }, [text, pal.link]) return ( <KeyboardAvoidingView @@ -354,12 +384,13 @@ export const ComposePost = observer(function ComposePost({ avatar={store.me.avatar} size={50} /> - <TextInput + <PasteInput testID="composerTextInput" ref={textInput} multiline scrollEnabled onChangeText={(text: string) => onChangeText(text)} + onPaste={onPaste} placeholder={selectTextInputPlaceholder} placeholderTextColor={pal.colors.textLight} style={[ @@ -368,7 +399,7 @@ export const ComposePost = observer(function ComposePost({ styles.textInputFormatting, ]}> {textDecorated} - </TextInput> + </PasteInput> </View> <SelectedPhoto selectedPhotos={selectedPhotos} diff --git a/src/view/com/composer/PhotoCarouselPicker.tsx b/src/view/com/composer/PhotoCarouselPicker.tsx index 440e7d38f..6b537e9c8 100644 --- a/src/view/com/composer/PhotoCarouselPicker.tsx +++ b/src/view/com/composer/PhotoCarouselPicker.tsx @@ -26,6 +26,28 @@ const IMAGE_PARAMS = { compressImageQuality: 1.0, } +export async function cropPhoto( + 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({ + mediaType: 'photo', + path, + ...IMAGE_PARAMS, + width, + height, + }) + const img = await compressIfNeeded(cropperRes, MAX_SIZE) + return img.path +} + export const PhotoCarouselPicker = ({ selectedPhotos, onSelectPhotos, @@ -55,21 +77,12 @@ export const PhotoCarouselPicker = ({ const handleSelectPhoto = useCallback( async (item: PhotoIdentifier) => { try { - // choose target dimensions based on the original - // this causes the photo cropper to start with the full image "selected" - const {width, height} = scaleDownDimensions( - {width: item.node.image.width, height: item.node.image.height}, - {width: MAX_WIDTH, height: MAX_HEIGHT}, + const imgPath = await cropPhoto( + item.node.image.uri, + item.node.image.width, + item.node.image.height, ) - const cropperRes = await openCropper({ - mediaType: 'photo', - path: item.node.image.uri, - ...IMAGE_PARAMS, - width, - height, - }) - const img = await compressIfNeeded(cropperRes, MAX_SIZE) - onSelectPhotos([...selectedPhotos, img.path]) + onSelectPhotos([...selectedPhotos, imgPath]) } catch (err: any) { // ignore store.log.warn('Error selecting photo', err) |