diff options
-rw-r--r-- | package.json | 7 | ||||
-rw-r--r-- | src/components/camera/GalleryIcon.tsx | 4 | ||||
-rw-r--r-- | src/components/moments/Moment.tsx | 45 | ||||
-rw-r--r-- | src/screens/moments/CameraScreen.tsx | 87 | ||||
-rw-r--r-- | src/utils/camera.ts | 32 | ||||
-rw-r--r-- | yarn.lock | 9 |
6 files changed, 109 insertions, 75 deletions
diff --git a/package.json b/package.json index 63203775..42e94b39 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "react-native": "0.63.3", "react-native-animatable": "^1.3.3", "react-native-camera": "^3.44.1", + "react-native-circular-progress": "^1.3.7", "react-native-confirmation-code-field": "^6.5.0", "react-native-contacts": "^6.0.4", "react-native-controlled-mentions": "^2.2.5", @@ -47,8 +48,8 @@ "react-native-haptic-feedback": "^1.11.0", "react-native-hyperlink": "^0.0.19", "react-native-image-crop-picker": "^0.36.0", - "react-native-image-picker": "^4.0.4", "react-native-image-pan-zoom": "^2.1.12", + "react-native-image-picker": "^4.0.4", "react-native-image-resizer": "^1.4.4", "react-native-inappbrowser-reborn": "^3.5.0", "react-native-linear-gradient": "^2.5.6", @@ -61,7 +62,7 @@ "react-native-share": "^5.1.7", "react-native-snap-carousel": "^3.9.1", "react-native-splash-screen": "^3.2.0", - "react-native-svg": "^12.1.0", + "react-native-svg": "^12.1.1", "react-native-vector-icons": "^7.0.0", "react-native-video": "^5.1.1", "react-promise-tracker": "^2.1.0", @@ -111,4 +112,4 @@ "./node_modules/react-native-gesture-handler/jestSetup.js" ] } -}
\ No newline at end of file +} diff --git a/src/components/camera/GalleryIcon.tsx b/src/components/camera/GalleryIcon.tsx index 8d396550..ca2d2559 100644 --- a/src/components/camera/GalleryIcon.tsx +++ b/src/components/camera/GalleryIcon.tsx @@ -1,12 +1,12 @@ import React from 'react'; import {Image, Text, TouchableOpacity, View} from 'react-native'; import {navigateToImagePicker} from '../../utils/camera'; -import {Image as ImageType} from 'react-native-image-crop-picker'; +import {ImageOrVideo} from 'react-native-image-crop-picker'; import {styles} from './styles'; interface GalleryIconProps { mostRecentPhotoUri: string; - callback: (pic: ImageType) => void; + callback: (media: ImageOrVideo) => void; } /* diff --git a/src/components/moments/Moment.tsx b/src/components/moments/Moment.tsx index 1e1cadce..73503c5e 100644 --- a/src/components/moments/Moment.tsx +++ b/src/components/moments/Moment.tsx @@ -1,6 +1,6 @@ import {useNavigation} from '@react-navigation/native'; import React from 'react'; -import {Alert, StyleProp, StyleSheet, View, ViewStyle} from 'react-native'; +import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'; import {Text} from 'react-native-animatable'; import {ScrollView, TouchableOpacity} from 'react-native-gesture-handler'; import LinearGradient from 'react-native-linear-gradient'; @@ -12,8 +12,6 @@ import UpIcon from '../../assets/icons/up_icon.svg'; import {TAGG_LIGHT_BLUE} from '../../constants'; import {MomentType, ScreenType} from '../../types'; import {normalize, SCREEN_WIDTH} from '../../utils'; -import {navigateToVideoPicker} from '../../utils/camera'; -import ImagePicker from 'react-native-image-crop-picker'; import MomentTile from './MomentTile'; interface MomentProps { @@ -43,17 +41,6 @@ const Moment: React.FC<MomentProps> = ({ }) => { const navigation = useNavigation(); - const navigateToCaptionScreenForVideo = (uri: string) => { - navigation.navigate('CaptionScreen', { - screenType, - title, - media: { - uri, - isVideo: true, - }, - }); - }; - const navigateToCameraScreen = () => { navigation.navigate('CameraScreen', { title, @@ -97,36 +84,6 @@ const Moment: React.FC<MomentProps> = ({ <PlusIcon width={23} height={23} - onPress={() => - Alert.alert('Video Upload', 'pick one', [ - { - text: 'gallery', - onPress: () => - navigateToVideoPicker((vid) => - navigateToCaptionScreenForVideo(vid.path), - ), - }, - { - text: 'camera (simulator will not work)', - onPress: () => - ImagePicker.openCamera({ - mediaType: 'video', - }) - .then((vid) => { - if (vid.path) { - navigateToCaptionScreenForVideo(vid.path); - } - }) - .catch((err) => console.error(err)), - }, - ]) - } - color={'black'} - style={styles.horizontalMargin} - /> - <PlusIcon - width={23} - height={23} onPress={navigateToCameraScreen} color={TAGG_LIGHT_BLUE} style={styles.horizontalMargin} diff --git a/src/screens/moments/CameraScreen.tsx b/src/screens/moments/CameraScreen.tsx index 37b37264..75638c40 100644 --- a/src/screens/moments/CameraScreen.tsx +++ b/src/screens/moments/CameraScreen.tsx @@ -6,6 +6,7 @@ import {StackNavigationProp} from '@react-navigation/stack'; import React, {createRef, useCallback, useEffect, useState} from 'react'; 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, @@ -14,9 +15,10 @@ import { SaveButton, TaggSquareButton, } from '../../components'; +import {TAGG_PURPLE} from '../../constants'; import {MainStackParams} from '../../routes'; import {HeaderHeight, normalize, SCREEN_WIDTH} from '../../utils'; -import {showGIFFailureAlert, takePicture} from '../../utils/camera'; +import {showGIFFailureAlert, takePicture, takeVideo} from '../../utils/camera'; type CameraScreenRouteProps = RouteProp<MainStackParams, 'CameraScreen'>; export type CameraScreenNavigationProps = StackNavigationProp< @@ -33,9 +35,10 @@ 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 [capturedImage, setCapturedImage] = useState<string>(''); + const [mediaFromGallery, setMediaFromGallery] = useState<string>(''); const [mostRecentPhoto, setMostRecentPhoto] = useState<string>(''); const [showSaveButton, setShowSaveButton] = useState<boolean>(false); + const [isRecording, setIsRecording] = useState<boolean>(false); useFocusEffect( useCallback(() => { @@ -64,7 +67,7 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => { .catch((_err) => console.log('Unable to fetch preview photo for gallery'), ); - }, [capturedImage]); + }, [mediaFromGallery]); const navigateToCropper = (uri: string) => { navigation.navigate('ZoomInCropper', { @@ -77,13 +80,13 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => { }); }; - const navigateToCaptionScreen = () => { + const navigateToCaptionScreen = (isVideo: boolean, uri: string) => { navigation.navigate('CaptionScreen', { screenType, title, media: { - uri: capturedImage, - isVideo: false, + uri, + isVideo, }, }); }; @@ -96,7 +99,7 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => { if (showSaveButton) { cameraRef.current?.resumePreview(); setShowSaveButton(false); - setCapturedImage(''); + setMediaFromGallery(''); } else { navigation.goBack(); } @@ -116,24 +119,53 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => { /> <View style={[styles.bottomContainer, {bottom: tabBarHeight}]}> {showSaveButton ? ( - <SaveButton capturedImageURI={capturedImage} /> + <SaveButton capturedImageURI={mediaFromGallery} /> ) : ( <FlipButton cameraType={cameraType} setCameraType={setCameraType} /> )} <TouchableOpacity - onPress={() => + activeOpacity={1} + onLongPress={() => { + takeVideo(cameraRef, (vid) => { + navigateToCaptionScreen(true, vid.uri); + }); + setIsRecording(true); + }} + onPressOut={async () => { + if (await cameraRef.current?.isRecording()) { + cameraRef.current?.stopRecording(); + setIsRecording(false); + } + }} + onPress={() => { takePicture(cameraRef, (pic) => { setShowSaveButton(true); - setCapturedImage(pic.uri); - }) - } - style={styles.captureButtonContainer}> + setMediaFromGallery(pic.uri); + }); + }} + style={ + isRecording + ? styles.captureButtonVideoContainer + : styles.captureButtonContainer + }> <View style={styles.captureButton} /> </TouchableOpacity> + {isRecording && ( + <AnimatedCircularProgress + size={95} + width={6} + fill={100} + rotation={0} + duration={60000 + 1000} // an extra second for UI to load + tintColor={TAGG_PURPLE} + style={styles.bottomContainer} + lineCap={'round'} + /> + )} <View style={styles.bottomRightContainer}> - {capturedImage ? ( + {mediaFromGallery ? ( <TaggSquareButton - onPress={navigateToCaptionScreen} + onPress={() => navigateToCaptionScreen(false, mediaFromGallery)} title={'Next'} buttonStyle={'large'} buttonColor={'blue'} @@ -144,15 +176,20 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => { ) : ( <GalleryIcon mostRecentPhotoUri={mostRecentPhoto} - callback={(pic) => { - const filename = pic.filename; + callback={(media) => { + const filename = media.filename; if ( filename && (filename.endsWith('gif') || filename.endsWith('GIF')) ) { - showGIFFailureAlert(() => navigateToCropper(pic.path)); + showGIFFailureAlert(() => navigateToCropper(media.path)); } else { - navigateToCropper(pic.path); + // is this a video? + if (media.duration !== null) { + navigateToCaptionScreen(true, media.path); + } else { + navigateToCropper(media.path); + } } }} /> @@ -173,6 +210,18 @@ const styles = StyleSheet.create({ flexDirection: 'column', backgroundColor: 'black', }, + captureButtonVideoContainer: { + alignSelf: 'center', + backgroundColor: 'transparent', + borderRadius: 100, + borderWidth: 15, + borderColor: 'rgba(255,255,255,0.3)', + width: 93, + height: 93, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, captureButtonContainer: { alignSelf: 'center', backgroundColor: 'transparent', diff --git a/src/utils/camera.ts b/src/utils/camera.ts index 3937129a..4f933b0c 100644 --- a/src/utils/camera.ts +++ b/src/utils/camera.ts @@ -2,11 +2,13 @@ import CameraRoll from '@react-native-community/cameraroll'; import {RefObject} from 'react'; import {Alert} from 'react-native'; import { + RecordOptions, + RecordResponse, RNCamera, TakePictureOptions, TakePictureResponse, } from 'react-native-camera'; -import ImagePicker, {Image, Video} from 'react-native-image-crop-picker'; +import ImagePicker, {ImageOrVideo, Video} from 'react-native-image-crop-picker'; import {ERROR_UPLOAD} from '../constants/strings'; /* @@ -17,11 +19,11 @@ export const takePicture = ( callback: (pic: TakePictureResponse) => void, ) => { if (cameraRef !== null) { - cameraRef.current?.pausePreview(); const options: TakePictureOptions = { forceUpOrientation: true, orientation: 'portrait', writeExif: false, + pauseAfterCapture: true, }; cameraRef.current?.takePictureAsync(options).then((pic) => { callback(pic); @@ -29,13 +31,31 @@ export const takePicture = ( } }; +export const takeVideo = ( + cameraRef: RefObject<RNCamera>, + callback: (vid: RecordResponse) => void, +) => { + if (cameraRef !== null) { + const options: RecordOptions = { + orientation: 'portrait', + maxDuration: 60, + quality: '1080p', + }; + cameraRef.current?.recordAsync(options).then((vid) => { + callback(vid); + }); + } +}; + export const saveImageToGallery = (capturedImageURI: string) => { CameraRoll.save(capturedImageURI, {album: 'Recents', type: 'photo'}) .then((_res) => Alert.alert('Saved to device!')) .catch((_err) => Alert.alert('Failed to save to device!')); }; -export const navigateToImagePicker = (callback: (pic: Image) => void) => { +export const navigateToImagePicker = ( + callback: (media: ImageOrVideo) => void, +) => { ImagePicker.openPicker({ smartAlbums: [ 'Favorites', @@ -44,10 +64,10 @@ export const navigateToImagePicker = (callback: (pic: Image) => void) => { 'Screenshots', 'UserLibrary', ], - mediaType: 'photo', + mediaType: 'any', }) - .then((pic) => { - callback(pic); + .then((media) => { + callback(media); }) .catch((err) => { if (err.code && err.code !== 'E_PICKER_CANCELLED') { @@ -6434,6 +6434,13 @@ react-native-camera@^3.44.1: dependencies: prop-types "^15.6.2" +react-native-circular-progress@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/react-native-circular-progress/-/react-native-circular-progress-1.3.7.tgz#cc430fbc612bd01134a8fc9667be107591ae6959" + integrity sha512-Lpe4Sb0Sdfdxopl6sEc3ooKnwlK5j1nKGq5lgi9UKDFKO6iBAszeH+PiUt6acGSRbKMH1av+9G9ni9r/6T0+nQ== + dependencies: + prop-types "^15.7.2" + react-native-confirmation-code-field@^6.5.0: version "6.7.0" resolved "https://registry.yarnpkg.com/react-native-confirmation-code-field/-/react-native-confirmation-code-field-6.7.0.tgz#81f5e646898addb3243cf89d41d884b0762ae962" @@ -6657,7 +6664,7 @@ react-native-svg-transformer@^0.14.3: path-dirname "^1.0.2" semver "^5.6.0" -react-native-svg@^12.1.0: +react-native-svg@^12.1.1: version "12.1.1" resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-12.1.1.tgz#5f292410b8bcc07bbc52b2da7ceb22caf5bcaaee" integrity sha512-NIAJ8jCnXGCqGWXkkJ1GTzO4a3Md5at5sagYV8Vh4MXYnL4z5Rh428Wahjhh+LIjx40EE5xM5YtwyJBqOIba2Q== |