From c3d418bc7ad3c702ed52fa522c1225dc4816c092 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Fri, 16 Jul 2021 17:46:15 -0400 Subject: Refactor ZoomInCropper to be EditMedia --- src/screens/index.ts | 1 + src/screens/moments/CameraScreen.tsx | 2 +- src/screens/upload/EditMedia.tsx | 431 +++++++++++++++++++++++++++++++++++ src/screens/upload/index.ts | 1 + 4 files changed, 434 insertions(+), 1 deletion(-) create mode 100644 src/screens/upload/EditMedia.tsx create mode 100644 src/screens/upload/index.ts (limited to 'src/screens') diff --git a/src/screens/index.ts b/src/screens/index.ts index 0c7d911f..5fa14d05 100644 --- a/src/screens/index.ts +++ b/src/screens/index.ts @@ -7,3 +7,4 @@ export * from './suggestedPeopleOnboarding'; export * from './badge'; export * from './chat'; export * from './moments'; +export * from './upload'; diff --git a/src/screens/moments/CameraScreen.tsx b/src/screens/moments/CameraScreen.tsx index 18e04261..cb5cd411 100644 --- a/src/screens/moments/CameraScreen.tsx +++ b/src/screens/moments/CameraScreen.tsx @@ -65,7 +65,7 @@ const CameraScreen: React.FC = ({route, navigation}) => { }, [mediaFromGallery]); const navigateToCropper = (uri: string) => { - navigation.navigate('ZoomInCropper', { + navigation.navigate('EditMedia', { screenType, media: { uri, diff --git a/src/screens/upload/EditMedia.tsx b/src/screens/upload/EditMedia.tsx new file mode 100644 index 00000000..ed5cd0d7 --- /dev/null +++ b/src/screens/upload/EditMedia.tsx @@ -0,0 +1,431 @@ +import ReactNativeZoomableView from '@dudigital/react-native-zoomable-view/src/ReactNativeZoomableView'; +import {RouteProp} from '@react-navigation/core'; +import {StackNavigationProp} from '@react-navigation/stack'; +import React, {useEffect, useRef, useState} from 'react'; +import {Image, StyleSheet, Text, 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 TrimIcon from '../../assets/icons/trim.svg'; +import CloseIcon from '../../assets/ionicons/close-outline.svg'; +import {TaggLoadingIndicator, TaggSquareButton} from '../../components/common'; +import {TrimmerPlayer} from '../../components/moments/trimmer'; +import {MainStackParams} from '../../routes'; +import { + cropVideo, + HeaderHeight, + SCREEN_HEIGHT, + SCREEN_WIDTH, + trimVideo, +} from '../../utils'; + +type EditMediaRouteProps = RouteProp; +type EditMediaNavigationProps = StackNavigationProp< + MainStackParams, + 'EditMedia' +>; +interface EditMediaProps { + route: EditMediaRouteProps; + navigation: EditMediaNavigationProps; +} + +export const EditMedia: React.FC = ({route, navigation}) => { + const {screenType, media, selectedCategory} = route.params; + const [aspectRatio, setAspectRatio] = useState(1); + // width and height of video, if video + const [origDimensions, setOrigDimensions] = useState([0, 0]); + const vidRef = useRef(null); + const [cropLoading, setCropLoading] = useState(false); + + // Stores the coordinates of the cropped image + const [x0, setX0] = useState(); + const [x1, setX1] = useState(); + const [y0, setY0] = useState(); + const [y1, setY1] = useState(); + + // Stores crop information for video + const [videoCrop, setVideoCrop] = useState<{ + cropWidth?: number; + cropHeight?: number; + cropOffsetX?: number; + cropOffsetY?: number; + }>({}); + + // Stores the current trim endpoints + const [trimEnds, setTrimEnds] = useState<{ + end: number; + start: number; + }>({ + end: 60, + start: 0, + }); + + 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 && checkIfUriImage(media.uri)) { + Image.getSize( + media.uri, + (w, h) => { + setAspectRatio(w / h); + }, + (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 (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], + })); + } + setCropLoading(true); + trimVideo( + media.uri, + (trimmedURL: string) => + cropVideo( + trimmedURL, + (croppedURL: string) => { + setCropLoading(false); + navigation.navigate('CaptionScreen', { + screenType, + media: { + uri: croppedURL, + isVideo: true, + }, + selectedCategory, + }); + }, + videoCrop, + ), + trimEnds, + ); + } + }; + + // 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, + })); + }, + ); + } + }; + + /* Records (x0, y0) and (x1, y1) coordinates used later for cropping, + * based on(x, y) - the center of the image and scale of zoom + */ + const onMove = (position: IOnMove) => { + Image.getSize( + media.uri, + (w, h) => { + const x = position.positionX; + const y = position.positionY; + const scale = position.scale; + const screen_ratio = SCREEN_HEIGHT / SCREEN_WIDTH; + let tempx0 = w / 2 - x * (w / SCREEN_WIDTH) - w / 2 / scale; + let tempx1 = w / 2 - x * (w / SCREEN_WIDTH) + w / 2 / scale; + if (tempx0 < 0) { + tempx0 = 0; + } + if (tempx1 > w) { + tempx1 = w; + } + const x_distance = Math.abs(tempx1 - tempx0); + const y_distance = screen_ratio * x_distance; + let tempy0 = h / 2 - y * (h / SCREEN_HEIGHT) + y_distance / 2; + let tempy1 = h / 2 - y * (h / SCREEN_HEIGHT) - y_distance / 2; + if (tempy0 > h) { + tempy0 = h; + } + if (tempy1 < 0) { + tempy1 = 0; + } + setX0(tempx0); + setX1(tempx1); + setY0(tempy0); + setY1(tempy1); + }, + (err) => console.log(err), + ); + }; + + return ( + + {cropLoading && } + navigation.goBack()}> + + + {checkIfUriImage(media.uri) ? ( + + + + ) : ( + { + onVideoMove(zoomableViewEventObject); + }} + onShiftingAfter={(_1: any, _2: any, zoomableViewEventObject: any) => { + onVideoMove(zoomableViewEventObject); + }} + onShiftingEnd={(_1: any, _2: any, zoomableViewEventObject: any) => { + onVideoMove(zoomableViewEventObject); + }} + onZoomAfter={(_1: any, _2: any, zoomableViewEventObject: any) => { + onVideoMove(zoomableViewEventObject); + }} + onZoomEnd={(_1: any, _2: any, zoomableViewEventObject: any) => { + onVideoMove(zoomableViewEventObject); + }} + style={styles.zoomView}> + + { + const {width, height} = response; + setOrigDimensions([width, height]); + setAspectRatio(width / height); + }} + onChangedEndpoints={(response: {start: number; end: number}) => + setTrimEnds(response) + } + /> + + + )} + {!checkIfUriImage(media.uri) && ( + + + Trim + + Volume + + )} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: 'black', + height: SCREEN_HEIGHT, + width: SCREEN_WIDTH, + }, + closeButton: { + position: 'absolute', + top: 0, + paddingTop: HeaderHeight, + zIndex: 1, + marginLeft: '5%', + }, + button: { + zIndex: 1, + position: 'absolute', + bottom: normalize(20), + right: normalize(15), + width: normalize(108), + height: normalize(25), + borderRadius: 10, + }, + buttonLabel: { + fontWeight: '700', + fontSize: normalize(15), + lineHeight: normalize(17.8), + letterSpacing: normalize(1.3), + textAlign: 'center', + }, + iconCarrier: { + width: SCREEN_WIDTH * 0.15, + height: SCREEN_HEIGHT * 0.2, + borderRadius: SCREEN_WIDTH * 0.1, + backgroundColor: 'rgba(0, 0, 0, 0.3)', + position: 'absolute', + right: SCREEN_WIDTH * 0.025, + top: SCREEN_HEIGHT * 0.2, + flexDirection: 'column', + justifyContent: 'space-evenly', + alignItems: 'center', + }, + iconText: { + color: 'white', + fontSize: 10, + }, + media: { + zIndex: 0, + flex: 1, + }, + videoParent: { + flex: 1, + }, + zoomView: { + backgroundColor: 'black', + flex: 1, + }, + volumnIcon: { + width: 25, + height: 25, + }, +}); + +export default EditMedia; diff --git a/src/screens/upload/index.ts b/src/screens/upload/index.ts new file mode 100644 index 00000000..0dadeede --- /dev/null +++ b/src/screens/upload/index.ts @@ -0,0 +1 @@ +export {default as EditMedia} from './EditMedia'; -- cgit v1.2.3-70-g09d2