diff options
author | Shravya Ramesh <shravs1208@gmail.com> | 2021-06-25 16:50:00 -0700 |
---|---|---|
committer | Shravya Ramesh <shravs1208@gmail.com> | 2021-06-25 16:50:00 -0700 |
commit | 727c6384a2a07c42cd132d02da8c7dbb5757ea4f (patch) | |
tree | 8019a333d344c93f5f08614b317c23b5c5db8ad4 /src | |
parent | f596a0246a9b9453df3a93c8c3fc5c9137bb50fc (diff) |
Refactor code, Fix orientation bug
Diffstat (limited to 'src')
-rw-r--r-- | src/components/camera/buttons.tsx | 149 | ||||
-rw-r--r-- | src/components/camera/index.ts | 1 | ||||
-rw-r--r-- | src/components/index.ts | 9 | ||||
-rw-r--r-- | src/screens/moments/CameraScreen.tsx | 194 | ||||
-rw-r--r-- | src/utils/camera.ts | 67 |
5 files changed, 263 insertions, 157 deletions
diff --git a/src/components/camera/buttons.tsx b/src/components/camera/buttons.tsx new file mode 100644 index 00000000..321be958 --- /dev/null +++ b/src/components/camera/buttons.tsx @@ -0,0 +1,149 @@ +import {useNavigation} from '@react-navigation/native'; +import React, {Dispatch, SetStateAction} from 'react'; +import {Image, StyleSheet, Text, TouchableOpacity} from 'react-native'; +import {CameraType, FlashMode} from 'react-native-camera'; +import FlashOffIcon from '../../assets/icons/camera/flash-off.svg'; +import FlashOnIcon from '../../assets/icons/camera/flash-on.svg'; +import FlipIcon from '../../assets/icons/camera/flip.svg'; +import SaveIcon from '../../assets/icons/camera/save.svg'; +import {ScreenType} from '../../types'; +import {downloadImage, navigateToImagePicker} from '../../utils/camera'; +import {normalize, SCREEN_WIDTH} from '../../utils/layouts'; + +interface GalleryIconProps { + screenType: ScreenType; + title: string; + mostRecentPhoto: string; +} + +/* + * Displays the most recent photo in the user's gallery + * On click, navigates to the image picker + */ +export const GalleryIcon: React.FC<GalleryIconProps> = ({ + screenType, + title, + mostRecentPhoto, +}) => { + return ( + <TouchableOpacity + onPress={() => navigateToImagePicker(screenType, title)} + style={styles.saveButton}> + <Image + source={{uri: mostRecentPhoto}} + width={40} + height={40} + style={{borderWidth: 2, borderColor: 'white', borderRadius: 5}} + /> + <Text style={styles.saveButtonLabel}>Gallery</Text> + </TouchableOpacity> + ); +}; + +interface FlipButtonProps { + setCameraType: Dispatch<SetStateAction<keyof CameraType>>; + cameraType: keyof CameraType; +} + +/* + * Toggles between back camera and front camera + * Appears only when user has not taken a picture yet + * Once user takes a picture, this button disappears to reveal the save button + */ +export const FlipButton: React.FC<FlipButtonProps> = ({ + setCameraType, + cameraType, +}) => ( + <TouchableOpacity + onPress={() => setCameraType(cameraType === 'front' ? 'back' : 'front')} + style={styles.saveButton}> + <FlipIcon width={40} height={40} /> + <Text style={styles.saveButtonLabel}>Flip</Text> + </TouchableOpacity> +); + +interface FlashButtonProps { + flashMode: keyof FlashMode; + setFlashMode: Dispatch<SetStateAction<keyof FlashMode>>; +} + +/* + * Toggles between flash on/off modes + */ +export const FlashButton: React.FC<FlashButtonProps> = ({ + flashMode, + setFlashMode, +}) => ( + <TouchableOpacity + onPress={() => setFlashMode(flashMode === 'on' ? 'off' : 'on')} + style={styles.flashButtonContainer}> + {flashMode === 'on' ? ( + <FlashOnIcon + height={30} + width={20} + color={'white'} + style={{zIndex: 999}} + /> + ) : ( + <FlashOffIcon + height={30} + width={20} + color={'white'} + style={{zIndex: 999}} + /> + )} + <Text style={styles.saveButtonLabel}>Flash</Text> + </TouchableOpacity> +); + +interface SaveButtonProps { + capturedImageURI: string; +} + +/* + * 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={() => { + downloadImage(capturedImageURI); + }} + style={[styles.saveButton]}> + <SaveIcon width={40} height={40} /> + <Text style={styles.saveButtonLabel}>Save</Text> + </TouchableOpacity> +); + +const styles = StyleSheet.create({ + saveButton: { + zIndex: 1, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + width: (SCREEN_WIDTH - 100) / 2, + }, + saveButtonLabel: { + color: 'white', + fontWeight: '700', + fontSize: normalize(12), + lineHeight: normalize(14.32), + marginTop: 5, + zIndex: 999, + }, + flashButtonContainer: { + position: 'absolute', + backgroundColor: '#808080', + opacity: 0.25, + zIndex: 1, + top: normalize(50), + right: 0, + marginRight: normalize(18), + height: 86, + width: 49, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + borderRadius: 30, + }, +}); diff --git a/src/components/camera/index.ts b/src/components/camera/index.ts new file mode 100644 index 00000000..428d0fe8 --- /dev/null +++ b/src/components/camera/index.ts @@ -0,0 +1 @@ +export * from './buttons'; diff --git a/src/components/index.ts b/src/components/index.ts index 47dc583b..c2f50118 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,9 +1,10 @@ +export * from './camera'; +export * from './comments'; export * from './common'; +export * from './messages'; +export * from './moments'; export * from './onboarding'; export * from './profile'; export * from './search'; -export * from './taggs'; -export * from './comments'; -export * from './moments'; export * from './suggestedPeople'; -export * from './messages'; +export * from './taggs'; diff --git a/src/screens/moments/CameraScreen.tsx b/src/screens/moments/CameraScreen.tsx index 70411a83..4c2633a3 100644 --- a/src/screens/moments/CameraScreen.tsx +++ b/src/screens/moments/CameraScreen.tsx @@ -3,28 +3,22 @@ import {useBottomTabBarHeight} from '@react-navigation/bottom-tabs'; import {RouteProp, useFocusEffect} from '@react-navigation/core'; import {StackNavigationProp} from '@react-navigation/stack'; import React, {createRef, useCallback, useEffect, useState} from 'react'; -import { - Alert, - Image, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; +import {StyleSheet, TouchableOpacity, View} from 'react-native'; import {CameraType, FlashMode, RNCamera} from 'react-native-camera'; -import ImagePicker from 'react-native-image-crop-picker'; -import FlashOffIcon from '../../assets/icons/camera/flash-off.svg'; -import FlashOnIcon from '../../assets/icons/camera/flash-on.svg'; -import FlipIcon from '../../assets/icons/camera/flip.svg'; -import SaveIcon from '../../assets/icons/camera/save.svg'; import CloseIcon from '../../assets/ionicons/close-outline.svg'; -import {TaggSquareButton} from '../../components'; -import {ERROR_UPLOAD} from '../../constants/strings'; +import { + FlashButton, + FlipButton, + GalleryIcon, + SaveButton, + TaggSquareButton, +} from '../../components'; import {MainStackParams} from '../../routes'; import {HeaderHeight, normalize, SCREEN_WIDTH} from '../../utils'; +import {takePicture} from '../../utils/camera'; type CameraScreenRouteProps = RouteProp<MainStackParams, 'CameraScreen'>; -type CameraScreenNavigationProps = StackNavigationProp< +export type CameraScreenNavigationProps = StackNavigationProp< MainStackParams, 'CameraScreen' >; @@ -40,8 +34,11 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => { const [flashMode, setFlashMode] = useState<keyof FlashMode>('off'); const [capturedImage, setCapturedImage] = useState<string>(''); const [mostRecentPhoto, setMostRecentPhoto] = useState<string>(''); + const [showSaveButton, setShowSaveButton] = useState<boolean>(false); - // Removes bottom navigation bar on current screen and add it back when navigating away + /* + * Removes bottom navigation bar on current screen and add it back when navigating away + */ useFocusEffect( useCallback(() => { navigation.dangerouslyGetParent()?.setOptions({ @@ -55,6 +52,9 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => { }, [navigation]), ); + /* + * Chooses the last picture from gallery to display as the gallery button icon + */ useEffect(() => { CameraRoll.getPhotos({first: 1}) .then((lastPhoto) => { @@ -62,20 +62,14 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => { setMostRecentPhoto(edge.node.image.uri), ); }) - .catch((err) => console.log('Unable to fetch preview photo for gallery')); + .catch((_err) => + console.log('Unable to fetch preview photo for gallery'), + ); }, [capturedImage]); - const takePicture = () => { - if (cameraRef !== null) { - cameraRef.current?.pausePreview(); - const options = {quality: 0.5, base64: true}; - cameraRef.current?.takePictureAsync(options).then((response) => { - setShowSaveButton(true); - setCapturedImage(response.uri); - }); - } - }; - + /* + * Appears once a picture has been captured to navigate to the caption screen + */ const handleNext = () => { navigation.navigate('CaptionScreen', { screenType, @@ -87,94 +81,10 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => { }); }; - const downloadImage = () => { - CameraRoll.save(capturedImage, {album: 'Recents', type: 'photo'}) - .then((_res) => Alert.alert('Saved to device!')) - .catch((_err) => Alert.alert('Failed to save to device!')); - }; - - const navigateToImagePicker = () => { - ImagePicker.openPicker({ - smartAlbums: [ - 'Favorites', - 'RecentlyAdded', - 'SelfPortraits', - 'Screenshots', - 'UserLibrary', - ], - mediaType: 'any', - }) - .then((picture) => { - if ('path' in picture) { - navigation.navigate('ZoomInCropper', { - screenType, - title: title, - image: picture, - }); - } - }) - .catch((err) => { - if (err.code && err.code !== 'E_PICKER_CANCELLED') { - Alert.alert(ERROR_UPLOAD); - } - }); - }; - - const GalleryIcon = () => ( - <TouchableOpacity onPress={navigateToImagePicker} style={styles.saveButton}> - <Image - source={{uri: mostRecentPhoto}} - width={40} - height={40} - style={{borderWidth: 2, borderColor: 'white', borderRadius: 5}} - /> - <Text style={styles.saveButtonLabel}>Gallery</Text> - </TouchableOpacity> - ); - - const FlipButton = () => ( - <TouchableOpacity - onPress={() => setCameraType(cameraType === 'front' ? 'back' : 'front')} - style={styles.saveButton}> - <FlipIcon width={40} height={40} /> - <Text style={styles.saveButtonLabel}>Flip</Text> - </TouchableOpacity> - ); - - const SaveButton = () => ( - <TouchableOpacity - onPress={() => { - downloadImage(); - }} - style={[styles.saveButton]}> - <SaveIcon width={40} height={40} /> - <Text style={styles.saveButtonLabel}>Save</Text> - </TouchableOpacity> - ); - - const FlashButton = () => ( - <TouchableOpacity - onPress={() => setFlashMode(flashMode === 'on' ? 'off' : 'on')} - style={styles.flashButtonContainer}> - {flashMode === 'on' ? ( - <FlashOnIcon - height={30} - width={20} - color={'white'} - style={{zIndex: 999}} - /> - ) : ( - <FlashOffIcon - height={30} - width={20} - color={'white'} - style={{zIndex: 999}} - /> - )} - <Text style={styles.saveButtonLabel}>Flash</Text> - </TouchableOpacity> - ); - + /* + * 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(); @@ -185,14 +95,12 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => { } }; - const [showSaveButton, setShowSaveButton] = useState<boolean>(false); - return ( <View style={styles.container}> <TouchableOpacity style={styles.closeButton} onPress={handleClose}> <CloseIcon height={25} width={25} color={'white'} /> </TouchableOpacity> - <FlashButton /> + <FlashButton flashMode={flashMode} setFlashMode={setFlashMode} /> <RNCamera ref={cameraRef} style={{ @@ -203,9 +111,15 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => { flashMode={flashMode} /> <View style={[styles.bottomContainer, {bottom: tabBarHeight}]}> - {showSaveButton ? <SaveButton /> : <FlipButton />} + {showSaveButton ? ( + <SaveButton capturedImageURI={capturedImage} /> + ) : ( + <FlipButton cameraType={cameraType} setCameraType={setCameraType} /> + )} <TouchableOpacity - onPress={takePicture} + onPress={() => + takePicture(cameraRef, setShowSaveButton, setCapturedImage) + } style={styles.captureButtonContainer}> <View style={styles.captureButton} /> </TouchableOpacity> @@ -227,7 +141,11 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => { labelStyle={styles.nextButtonLabel} /> ) : ( - <GalleryIcon /> + <GalleryIcon + mostRecentPhoto={mostRecentPhoto} + screenType={screenType} + title={title} + /> )} </View> </View> @@ -277,21 +195,6 @@ const styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'center', }, - saveButton: { - zIndex: 1, - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - width: (SCREEN_WIDTH - 100) / 2, - }, - saveButtonLabel: { - color: 'white', - fontWeight: '700', - fontSize: normalize(12), - lineHeight: normalize(14.32), - marginTop: 5, - zIndex: 999, - }, nextButton: { zIndex: 1, width: normalize(100), @@ -305,21 +208,6 @@ const styles = StyleSheet.create({ letterSpacing: normalize(1.3), textAlign: 'center', }, - flashButtonContainer: { - position: 'absolute', - backgroundColor: '#808080', - // opacity: 0.25, - zIndex: 1, - top: normalize(50), - right: 0, - marginRight: normalize(18), - height: 86, - width: 49, - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 30, - }, }); export default CameraScreen; diff --git a/src/utils/camera.ts b/src/utils/camera.ts new file mode 100644 index 00000000..fc2471e5 --- /dev/null +++ b/src/utils/camera.ts @@ -0,0 +1,67 @@ +import CameraRoll from '@react-native-community/cameraroll'; +import {useNavigation} from '@react-navigation/native'; +import {Dispatch, RefObject, SetStateAction} from 'react'; +import {Alert} from 'react-native'; +import {Orientation, RNCamera} from 'react-native-camera'; +import ImagePicker from 'react-native-image-crop-picker'; +import {ScreenType} from 'src/types'; +import {ERROR_UPLOAD} from '../constants/strings'; + +/* + * Captures a photo and pauses to shoe the preview of the picture taken + */ +export const takePicture = ( + cameraRef: RefObject<RNCamera>, + setShowSaveButton: Dispatch<SetStateAction<boolean>>, + setCapturedImage: Dispatch<SetStateAction<string>>, +) => { + if (cameraRef !== null) { + cameraRef.current?.pausePreview(); + const options = { + forceUpOrientation: true, + quality: 0.5, + base64: true, + }; + cameraRef.current?.takePictureAsync(options).then((response) => { + setShowSaveButton(true); + setCapturedImage(response.uri); + }); + } +}; + +export const downloadImage = (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 = ( + screenType: ScreenType, + title: string, +) => { + const navigation = useNavigation(); + ImagePicker.openPicker({ + smartAlbums: [ + 'Favorites', + 'RecentlyAdded', + 'SelfPortraits', + 'Screenshots', + 'UserLibrary', + ], + mediaType: 'any', + }) + .then((picture) => { + if ('path' in picture) { + navigation.navigate('ZoomInCropper', { + screenType, + title, + image: picture, + }); + } + }) + .catch((err) => { + if (err.code && err.code !== 'E_PICKER_CANCELLED') { + Alert.alert(ERROR_UPLOAD); + } + }); +}; |