import {RouteProp} from '@react-navigation/core'; import {StackNavigationProp} from '@react-navigation/stack'; 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 { cropVideo, trimVideo, HeaderHeight, SCREEN_HEIGHT, SCREEN_WIDTH, } from '../../utils'; import {TaggSquareButton, TaggLoadingIndicator} from '../common'; import ReactNativeZoomableView from '@dudigital/react-native-zoomable-view/src/ReactNativeZoomableView'; import {TrimmerPlayer} from '../moments/trimmer'; type ZoomInCropperRouteProps = RouteProp; type ZoomInCropperNavigationProps = StackNavigationProp< MainStackParams, 'ZoomInCropper' >; interface ZoomInCropperProps { route: ZoomInCropperRouteProps; navigation: ZoomInCropperNavigationProps; } export const ZoomInCropper: 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) } /> )} ); }; 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', }, media: { zIndex: 0, flex: 1, }, videoParent: { flex: 1, }, zoomView: { backgroundColor: 'black', flex: 1, }, });