diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/comments/ZoomInCropper.tsx | 290 | ||||
-rw-r--r-- | src/screens/moments/CameraScreen.tsx | 7 | ||||
-rw-r--r-- | src/services/MomentService.ts | 5 | ||||
-rw-r--r-- | src/utils/camera.ts | 38 | ||||
-rw-r--r-- | src/utils/index.ts | 1 |
5 files changed, 287 insertions, 54 deletions
diff --git a/src/components/comments/ZoomInCropper.tsx b/src/components/comments/ZoomInCropper.tsx index e624c81c..6f8ff97c 100644 --- a/src/components/comments/ZoomInCropper.tsx +++ b/src/components/comments/ZoomInCropper.tsx @@ -1,14 +1,21 @@ import {RouteProp} from '@react-navigation/core'; import {StackNavigationProp} from '@react-navigation/stack'; -import React, {useEffect, useState} from 'react'; -import {Image, StyleSheet, TouchableOpacity} from 'react-native'; +import React, {useEffect, useRef, useState} from 'react'; +import {Image, StyleSheet, TouchableOpacity, View} from 'react-native'; import {normalize} from 'react-native-elements'; import ImageZoom, {IOnMove} from 'react-native-image-pan-zoom'; import PhotoManipulator from 'react-native-photo-manipulator'; import CloseIcon from '../../assets/ionicons/close-outline.svg'; import {MainStackParams} from '../../routes'; -import {HeaderHeight, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import { + cropVideo, + HeaderHeight, + SCREEN_HEIGHT, + SCREEN_WIDTH, +} from '../../utils'; import {TaggSquareButton} from '../common'; +import ReactNativeZoomableView from '@dudigital/react-native-zoomable-view/src/ReactNativeZoomableView'; +import Video from 'react-native-video'; type ZoomInCropperRouteProps = RouteProp<MainStackParams, 'ZoomInCropper'>; type ZoomInCropperNavigationProps = StackNavigationProp< @@ -26,6 +33,9 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({ }) => { const {screenType, media, selectedCategory} = route.params; const [aspectRatio, setAspectRatio] = useState<number>(1); + // width and height of video, if video + const [origDimensions, setOrigDimensions] = useState<number[]>([0, 0]); + const vidRef = useRef<View>(null); // Stores the coordinates of the cropped image const [x0, setX0] = useState<number>(); @@ -33,9 +43,28 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({ const [y0, setY0] = useState<number>(); const [y1, setY1] = useState<number>(); + // Stores crop information for video + const [videoCrop, setVideoCrop] = useState<{ + cropWidth?: number; + cropHeight?: number; + cropOffsetX?: number; + cropOffsetY?: number; + }>({}); + + const checkIfUriImage = (uri: string) => { + return ( + uri.endsWith('jpg') || + uri.endsWith('JPG') || + uri.endsWith('PNG') || + uri.endsWith('png') || + uri.endsWith('GIF') || + uri.endsWith('gif') + ); + }; + // Setting original aspect ratio of image useEffect(() => { - if (media.uri) { + if (media.uri && checkIfUriImage(media.uri)) { Image.getSize( media.uri, (w, h) => { @@ -43,45 +72,146 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({ }, (err) => console.log(err), ); + } else if (media.uri && !checkIfUriImage(media.uri)) { + setVideoCrop((prevState) => ({ + ...prevState, + cropWidth: origDimensions[0], + cropHeight: origDimensions[1], + })); } }, []); + // Possible need to delay setting aspect ratio of video until loaded + useEffect(() => { + if (media.uri && !checkIfUriImage(media.uri)) { + setVideoCrop((prevState) => ({ + ...prevState, + cropWidth: origDimensions[0], + cropHeight: origDimensions[1], + })); + } + }, [origDimensions]); + // Crops original image based of (x0, y0) and (x1, y1) coordinates const handleNext = () => { - if ( - x0 !== undefined && - x1 !== undefined && - y0 !== undefined && - y1 !== undefined - ) { - PhotoManipulator.crop(media.uri, { - x: x0, - y: y1, - width: Math.abs(x0 - x1), - height: Math.abs(y0 - y1), - }) - .then((croppedURL) => { + if (checkIfUriImage(media.uri)) { + if ( + x0 !== undefined && + x1 !== undefined && + y0 !== undefined && + y1 !== undefined + ) { + PhotoManipulator.crop(media.uri, { + x: x0, + y: y1, + width: Math.abs(x0 - x1), + height: Math.abs(y0 - y1), + }) + .then((croppedURL) => { + navigation.navigate('CaptionScreen', { + screenType, + media: { + uri: croppedURL, + isVideo: false, + }, + }); + }) + .catch((err) => console.log('err: ', err)); + } else if ( + x0 === undefined && + x1 === undefined && + y0 === undefined && + y1 === undefined + ) { + navigation.navigate('CaptionScreen', { + screenType, + media, + }); + } + } else { + if (!videoCrop.cropHeight || !videoCrop.cropWidth) { + setVideoCrop((prevState) => ({ + ...prevState, + cropWidth: origDimensions[0], + cropHeight: origDimensions[1], + })); + } + cropVideo( + media.uri, + (croppedURL: string) => { navigation.navigate('CaptionScreen', { screenType, media: { uri: croppedURL, - isVideo: false, + isVideo: true, }, selectedCategory, }); - }) - .catch((err) => console.log('err: ', err)); - } else if ( - x0 === undefined && - x1 === undefined && - y0 === undefined && - y1 === undefined - ) { - navigation.navigate('CaptionScreen', { - screenType, - media, - selectedCategory, - }); + }, + videoCrop, + ); + } + }; + + // for whenever the video is altered by the user + const onVideoMove = (zoomableEvent: any) => { + const {originalHeight, originalWidth} = zoomableEvent; + + let cropWidth = 0; + let cropHeight = 0; + let cropOffsetX = 0; + let cropOffsetY = 0; + + if (vidRef !== null && vidRef.current !== null) { + vidRef.current.measure( + ( + _x: number, + _y: number, + width: number, + height: number, + pageX: number, + pageY: number, + ) => { + // width + cropWidth = origDimensions[0] * (originalWidth / width); + + // offsetX + cropOffsetX = -1 * origDimensions[0] * (pageX / width); + if (cropOffsetX < 0) { + cropOffsetX = 0; + } else if (cropOffsetX + cropWidth > origDimensions[0] - 1) { + cropOffsetX = origDimensions[0] - cropWidth - 1; + } + + // height + if ( + height * (SCREEN_WIDTH / aspectRatio / originalHeight) > + SCREEN_HEIGHT + ) { + const superHeight = width / aspectRatio; + cropHeight = origDimensions[1] * (originalHeight / superHeight); + + // offsetY + const topDeadZone = (height - superHeight) / 2; + const offsetY = topDeadZone + pageY; + cropOffsetY = -1 * origDimensions[1] * (offsetY / superHeight); + if (cropOffsetY < 0) { + cropOffsetY = 0; + } else if (cropOffsetY + cropHeight > origDimensions[1]) { + cropOffsetY = origDimensions[1] - cropHeight - 1; + } + } else { + cropHeight = origDimensions[1]; + } + setVideoCrop((prevState) => ({ + ...prevState, + cropWidth: cropWidth, + cropHeight: cropHeight, + cropOffsetX: cropOffsetX, + cropOffsetY: cropOffsetY, + })); + }, + ); } }; @@ -124,26 +254,77 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({ }; return ( - <> + <View style={styles.container}> <TouchableOpacity style={styles.closeButton} onPress={() => navigation.goBack()}> <CloseIcon height={25} width={25} color={'white'} /> </TouchableOpacity> - <ImageZoom - style={styles.zoomView} - cropWidth={SCREEN_WIDTH} - cropHeight={SCREEN_HEIGHT} - imageWidth={SCREEN_WIDTH} - imageHeight={SCREEN_WIDTH / aspectRatio} - onMove={onMove}> - <Image - style={{width: SCREEN_WIDTH, height: SCREEN_WIDTH / aspectRatio}} - source={{ - uri: media.uri, + {checkIfUriImage(media.uri) ? ( + <ImageZoom + style={styles.zoomView} + cropWidth={SCREEN_WIDTH} + cropHeight={SCREEN_HEIGHT} + imageWidth={SCREEN_WIDTH} + imageHeight={SCREEN_WIDTH / aspectRatio} + onMove={onMove}> + <Image + style={{width: SCREEN_WIDTH, height: SCREEN_WIDTH / aspectRatio}} + source={{ + uri: media.uri, + }} + /> + </ImageZoom> + ) : ( + <ReactNativeZoomableView + maxZoom={10} + minZoom={1} + zoomStep={0.5} + initialZoom={1} + bindToBorders={true} + // onZoomAfter={this.logOutZoomState} + onDoubleTapAfter={( + _1: any, + _2: any, + zoomableViewEventObject: any, + ) => { + onVideoMove(zoomableViewEventObject); + }} + onShiftingAfter={(_1: any, _2: any, zoomableViewEventObject: any) => { + onVideoMove(zoomableViewEventObject); + }} + onShiftingEnd={(_1: any, _2: any, zoomableViewEventObject: any) => { + onVideoMove(zoomableViewEventObject); }} - /> - </ImageZoom> + onZoomAfter={(_1: any, _2: any, zoomableViewEventObject: any) => { + onVideoMove(zoomableViewEventObject); + }} + onZoomEnd={(_1: any, _2: any, zoomableViewEventObject: any) => { + onVideoMove(zoomableViewEventObject); + }} + style={styles.zoomView}> + <View style={styles.videoParent} ref={vidRef}> + <Video + source={{uri: media.uri}} + volume={1} + style={[ + styles.media, + { + height: SCREEN_WIDTH / aspectRatio, + }, + ]} + repeat={true} + resizeMode={'contain'} + onLoad={(response) => { + const {width, height} = response.naturalSize; + setOrigDimensions([width, height]); + setAspectRatio(width / height); + }} + /> + </View> + </ReactNativeZoomableView> + )} + <TaggSquareButton onPress={handleNext} title={'Next'} @@ -153,11 +334,16 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({ style={styles.button} labelStyle={styles.buttonLabel} /> - </> + </View> ); }; const styles = StyleSheet.create({ + container: { + backgroundColor: 'black', + height: SCREEN_HEIGHT, + width: SCREEN_WIDTH, + }, closeButton: { position: 'absolute', top: 0, @@ -181,5 +367,15 @@ const styles = StyleSheet.create({ letterSpacing: normalize(1.3), textAlign: 'center', }, - zoomView: {backgroundColor: 'black'}, + media: { + zIndex: 0, + flex: 1, + }, + videoParent: { + flex: 1, + }, + zoomView: { + backgroundColor: 'black', + flex: 1, + }, }); diff --git a/src/screens/moments/CameraScreen.tsx b/src/screens/moments/CameraScreen.tsx index 2282ecad..ee5834cb 100644 --- a/src/screens/moments/CameraScreen.tsx +++ b/src/screens/moments/CameraScreen.tsx @@ -184,12 +184,7 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => { ) { showGIFFailureAlert(() => navigateToCropper(media.path)); } else { - // is this a video? - if (media.duration !== null) { - navigateToCaptionScreen(true, media.path); - } else { - navigateToCropper(media.path); - } + navigateToCropper(media.path); } }} /> diff --git a/src/services/MomentService.ts b/src/services/MomentService.ts index 50cedef9..25d44041 100644 --- a/src/services/MomentService.ts +++ b/src/services/MomentService.ts @@ -24,9 +24,12 @@ export const postMoment = async ( try { const request = new FormData(); + const fileExtension = uri.split('.').pop(); + const extension = fileExtension ? `.${fileExtension}` : '.jpg'; + request.append('image', { uri: uri, - name: 'moment.jpg', + name: Math.random().toString(36).substring(7) + extension, type: 'image/jpg', }); request.append('moment', category); diff --git a/src/utils/camera.ts b/src/utils/camera.ts index 4f933b0c..9e37d62e 100644 --- a/src/utils/camera.ts +++ b/src/utils/camera.ts @@ -8,6 +8,7 @@ import { TakePictureOptions, TakePictureResponse, } from 'react-native-camera'; +import {ProcessingManager} from 'react-native-video-processing'; import ImagePicker, {ImageOrVideo, Video} from 'react-native-image-crop-picker'; import {ERROR_UPLOAD} from '../constants/strings'; @@ -116,3 +117,40 @@ export const showGIFFailureAlert = (onSuccess: () => void) => ), }, ); + +export const cropVideo = ( + sourceUri: string, + handleData: (data: any) => any, + videoCropValues?: { + cropWidth?: number; + cropHeight?: number; + cropOffsetX?: number; + cropOffsetY?: number; + }, +) => { + ProcessingManager.crop(sourceUri, { + cropWidth: videoCropValues + ? videoCropValues.cropWidth + ? Math.round(videoCropValues.cropWidth) + : 100 + : 100, + cropHeight: videoCropValues + ? videoCropValues.cropHeight + ? Math.round(videoCropValues.cropHeight) + : 100 + : 100, + cropOffsetX: videoCropValues + ? videoCropValues.cropOffsetX + ? Math.round(videoCropValues.cropOffsetX) + : 0 + : 0, + cropOffsetY: videoCropValues + ? videoCropValues.cropOffsetY + ? Math.round(videoCropValues.cropOffsetY) + : 0 + : 0, + quality: 'highest', + }).then((data: any) => { + handleData(data); + }); +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index 4ff9afac..dc00c049 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -5,3 +5,4 @@ export * from './users'; export * from './friends'; export * from './search'; export * from './messages'; +export * from './camera'; |