aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Chen <ivan@tagg.id>2021-07-16 21:13:39 -0400
committerGitHub <noreply@github.com>2021-07-16 21:13:39 -0400
commitc1b4e35862172b2268a3a53ece0acc807260652e (patch)
tree40703c8d2dd5abf0a24c07ab8932559ebc2f9cd5
parentc22c19c9eeb28641d36cb9df38fe95301e0f768c (diff)
parent4ebb552aef8c5f6136c9359c54f2e4e1aa921241 (diff)
Merge branch 'master' into tma988-remove-positioned-tags-for-video-moments
-rw-r--r--src/assets/icons/trim.svg14
-rw-r--r--src/assets/images/volume-on.pngbin0 -> 5479 bytes
-rw-r--r--src/components/camera/SaveButton.tsx14
-rw-r--r--src/components/moments/TrimmerPlayer.tsx124
-rw-r--r--src/components/moments/index.ts1
-rw-r--r--src/routes/main/MainStackNavigator.tsx2
-rw-r--r--src/routes/main/MainStackScreen.tsx6
-rw-r--r--src/routes/tabs/NavigationBar.tsx6
-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.tsx (renamed from src/components/comments/ZoomInCropper.tsx)286
-rw-r--r--src/screens/upload/index.ts1
-rw-r--r--src/utils/camera.ts41
-rw-r--r--src/utils/users.ts1
15 files changed, 415 insertions, 205 deletions
diff --git a/src/assets/icons/trim.svg b/src/assets/icons/trim.svg
new file mode 100644
index 00000000..b966c7f7
--- /dev/null
+++ b/src/assets/icons/trim.svg
@@ -0,0 +1,14 @@
+<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M24.9544 12.5523C24.5778 11.8828 23.612 11.6806 23.1761 11.6632L23.0715 3.08577C23.0715 2.33264 22.5834 1.97001 22.3393 1.88285H2.72636C2.01506 1.92469 1.94184 2.38842 1.99414 2.61506V21.9665C1.99414 22.7197 2.48228 22.9777 2.72636 23.0126H11.5653C11.3979 23.5565 12.0534 24.5642 12.4021 25H2.72636C0.38326 24.9163 -0.0630441 23.1869 0.00669092 22.3326V2.61506C0.00669092 0.523013 1.8198 0 2.72636 0H22.3393C24.4314 0.125523 24.9544 1.79568 24.9544 2.61506V12.5523Z" fill="white"/>
+<circle cx="22.182" cy="16.5267" r="1.5" stroke="white" stroke-width="1.39331"/>
+<path d="M9.99707 15.7949H20.2481C20.0389 16.2552 20.1609 16.9979 20.2481 17.3117H11.6184C10.6979 17.1861 10.154 16.2482 9.99707 15.7949Z" fill="white"/>
+<circle r="1.5" transform="matrix(4.37114e-08 1 1 -4.37114e-08 16.3246 22.3324)" stroke="white" stroke-width="1.39331"/>
+<path d="M15.5928 10.1465L15.5928 20.3975C16.053 20.1883 16.7957 20.3104 17.1095 20.3975L17.1095 11.7678C16.984 10.8473 16.0461 10.3034 15.5928 10.1465Z" fill="white"/>
+<rect x="3.56348" y="3.97461" width="2.30126" height="1.67364" rx="0.83682" fill="white"/>
+<rect x="3.56348" y="19.3516" width="2.30126" height="1.67364" rx="0.83682" fill="white"/>
+<rect x="7.43359" y="3.97461" width="2.30126" height="1.67364" rx="0.83682" fill="white"/>
+<rect x="7.43359" y="19.3516" width="2.30126" height="1.67364" rx="0.83682" fill="white"/>
+<rect x="11.3037" y="3.97461" width="2.30126" height="1.67364" rx="0.83682" fill="white"/>
+<rect x="15.1738" y="3.97461" width="2.30126" height="1.67364" rx="0.83682" fill="white"/>
+<rect x="19.0449" y="3.97461" width="2.30126" height="1.67364" rx="0.83682" fill="white"/>
+</svg>
diff --git a/src/assets/images/volume-on.png b/src/assets/images/volume-on.png
new file mode 100644
index 00000000..7cbbaa84
--- /dev/null
+++ b/src/assets/images/volume-on.png
Binary files differ
diff --git a/src/components/camera/SaveButton.tsx b/src/components/camera/SaveButton.tsx
index 0e220497..d1b87e65 100644
--- a/src/components/camera/SaveButton.tsx
+++ b/src/components/camera/SaveButton.tsx
@@ -1,23 +1,19 @@
import React from 'react';
-import {Text, TouchableOpacity} from 'react-native';
+import {StyleProp, Text, TouchableOpacity, ViewStyle} from 'react-native';
import SaveIcon from '../../assets/icons/camera/save.svg';
-import {saveImageToGallery} from '../../utils/camera';
import {styles} from './styles';
interface SaveButtonProps {
- capturedImageURI: string;
+ onPress: () => void;
+ style?: StyleProp<ViewStyle>;
}
/*
* Appears when a picture has been taken,
* On click, saves the captured image to "Recents" album on device gallery
*/
-export const SaveButton: React.FC<SaveButtonProps> = ({capturedImageURI}) => (
- <TouchableOpacity
- onPress={() => {
- saveImageToGallery(capturedImageURI);
- }}
- style={styles.saveButton}>
+export const SaveButton: React.FC<SaveButtonProps> = ({onPress, style}) => (
+ <TouchableOpacity onPress={onPress} style={[styles.saveButton, style]}>
<SaveIcon width={40} height={40} />
<Text style={styles.saveButtonLabel}>Save</Text>
</TouchableOpacity>
diff --git a/src/components/moments/TrimmerPlayer.tsx b/src/components/moments/TrimmerPlayer.tsx
new file mode 100644
index 00000000..b28df590
--- /dev/null
+++ b/src/components/moments/TrimmerPlayer.tsx
@@ -0,0 +1,124 @@
+import React, {useEffect, useRef, useState} from 'react';
+import {StyleSheet, View} from 'react-native';
+import Video from 'react-native-video';
+import {Trimmer} from 'react-native-video-processing';
+import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+
+interface TrimmerPlayerProps {
+ source: string;
+ videoStyles: Object;
+ hideTrimmer: boolean;
+ handleLoad: Function;
+ onChangedEndpoints: Function;
+}
+
+const TrimmerPlayer: React.FC<TrimmerPlayerProps> = ({
+ source,
+ videoStyles,
+ hideTrimmer,
+ handleLoad,
+ onChangedEndpoints,
+}) => {
+ // Stores the reference to player for seeking
+ const playerRef = useRef<Video>();
+ // Stores where the video is playing (seekTime)
+ const [seekTime, setSeekTime] = useState<number>(0);
+ const [paused, setPaused] = useState<boolean>(false);
+ // Stores where the tracker is
+ const [trackerTime, setTrackerTime] = useState<number>(0);
+ // Stores start/end of desired trimmed video
+ const [end, setEnd] = useState<number>(60);
+ const [start, setStart] = useState<number>(0);
+
+ useEffect(() => {
+ playerRef.current?.seek(seekTime);
+ }, [seekTime]);
+ useEffect(() => {
+ if (!paused && (trackerTime >= end || trackerTime < start)) {
+ setTrackerTime(start);
+ playerRef.current?.seek(start);
+ }
+ }, [trackerTime]);
+ useEffect(() => {
+ setSeekTime(start);
+ setTrackerTime(start);
+ }, [start]);
+ useEffect(() => {
+ setSeekTime(end);
+ setTrackerTime(start);
+ }, [end]);
+ // Callback so parent knows where the trimming endpts are
+ useEffect(() => onChangedEndpoints({end, start}), [end, start]);
+
+ useEffect(() => {
+ playerRef.current?.seek(0);
+ }, [hideTrimmer]);
+
+ return (
+ <>
+ <Video
+ // link to descr and use of props of video player ->
+ // https://github.com/react-native-video/react-native-video
+ ref={(ref) => {
+ playerRef.current = ref || undefined;
+ }}
+ source={{uri: source}}
+ rate={1.0}
+ volume={1.0}
+ muted={false}
+ paused={paused}
+ resizeMode={'contain'}
+ repeat={true}
+ onLoad={(payload) => {
+ setEnd(payload.duration);
+ handleLoad(payload.naturalSize);
+ }}
+ onProgress={(e) => {
+ if (!paused) {
+ setTrackerTime(e.currentTime);
+ }
+ }} // Callback every ~250ms with currentTime
+ style={videoStyles}
+ onTouchEnd={() => {
+ setPaused((state) => !state);
+ }}
+ />
+ {!hideTrimmer && (
+ <View style={styles.trimmerContainer}>
+ <Trimmer
+ // link to descr and use of props for trimmer ->
+ // https://github.com/shahen94/react-native-video-processing
+ source={source}
+ height={75}
+ width={SCREEN_WIDTH}
+ onTrackerMove={(e: {currentTime: number}) => {
+ setPaused(true);
+ setSeekTime(e.currentTime);
+ }}
+ currentTime={trackerTime}
+ themeColor={'white'}
+ thumbWidth={10}
+ trackerColor={'white'}
+ onChange={(e: {endTime: number; startTime: number}) => {
+ setPaused(true);
+ setEnd(e.endTime);
+ setStart(e.startTime);
+ }}
+ />
+ </View>
+ )}
+ </>
+ );
+};
+
+const styles = StyleSheet.create({
+ trimmerContainer: {
+ position: 'absolute',
+ bottom: SCREEN_HEIGHT * 0.1,
+ alignItems: 'center',
+ borderWidth: 1,
+ borderColor: 'red',
+ },
+});
+
+export default TrimmerPlayer;
diff --git a/src/components/moments/index.ts b/src/components/moments/index.ts
index 2cc4c9dd..16c9aed2 100644
--- a/src/components/moments/index.ts
+++ b/src/components/moments/index.ts
@@ -4,3 +4,4 @@ export {default as Moment} from './Moment';
export {default as TagFriendsFooter} from './TagFriendsFoooter';
export {default as MomentPost} from './MomentPost';
export {default as TaggedUsersDrawer} from './TaggedUsersDrawer';
+export {default as TrimmerPlayer} from './TrimmerPlayer';
diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx
index c569d2d6..11e9d08d 100644
--- a/src/routes/main/MainStackNavigator.tsx
+++ b/src/routes/main/MainStackNavigator.tsx
@@ -39,7 +39,7 @@ export type MainStackParams = {
screenType: ScreenType;
selectedCategory?: string;
};
- ZoomInCropper: {
+ EditMedia: {
media: {uri: string; isVideo: boolean};
screenType: ScreenType;
selectedCategory?: string;
diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx
index 15300c0d..064e9725 100644
--- a/src/routes/main/MainStackScreen.tsx
+++ b/src/routes/main/MainStackScreen.tsx
@@ -34,12 +34,12 @@ import {
TagSelectionScreen,
TagFriendsScreen,
CameraScreen,
+ EditMedia,
} from '../../screens';
import MutualBadgeHolders from '../../screens/suggestedPeople/MutualBadgeHolders';
import {ScreenType} from '../../types';
import {AvatarHeaderHeight, ChatHeaderHeight, SCREEN_WIDTH} from '../../utils';
import {MainStack, MainStackParams} from './MainStackNavigator';
-import {ZoomInCropper} from '../../components/comments/ZoomInCropper';
import ChoosingCategoryScreen from '../../screens/profile/ChoosingCategoryScreen';
/**
@@ -336,8 +336,8 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
}}
/>
<MainStack.Screen
- name="ZoomInCropper"
- component={ZoomInCropper}
+ name="EditMedia"
+ component={EditMedia}
options={{
...modalStyle,
gestureEnabled: false,
diff --git a/src/routes/tabs/NavigationBar.tsx b/src/routes/tabs/NavigationBar.tsx
index c3f0b9f8..a3584f55 100644
--- a/src/routes/tabs/NavigationBar.tsx
+++ b/src/routes/tabs/NavigationBar.tsx
@@ -6,7 +6,7 @@ import {NO_NOTIFICATIONS} from '../../store/initialStates';
import {RootState} from '../../store/rootReducer';
import {setNotificationsReadDate} from '../../services';
import {ScreenType} from '../../types';
-import {haveUnreadNotifications} from '../../utils';
+import {haveUnreadNotifications, isIPhoneX} from '../../utils';
import MainStackScreen from '../main/MainStackScreen';
import {NotificationPill} from '../../components/notifications';
@@ -84,9 +84,7 @@ const NavigationBar: React.FC = () => {
backgroundColor: 'transparent',
position: 'absolute',
borderTopWidth: 0,
- left: 0,
- right: 0,
- bottom: '1%',
+ height: isIPhoneX() ? 85 : 55,
},
}}>
<Tabs.Screen
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/components/comments/ZoomInCropper.tsx b/src/screens/upload/EditMedia.tsx
index 6f8ff97c..1dc408ee 100644
--- a/src/components/comments/ZoomInCropper.tsx
+++ b/src/screens/upload/EditMedia.tsx
@@ -1,41 +1,48 @@
+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, TouchableOpacity, View} from 'react-native';
-import {normalize} from 'react-native-elements';
+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';
-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<
+type EditMediaRouteProps = RouteProp<MainStackParams, 'EditMedia'>;
+type EditMediaNavigationProps = StackNavigationProp<
MainStackParams,
- 'ZoomInCropper'
+ 'EditMedia'
>;
-interface ZoomInCropperProps {
- route: ZoomInCropperRouteProps;
- navigation: ZoomInCropperNavigationProps;
+interface EditMediaProps {
+ route: EditMediaRouteProps;
+ navigation: EditMediaNavigationProps;
}
-export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({
- route,
- navigation,
-}) => {
- const {screenType, media, selectedCategory} = route.params;
+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>();
@@ -51,28 +58,26 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({
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')
- );
- };
+ // 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 (media.uri && checkIfUriImage(media.uri)) {
+ if (mediaUri && !isVideo) {
Image.getSize(
- media.uri,
+ mediaUri,
(w, h) => {
setAspectRatio(w / h);
},
(err) => console.log(err),
);
- } else if (media.uri && !checkIfUriImage(media.uri)) {
+ } else if (mediaUri && isVideo) {
setVideoCrop((prevState) => ({
...prevState,
cropWidth: origDimensions[0],
@@ -83,7 +88,7 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({
// Possible need to delay setting aspect ratio of video until loaded
useEffect(() => {
- if (media.uri && !checkIfUriImage(media.uri)) {
+ if (mediaUri && isVideo) {
setVideoCrop((prevState) => ({
...prevState,
cropWidth: origDimensions[0],
@@ -93,28 +98,23 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({
}, [origDimensions]);
// Crops original image based of (x0, y0) and (x1, y1) coordinates
- const handleNext = () => {
- if (checkIfUriImage(media.uri)) {
+ const processVideo = (callback: (finalUri: string) => void) => {
+ if (!isVideo) {
if (
x0 !== undefined &&
x1 !== undefined &&
y0 !== undefined &&
y1 !== undefined
) {
- PhotoManipulator.crop(media.uri, {
+ PhotoManipulator.crop(mediaUri, {
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,
- },
- });
+ // Pass the cropped image
+ callback(croppedURL);
})
.catch((err) => console.log('err: ', err));
} else if (
@@ -123,10 +123,8 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({
y0 === undefined &&
y1 === undefined
) {
- navigation.navigate('CaptionScreen', {
- screenType,
- media,
- });
+ // If no crop coordinates are set, then we will just pass the original image
+ callback(mediaUri);
}
} else {
if (!videoCrop.cropHeight || !videoCrop.cropWidth) {
@@ -136,17 +134,13 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({
cropHeight: origDimensions[1],
}));
}
+ setCropLoading(true);
cropVideo(
- media.uri,
+ mediaUri,
(croppedURL: string) => {
- navigation.navigate('CaptionScreen', {
- screenType,
- media: {
- uri: croppedURL,
- isVideo: true,
- },
- selectedCategory,
- });
+ setCropLoading(false);
+ // Pass the trimmed/cropped video
+ callback(croppedURL);
},
videoCrop,
);
@@ -220,7 +214,7 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({
*/
const onMove = (position: IOnMove) => {
Image.getSize(
- media.uri,
+ mediaUri,
(w, h) => {
const x = position.positionX;
const y = position.positionY;
@@ -255,12 +249,39 @@ 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>
- {checkIfUriImage(media.uri) ? (
+ {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}
@@ -271,7 +292,7 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({
<Image
style={{width: SCREEN_WIDTH, height: SCREEN_WIDTH / aspectRatio}}
source={{
- uri: media.uri,
+ uri: mediaUri,
}}
/>
</ImageZoom>
@@ -282,7 +303,7 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({
zoomStep={0.5}
initialZoom={1}
bindToBorders={true}
- // onZoomAfter={this.logOutZoomState}
+ zoomEnabled={hideTrimmer}
onDoubleTapAfter={(
_1: any,
_2: any,
@@ -304,36 +325,79 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({
}}
style={styles.zoomView}>
<View style={styles.videoParent} ref={vidRef}>
- <Video
- source={{uri: media.uri}}
- volume={1}
- style={[
+ <TrimmerPlayer
+ hideTrimmer={hideTrimmer}
+ source={mediaUri}
+ videoStyles={[
styles.media,
{
height: SCREEN_WIDTH / aspectRatio,
},
]}
- repeat={true}
- resizeMode={'contain'}
- onLoad={(response) => {
- const {width, height} = response.naturalSize;
+ 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>
)}
-
- <TaggSquareButton
- onPress={handleNext}
- title={'Next'}
- buttonStyle={'normal'}
- buttonColor={'blue'}
- labelColor={'white'}
- style={styles.button}
- labelStyle={styles.buttonLabel}
- />
+ {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>
);
};
@@ -351,14 +415,36 @@ const styles = StyleSheet.create({
zIndex: 1,
marginLeft: '5%',
},
- button: {
- zIndex: 1,
+ 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',
- bottom: normalize(20),
- right: normalize(15),
+ 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(25),
- borderRadius: 10,
+ height: normalize(36),
},
buttonLabel: {
fontWeight: '700',
@@ -367,6 +453,28 @@ const styles = StyleSheet.create({
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,
@@ -378,4 +486,10 @@ const styles = StyleSheet.create({
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';
diff --git a/src/utils/camera.ts b/src/utils/camera.ts
index 9e37d62e..9d7ff67f 100644
--- a/src/utils/camera.ts
+++ b/src/utils/camera.ts
@@ -9,7 +9,7 @@ import {
TakePictureResponse,
} from 'react-native-camera';
import {ProcessingManager} from 'react-native-video-processing';
-import ImagePicker, {ImageOrVideo, Video} from 'react-native-image-crop-picker';
+import ImagePicker, {ImageOrVideo} from 'react-native-image-crop-picker';
import {ERROR_UPLOAD} from '../constants/strings';
/*
@@ -48,8 +48,11 @@ export const takeVideo = (
}
};
-export const saveImageToGallery = (capturedImageURI: string) => {
- CameraRoll.save(capturedImageURI, {album: 'Recents', type: 'photo'})
+export const saveImageToGallery = (
+ capturedImageURI: string,
+ type: 'photo' | 'video',
+) => {
+ CameraRoll.save(capturedImageURI, {album: 'Recents', type: type})
.then((_res) => Alert.alert('Saved to device!'))
.catch((_err) => Alert.alert('Failed to save to device!'));
};
@@ -66,6 +69,7 @@ export const navigateToImagePicker = (
'UserLibrary',
],
mediaType: 'any',
+ compressVideoPreset: 'Passthrough',
})
.then((media) => {
callback(media);
@@ -77,22 +81,6 @@ export const navigateToImagePicker = (
});
};
-export const navigateToVideoPicker = (callback: (vid: Video) => void) => {
- ImagePicker.openPicker({
- mediaType: 'video',
- })
- .then(async (vid) => {
- if (vid.path) {
- callback(vid);
- }
- })
- .catch((err) => {
- if (err.code && err.code !== 'E_PICKER_CANCELLED') {
- Alert.alert(ERROR_UPLOAD);
- }
- });
-};
-
export const showGIFFailureAlert = (onSuccess: () => void) =>
Alert.alert(
'Warning',
@@ -118,6 +106,21 @@ export const showGIFFailureAlert = (onSuccess: () => void) =>
},
);
+export const trimVideo = (
+ sourceUri: string,
+ handleData: (data: any) => any,
+ ends: {
+ start: number;
+ end: number;
+ },
+) => {
+ ProcessingManager.trim(sourceUri, {
+ startTime: ends.start / 2, //needed divide by 2 for bug in module
+ endTime: ends.end,
+ quality: 'passthrough',
+ }).then((data: any) => handleData(data));
+};
+
export const cropVideo = (
sourceUri: string,
handleData: (data: any) => any,
diff --git a/src/utils/users.ts b/src/utils/users.ts
index c1c3b8bc..992d7721 100644
--- a/src/utils/users.ts
+++ b/src/utils/users.ts
@@ -280,6 +280,7 @@ export const patchProfile = async (
screenTitle = '';
requestTitle = '';
fileName = '';
+ imageSettings = {};
}
return await ImagePicker.openPicker(imageSettings)