aboutsummaryrefslogtreecommitdiff
path: root/src/screens
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens')
-rw-r--r--src/screens/index.ts1
-rw-r--r--src/screens/moments/CameraScreen.tsx121
-rw-r--r--src/screens/profile/CaptionScreen.tsx2
-rw-r--r--src/screens/upload/EditMedia.tsx495
-rw-r--r--src/screens/upload/index.ts1
5 files changed, 537 insertions, 83 deletions
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 ee5834cb..33ee2347 100644
--- a/src/screens/moments/CameraScreen.tsx
+++ b/src/screens/moments/CameraScreen.tsx
@@ -8,16 +8,10 @@ import {StyleSheet, TouchableOpacity, View} from 'react-native';
import {CameraType, FlashMode, RNCamera} from 'react-native-camera';
import {AnimatedCircularProgress} from 'react-native-circular-progress';
import CloseIcon from '../../assets/ionicons/close-outline.svg';
-import {
- FlashButton,
- FlipButton,
- GalleryIcon,
- SaveButton,
- TaggSquareButton,
-} from '../../components';
+import {FlashButton, FlipButton, GalleryIcon} from '../../components';
import {TAGG_PURPLE} from '../../constants';
import {MainStackParams} from '../../routes';
-import {HeaderHeight, normalize, SCREEN_WIDTH} from '../../utils';
+import {HeaderHeight, SCREEN_WIDTH} from '../../utils';
import {showGIFFailureAlert, takePicture, takeVideo} from '../../utils/camera';
type CameraScreenRouteProps = RouteProp<MainStackParams, 'CameraScreen'>;
@@ -35,9 +29,7 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => {
const tabBarHeight = useBottomTabBarHeight();
const [cameraType, setCameraType] = useState<keyof CameraType>('front');
const [flashMode, setFlashMode] = useState<keyof FlashMode>('off');
- const [mediaFromGallery, setMediaFromGallery] = useState<string>('');
const [mostRecentPhoto, setMostRecentPhoto] = useState<string>('');
- const [showSaveButton, setShowSaveButton] = useState<boolean>(false);
const [isRecording, setIsRecording] = useState<boolean>(false);
useFocusEffect(
@@ -62,14 +54,21 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => {
.catch((_err) =>
console.log('Unable to fetch preview photo for gallery'),
);
- }, [mediaFromGallery]);
+ }, []);
- const navigateToCropper = (uri: string) => {
- navigation.navigate('ZoomInCropper', {
+ const navigateToEditMedia = (uri: string) => {
+ navigation.navigate('EditMedia', {
screenType,
media: {
uri,
- isVideo: false,
+ isVideo: !(
+ uri.endsWith('jpg') ||
+ uri.endsWith('JPG') ||
+ uri.endsWith('PNG') ||
+ uri.endsWith('png') ||
+ uri.endsWith('GIF') ||
+ uri.endsWith('gif')
+ ),
},
selectedCategory,
});
@@ -85,21 +84,11 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => {
});
};
- /*
- * If picture is not taken yet, exists from camera screen to profile view
- * If picture is taken, exists from captured image's preview to camera
- * */
const handleClose = () => {
- if (showSaveButton) {
- cameraRef.current?.resumePreview();
- setShowSaveButton(false);
- setMediaFromGallery('');
- } else {
- navigation.dangerouslyGetParent()?.setOptions({
- tabBarVisible: true,
- });
- navigation.goBack();
- }
+ navigation.dangerouslyGetParent()?.setOptions({
+ tabBarVisible: true,
+ });
+ navigation.goBack();
};
return (
@@ -118,12 +107,13 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => {
}}
/>
<View style={[styles.bottomContainer, {bottom: tabBarHeight}]}>
- {showSaveButton ? (
- <SaveButton capturedImageURI={mediaFromGallery} />
- ) : (
- <FlipButton cameraType={cameraType} setCameraType={setCameraType} />
- )}
+ <FlipButton cameraType={cameraType} setCameraType={setCameraType} />
<TouchableOpacity
+ style={
+ isRecording
+ ? styles.captureButtonVideoContainer
+ : styles.captureButtonContainer
+ }
activeOpacity={1}
onLongPress={() => {
takeVideo(cameraRef, (vid) => {
@@ -138,16 +128,8 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => {
}
}}
onPress={() => {
- takePicture(cameraRef, (pic) => {
- setShowSaveButton(true);
- setMediaFromGallery(pic.uri);
- });
- }}
- style={
- isRecording
- ? styles.captureButtonVideoContainer
- : styles.captureButtonContainer
- }>
+ takePicture(cameraRef, (pic) => navigateToEditMedia(pic.uri));
+ }}>
<View style={styles.captureButton} />
</TouchableOpacity>
{isRecording && (
@@ -163,32 +145,20 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => {
/>
)}
<View style={styles.bottomRightContainer}>
- {mediaFromGallery ? (
- <TaggSquareButton
- onPress={() => navigateToCaptionScreen(false, mediaFromGallery)}
- title={'Next'}
- buttonStyle={'large'}
- buttonColor={'blue'}
- labelColor={'white'}
- style={styles.nextButton}
- labelStyle={styles.nextButtonLabel}
- />
- ) : (
- <GalleryIcon
- mostRecentPhotoUri={mostRecentPhoto}
- callback={(media) => {
- const filename = media.filename;
- if (
- filename &&
- (filename.endsWith('gif') || filename.endsWith('GIF'))
- ) {
- showGIFFailureAlert(() => navigateToCropper(media.path));
- } else {
- navigateToCropper(media.path);
- }
- }}
- />
- )}
+ <GalleryIcon
+ mostRecentPhotoUri={mostRecentPhoto}
+ callback={(media) => {
+ const filename = media.filename;
+ if (
+ filename &&
+ (filename.endsWith('gif') || filename.endsWith('GIF'))
+ ) {
+ showGIFFailureAlert(() => navigateToEditMedia(media.path));
+ } else {
+ navigateToEditMedia(media.path);
+ }
+ }}
+ />
</View>
</View>
</View>
@@ -254,19 +224,6 @@ const styles = StyleSheet.create({
alignItems: 'center',
width: (SCREEN_WIDTH - 100) / 2,
},
- nextButton: {
- zIndex: 1,
- width: normalize(100),
- height: normalize(37),
- borderRadius: 10,
- },
- nextButtonLabel: {
- fontWeight: '700',
- fontSize: normalize(15),
- lineHeight: normalize(17.8),
- letterSpacing: normalize(1.3),
- textAlign: 'center',
- },
});
export default CameraScreen;
diff --git a/src/screens/profile/CaptionScreen.tsx b/src/screens/profile/CaptionScreen.tsx
index d07743ad..7f77bdca 100644
--- a/src/screens/profile/CaptionScreen.tsx
+++ b/src/screens/profile/CaptionScreen.tsx
@@ -49,7 +49,6 @@ import {RootState} from '../../store/rootReducer';
import {MomentTagType} from '../../types';
import {isIPhoneX, normalize, SCREEN_WIDTH, StatusBarHeight} from '../../utils';
import {mentionPartTypes} from '../../utils/comments';
-
/**
* Upload Screen to allow users to upload posts to Tagg
*/
@@ -362,6 +361,7 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => {
</SearchBackground>
);
};
+
const styles = StyleSheet.create({
contentContainer: {
paddingTop: StatusBarHeight,
diff --git a/src/screens/upload/EditMedia.tsx b/src/screens/upload/EditMedia.tsx
new file mode 100644
index 00000000..1dc408ee
--- /dev/null
+++ b/src/screens/upload/EditMedia.tsx
@@ -0,0 +1,495 @@
+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 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 {SaveButton, TrimmerPlayer} from '../../components';
+import {TaggLoadingIndicator, TaggSquareButton} from '../../components/common';
+import {MainStackParams} from '../../routes';
+import {
+ cropVideo,
+ HeaderHeight,
+ normalize,
+ saveImageToGallery,
+ SCREEN_HEIGHT,
+ SCREEN_WIDTH,
+ trimVideo,
+} from '../../utils';
+
+type EditMediaRouteProps = RouteProp<MainStackParams, 'EditMedia'>;
+type EditMediaNavigationProps = StackNavigationProp<
+ MainStackParams,
+ 'EditMedia'
+>;
+interface EditMediaProps {
+ route: EditMediaRouteProps;
+ navigation: EditMediaNavigationProps;
+}
+
+export const EditMedia: React.FC<EditMediaProps> = ({route, navigation}) => {
+ const {
+ screenType,
+ selectedCategory,
+ media: {isVideo},
+ } = route.params;
+ const [aspectRatio, setAspectRatio] = useState<number>(1);
+ // width and height of video, if video
+ const [origDimensions, setOrigDimensions] = useState<number[]>([0, 0]);
+ const [mediaUri, setMediaUri] = useState<string>(route.params.media.uri);
+ const vidRef = useRef<View>(null);
+ const [cropLoading, setCropLoading] = useState<boolean>(false);
+ const [hideTrimmer, setHideTrimmer] = useState<boolean>(true);
+
+ // Stores the coordinates of the cropped image
+ const [x0, setX0] = useState<number>();
+ const [x1, setX1] = useState<number>();
+ 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;
+ }>({});
+
+ // Stores the current trim endpoints
+ const [trimEnds, setTrimEnds] = useState<{
+ end: number;
+ start: number;
+ }>({
+ end: 60,
+ start: 0,
+ });
+
+ // Setting original aspect ratio of image
+ useEffect(() => {
+ if (mediaUri && !isVideo) {
+ Image.getSize(
+ mediaUri,
+ (w, h) => {
+ setAspectRatio(w / h);
+ },
+ (err) => console.log(err),
+ );
+ } else if (mediaUri && isVideo) {
+ setVideoCrop((prevState) => ({
+ ...prevState,
+ cropWidth: origDimensions[0],
+ cropHeight: origDimensions[1],
+ }));
+ }
+ }, []);
+
+ // Possible need to delay setting aspect ratio of video until loaded
+ useEffect(() => {
+ if (mediaUri && isVideo) {
+ setVideoCrop((prevState) => ({
+ ...prevState,
+ cropWidth: origDimensions[0],
+ cropHeight: origDimensions[1],
+ }));
+ }
+ }, [origDimensions]);
+
+ // Crops original image based of (x0, y0) and (x1, y1) coordinates
+ const processVideo = (callback: (finalUri: string) => void) => {
+ if (!isVideo) {
+ if (
+ x0 !== undefined &&
+ x1 !== undefined &&
+ y0 !== undefined &&
+ y1 !== undefined
+ ) {
+ PhotoManipulator.crop(mediaUri, {
+ x: x0,
+ y: y1,
+ width: Math.abs(x0 - x1),
+ height: Math.abs(y0 - y1),
+ })
+ .then((croppedURL) => {
+ // Pass the cropped image
+ callback(croppedURL);
+ })
+ .catch((err) => console.log('err: ', err));
+ } else if (
+ x0 === undefined &&
+ x1 === undefined &&
+ y0 === undefined &&
+ y1 === undefined
+ ) {
+ // If no crop coordinates are set, then we will just pass the original image
+ callback(mediaUri);
+ }
+ } else {
+ if (!videoCrop.cropHeight || !videoCrop.cropWidth) {
+ setVideoCrop((prevState) => ({
+ ...prevState,
+ cropWidth: origDimensions[0],
+ cropHeight: origDimensions[1],
+ }));
+ }
+ setCropLoading(true);
+ cropVideo(
+ mediaUri,
+ (croppedURL: string) => {
+ setCropLoading(false);
+ // Pass the trimmed/cropped video
+ callback(croppedURL);
+ },
+ 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,
+ }));
+ },
+ );
+ }
+ };
+
+ /* 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(
+ mediaUri,
+ (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 (
+ <View style={styles.container}>
+ {cropLoading && <TaggLoadingIndicator fullscreen />}
+ {hideTrimmer && (
+ <TouchableOpacity
+ style={styles.closeButton}
+ onPress={() => navigation.goBack()}>
+ <CloseIcon height={25} width={25} color={'white'} />
+ </TouchableOpacity>
+ )}
+ {!hideTrimmer && (
+ <View style={styles.topContainer}>
+ <TouchableOpacity onPress={() => setHideTrimmer(true)}>
+ <Text style={styles.bigText}>Cancel</Text>
+ </TouchableOpacity>
+ <TouchableOpacity
+ onPress={() =>
+ trimVideo(
+ mediaUri,
+ (trimmedUri: string) => {
+ setCropLoading(true);
+ setMediaUri(trimmedUri);
+ setTimeout(() => {
+ setHideTrimmer(true);
+ setCropLoading(false);
+ }, 500);
+ },
+ trimEnds,
+ )
+ }>
+ <Text style={styles.bigText}>Save</Text>
+ </TouchableOpacity>
+ </View>
+ )}
+ {!isVideo ? (
+ <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: mediaUri,
+ }}
+ />
+ </ImageZoom>
+ ) : (
+ <ReactNativeZoomableView
+ maxZoom={10}
+ minZoom={1}
+ zoomStep={0.5}
+ initialZoom={1}
+ bindToBorders={true}
+ zoomEnabled={hideTrimmer}
+ 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);
+ }}
+ 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}>
+ <TrimmerPlayer
+ hideTrimmer={hideTrimmer}
+ source={mediaUri}
+ videoStyles={[
+ styles.media,
+ {
+ height: SCREEN_WIDTH / aspectRatio,
+ },
+ ]}
+ handleLoad={(response: {width: number; height: number}) => {
+ const {width, height} = response;
+ setOrigDimensions([width, height]);
+ setAspectRatio(width / height);
+ }}
+ onChangedEndpoints={(response: {start: number; end: number}) =>
+ setTrimEnds(response)
+ }
+ />
+ </View>
+ </ReactNativeZoomableView>
+ )}
+ {isVideo && hideTrimmer && (
+ <View style={styles.iconCarrier}>
+ <TouchableOpacity
+ style={styles.iconContainer}
+ onPress={() => setHideTrimmer((state) => !state)}>
+ <TrimIcon />
+ <Text style={styles.iconText}>Trim</Text>
+ </TouchableOpacity>
+ <TouchableOpacity
+ style={styles.iconContainer}
+ // TODO: finish me
+ onPress={() => null}>
+ <Image
+ style={styles.volumnIcon}
+ source={require('../../assets/images/volume-on.png')}
+ />
+ <Text style={styles.iconText}>Volume</Text>
+ </TouchableOpacity>
+ </View>
+ )}
+ {hideTrimmer && (
+ <View style={styles.bottomContainer}>
+ <SaveButton
+ style={styles.saveButton}
+ onPress={() =>
+ processVideo((uri) =>
+ saveImageToGallery(uri, isVideo ? 'video' : 'photo'),
+ )
+ }
+ />
+ <TaggSquareButton
+ style={styles.button}
+ onPress={() =>
+ processVideo((uri) =>
+ navigation.navigate('CaptionScreen', {
+ screenType,
+ media: {
+ uri: uri,
+ isVideo: isVideo,
+ },
+ selectedCategory,
+ }),
+ )
+ }
+ title={'Next'}
+ buttonStyle={'large'}
+ buttonColor={'blue'}
+ labelColor={'white'}
+ labelStyle={styles.buttonLabel}
+ />
+ </View>
+ )}
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ backgroundColor: 'black',
+ height: SCREEN_HEIGHT,
+ width: SCREEN_WIDTH,
+ },
+ closeButton: {
+ position: 'absolute',
+ top: 0,
+ paddingTop: HeaderHeight,
+ zIndex: 1,
+ marginLeft: '5%',
+ },
+ bottomContainer: {
+ position: 'absolute',
+ bottom: SCREEN_HEIGHT * 0.1,
+ width: SCREEN_WIDTH * 0.8,
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ alignSelf: 'center',
+ flexDirection: 'row',
+ },
+ topContainer: {
+ position: 'absolute',
+ top: SCREEN_HEIGHT * 0.1,
+ width: SCREEN_WIDTH * 0.9,
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ alignSelf: 'center',
+ flexDirection: 'row',
+ zIndex: 1,
+ },
+ bigText: {
+ fontSize: normalize(15),
+ color: 'white',
+ fontWeight: 'bold',
+ },
+ saveButton: {
+ width: 50,
+ },
+ button: {
+ width: normalize(108),
+ height: normalize(36),
+ },
+ 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.1,
+ flexDirection: 'column',
+ justifyContent: 'space-evenly',
+ alignItems: 'center',
+ },
+ iconContainer: {
+ height: 50,
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+ iconText: {
+ color: 'white',
+ fontSize: normalize(11),
+ fontWeight: 'bold',
+ },
+ 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';