diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/assets/icons/trim.svg | 14 | ||||
-rw-r--r-- | src/assets/images/volume-on.png | bin | 0 -> 5479 bytes | |||
-rw-r--r-- | src/components/camera/SaveButton.tsx | 14 | ||||
-rw-r--r-- | src/components/moments/TrimmerPlayer.tsx | 124 | ||||
-rw-r--r-- | src/components/moments/index.ts | 1 | ||||
-rw-r--r-- | src/routes/main/MainStackNavigator.tsx | 2 | ||||
-rw-r--r-- | src/routes/main/MainStackScreen.tsx | 6 | ||||
-rw-r--r-- | src/routes/tabs/NavigationBar.tsx | 6 | ||||
-rw-r--r-- | src/screens/index.ts | 1 | ||||
-rw-r--r-- | src/screens/moments/CameraScreen.tsx | 121 | ||||
-rw-r--r-- | src/screens/profile/CaptionScreen.tsx | 2 | ||||
-rw-r--r-- | src/screens/upload/EditMedia.tsx (renamed from src/components/comments/ZoomInCropper.tsx) | 286 | ||||
-rw-r--r-- | src/screens/upload/index.ts | 1 | ||||
-rw-r--r-- | src/utils/camera.ts | 41 | ||||
-rw-r--r-- | src/utils/users.ts | 1 |
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 Binary files differnew file mode 100644 index 00000000..7cbbaa84 --- /dev/null +++ b/src/assets/images/volume-on.png 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) |