aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/assets/icons/camera/flash-off.svg1
-rw-r--r--src/assets/icons/camera/flash-on.svg1
-rw-r--r--src/assets/icons/camera/flip.svg1
-rw-r--r--src/assets/icons/camera/save.svg1
-rw-r--r--src/components/camera/FlashButton.tsx42
-rw-r--r--src/components/camera/FlipButton.tsx29
-rw-r--r--src/components/camera/GalleryIcon.tsx43
-rw-r--r--src/components/camera/SaveButton.tsx26
-rw-r--r--src/components/camera/index.ts4
-rw-r--r--src/components/camera/styles.tsx53
-rw-r--r--src/components/index.ts9
-rw-r--r--src/components/moments/Moment.tsx38
-rw-r--r--src/routes/main/MainStackNavigator.tsx4
-rw-r--r--src/routes/main/MainStackScreen.tsx8
-rw-r--r--src/screens/moments/CameraScreen.tsx215
-rw-r--r--src/screens/moments/index.ts1
-rw-r--r--src/utils/camera.ts65
17 files changed, 507 insertions, 34 deletions
diff --git a/src/assets/icons/camera/flash-off.svg b/src/assets/icons/camera/flash-off.svg
new file mode 100644
index 00000000..fb04efd2
--- /dev/null
+++ b/src/assets/icons/camera/flash-off.svg
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 720"><defs><style>.cls-1{fill:#fff;}</style></defs><path class="cls-1" d="M361.84,13.57,128.3,360c-3.94,2.81-6.8,11.94-7.74,16.16-4.5,29.23,19.23,41.69,31.66,44.27H289.39l-26,253c-2.11,10.3-1.13,33.3,19.69,42.86s38.69-1.18,45-7.73l260.27-373.1,9.85-16.16c8.44-32.61-14.78-46.38-27.44-49.19H400.53L427.26,38.16c1.13-17.42-11.26-29.28-17.59-33C386.6-7.23,368.17,5.61,361.84,13.57Z"/></svg> \ No newline at end of file
diff --git a/src/assets/icons/camera/flash-on.svg b/src/assets/icons/camera/flash-on.svg
new file mode 100644
index 00000000..b4608b75
--- /dev/null
+++ b/src/assets/icons/camera/flash-on.svg
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 720"><defs><style>.cls-1{fill:#fff;}</style></defs><path class="cls-1" d="M413.14,36.09,387.86,255.37h5.7L251.35,397.57H153c-11.76-2.43-34.2-14.21-29.95-41.86.89-4,3.6-12.62,7.32-15.28L351.28,12.83c6-7.53,23.41-19.67,45.23-8C402.5,8.4,414.2,19.61,413.14,36.09Z"/><path class="cls-1" d="M594.91,341l-9.32,15.28L339.45,709.1c-6,6.2-22.89,16.35-42.58,7.31s-20.62-30.78-18.63-40.53L297,493.2,495.79,294.46H569C580.94,297.12,602.89,310.14,594.91,341Z"/><path class="cls-1" d="M634.48,114.82a29,29,0,0,1-8.5,20.53L486.15,275.18,287.4,473.92,135.09,626.24A29,29,0,1,1,94,585.18l167.8-167.8L404,275.18,584.92,94.29a29,29,0,0,1,49.56,20.53Z"/></svg> \ No newline at end of file
diff --git a/src/assets/icons/camera/flip.svg b/src/assets/icons/camera/flip.svg
new file mode 100644
index 00000000..e2ef1a0c
--- /dev/null
+++ b/src/assets/icons/camera/flip.svg
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 720"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-linecap:round;stroke-linejoin:round;stroke-width:49.69px;}</style></defs><path class="cls-1" d="M691.29,360c0,164.67-133.49,298.16-298.16,298.16-132.89,0-245.48-86.95-284-207.06m0-182.2c38.5-120.11,151.09-207.06,284-207.06A297.74,297.74,0,0,1,633.31,183.3"/><path class="cls-1" d="M28.71,525.64l74.54-99.38L227.48,476"/><path class="cls-1" d="M525.64,208.11l124.16-4.06L680.34,73.78"/></svg> \ No newline at end of file
diff --git a/src/assets/icons/camera/save.svg b/src/assets/icons/camera/save.svg
new file mode 100644
index 00000000..6a28fb55
--- /dev/null
+++ b/src/assets/icons/camera/save.svg
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 720"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-linecap:round;stroke-linejoin:round;stroke-width:49.92px;}</style></defs><path class="cls-1" d="M359.15,42v526.9"/><path class="cls-1" d="M192.76,424.7,359.85,577.23,539.41,424.7"/><path class="cls-1" d="M26.37,458V618.53c.63,20.44,15.92,61.3,72.08,61.3h527c21.53-1.29,65-15.37,66.46-61.3V458"/></svg> \ No newline at end of file
diff --git a/src/components/camera/FlashButton.tsx b/src/components/camera/FlashButton.tsx
new file mode 100644
index 00000000..06a4e44e
--- /dev/null
+++ b/src/components/camera/FlashButton.tsx
@@ -0,0 +1,42 @@
+import React, {Dispatch, SetStateAction} from 'react';
+import {Text, TouchableOpacity} from 'react-native';
+import {FlashMode} from 'react-native-camera';
+import FlashOffIcon from '../../assets/icons/camera/flash-off.svg';
+import FlashOnIcon from '../../assets/icons/camera/flash-on.svg';
+import {styles} from './styles';
+
+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={styles.flashIcon}
+ />
+ ) : (
+ <FlashOffIcon
+ height={30}
+ width={20}
+ color={'white'}
+ style={styles.flashIcon}
+ />
+ )}
+ <Text style={styles.saveButtonLabel}>Flash</Text>
+ </TouchableOpacity>
+);
+
+export default FlashButton;
diff --git a/src/components/camera/FlipButton.tsx b/src/components/camera/FlipButton.tsx
new file mode 100644
index 00000000..c6f710a9
--- /dev/null
+++ b/src/components/camera/FlipButton.tsx
@@ -0,0 +1,29 @@
+import React, {Dispatch, SetStateAction} from 'react';
+import {Text, TouchableOpacity} from 'react-native';
+import {CameraType} from 'react-native-camera';
+import FlipIcon from '../../assets/icons/camera/flip.svg';
+import {styles} from './styles';
+
+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>
+);
+
+export default FlipButton;
diff --git a/src/components/camera/GalleryIcon.tsx b/src/components/camera/GalleryIcon.tsx
new file mode 100644
index 00000000..c49ace7d
--- /dev/null
+++ b/src/components/camera/GalleryIcon.tsx
@@ -0,0 +1,43 @@
+import {useNavigation} from '@react-navigation/native';
+import React from 'react';
+import {Image, Text, TouchableOpacity, View} from 'react-native';
+import {ScreenType} from '../../types';
+import {navigateToImagePicker} from '../../utils/camera';
+import {styles} from './styles';
+
+interface GalleryIconProps {
+ screenType: ScreenType;
+ title: string;
+ mostRecentPhotoUri: 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,
+ mostRecentPhotoUri,
+}) => {
+ const navigation = useNavigation();
+ return (
+ <TouchableOpacity
+ onPress={() => navigateToImagePicker(navigation, screenType, title)}
+ style={styles.saveButton}>
+ {mostRecentPhotoUri !== '' ? (
+ <Image
+ source={{uri: mostRecentPhotoUri}}
+ width={40}
+ height={40}
+ style={styles.galleryIcon}
+ />
+ ) : (
+ <View style={styles.galleryIconEmpty} />
+ )}
+ <Text style={styles.saveButtonLabel}>Gallery</Text>
+ </TouchableOpacity>
+ );
+};
+
+export default GalleryIcon;
diff --git a/src/components/camera/SaveButton.tsx b/src/components/camera/SaveButton.tsx
new file mode 100644
index 00000000..840cc804
--- /dev/null
+++ b/src/components/camera/SaveButton.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import {Text, TouchableOpacity} from 'react-native';
+import SaveIcon from '../../assets/icons/camera/save.svg';
+import {downloadImage} from '../../utils/camera';
+import {styles} from './styles';
+
+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>
+);
+
+export default SaveButton;
diff --git a/src/components/camera/index.ts b/src/components/camera/index.ts
new file mode 100644
index 00000000..d33d1e4a
--- /dev/null
+++ b/src/components/camera/index.ts
@@ -0,0 +1,4 @@
+export {default as GalleryIcon} from './GalleryIcon';
+export {default as FlashButton} from './FlashButton';
+export {default as FlipButton} from './FlipButton';
+export {default as SaveButton} from './SaveButton';
diff --git a/src/components/camera/styles.tsx b/src/components/camera/styles.tsx
new file mode 100644
index 00000000..33b47cc4
--- /dev/null
+++ b/src/components/camera/styles.tsx
@@ -0,0 +1,53 @@
+import {StyleSheet} from 'react-native';
+import {normalize, SCREEN_WIDTH} from '../../utils/layouts';
+
+export 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,
+ },
+ galleryIcon: {
+ borderWidth: 2,
+ borderColor: 'white',
+ borderRadius: 5,
+ width: 40,
+ height: 40,
+ },
+ galleryIconEmpty: {
+ borderWidth: 2,
+ borderColor: 'white',
+ borderRadius: 5,
+ width: 40,
+ height: 40,
+ backgroundColor: 'grey',
+ },
+ flashIcon: {
+ zIndex: 2,
+ },
+});
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/components/moments/Moment.tsx b/src/components/moments/Moment.tsx
index 81e23076..9449271b 100644
--- a/src/components/moments/Moment.tsx
+++ b/src/components/moments/Moment.tsx
@@ -1,9 +1,8 @@
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 ImagePicker from 'react-native-image-crop-picker';
import LinearGradient from 'react-native-linear-gradient';
import DeleteIcon from '../../assets/icons/delete-logo.svg';
import DownIcon from '../../assets/icons/down_icon.svg';
@@ -11,7 +10,6 @@ import BigPlusIcon from '../../assets/icons/plus-icon-white.svg';
import PlusIcon from '../../assets/icons/plus-icon.svg';
import UpIcon from '../../assets/icons/up_icon.svg';
import {TAGG_LIGHT_BLUE} from '../../constants';
-import {ERROR_UPLOAD} from '../../constants/strings';
import {MomentType, ScreenType} from '../../types';
import {normalize, SCREEN_WIDTH} from '../../utils';
import MomentTile from './MomentTile';
@@ -43,31 +41,11 @@ const Moment: React.FC<MomentProps> = ({
}) => {
const navigation = useNavigation();
- 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 navigateToCameraScreen = () => {
+ navigation.navigate('CameraScreen', {
+ title,
+ screenType,
+ });
};
return (
@@ -106,7 +84,7 @@ const Moment: React.FC<MomentProps> = ({
<PlusIcon
width={23}
height={23}
- onPress={() => navigateToImagePicker()}
+ onPress={() => navigateToCameraScreen()}
color={TAGG_LIGHT_BLUE}
style={styles.horizontalMargin}
/>
@@ -136,7 +114,7 @@ const Moment: React.FC<MomentProps> = ({
/>
))}
{(images === undefined || images.length === 0) && !userXId && (
- <TouchableOpacity onPress={() => navigateToImagePicker()}>
+ <TouchableOpacity onPress={() => navigateToCameraScreen()}>
<LinearGradient
colors={['rgba(105, 141, 211, 1)', 'rgba(105, 141, 211, 0.3)']}>
<View style={styles.defaultImage}>
diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx
index b58e03cc..ef3fc7fd 100644
--- a/src/routes/main/MainStackNavigator.tsx
+++ b/src/routes/main/MainStackNavigator.tsx
@@ -115,6 +115,10 @@ export type MainStackParams = {
screenType: ScreenType;
title: string;
};
+ CameraScreen: {
+ title: string;
+ screenType: ScreenType;
+ };
};
export const MainStack = createStackNavigator<MainStackParams>();
diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx
index 9e3747f9..f6adeab1 100644
--- a/src/routes/main/MainStackScreen.tsx
+++ b/src/routes/main/MainStackScreen.tsx
@@ -34,6 +34,7 @@ import {
SuggestedPeopleWelcomeScreen,
TagSelectionScreen,
TagFriendsScreen,
+ CameraScreen,
} from '../../screens';
import MutualBadgeHolders from '../../screens/suggestedPeople/MutualBadgeHolders';
import {ScreenType} from '../../types';
@@ -334,6 +335,13 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
gestureEnabled: false,
}}
/>
+ <MainStack.Screen
+ name="CameraScreen"
+ component={CameraScreen}
+ options={{
+ gestureEnabled: false,
+ }}
+ />
</MainStack.Navigator>
);
};
diff --git a/src/screens/moments/CameraScreen.tsx b/src/screens/moments/CameraScreen.tsx
new file mode 100644
index 00000000..c6ed1116
--- /dev/null
+++ b/src/screens/moments/CameraScreen.tsx
@@ -0,0 +1,215 @@
+import CameraRoll from '@react-native-community/cameraroll';
+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 {StyleSheet, TouchableOpacity, View} from 'react-native';
+import {CameraType, FlashMode, RNCamera} from 'react-native-camera';
+import CloseIcon from '../../assets/ionicons/close-outline.svg';
+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'>;
+export type CameraScreenNavigationProps = StackNavigationProp<
+ MainStackParams,
+ 'CameraScreen'
+>;
+interface CameraScreenProps {
+ route: CameraScreenRouteProps;
+ navigation: CameraScreenNavigationProps;
+}
+const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => {
+ const {title, screenType} = route.params;
+ const cameraRef = createRef<RNCamera>();
+ const tabBarHeight = useBottomTabBarHeight();
+ const [cameraType, setCameraType] = useState<keyof CameraType>('front');
+ 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
+ */
+ useFocusEffect(
+ useCallback(() => {
+ navigation.dangerouslyGetParent()?.setOptions({
+ tabBarVisible: false,
+ });
+ return () => {
+ navigation.dangerouslyGetParent()?.setOptions({
+ tabBarVisible: true,
+ });
+ };
+ }, [navigation]),
+ );
+
+ /*
+ * Chooses the last picture from gallery to display as the gallery button icon
+ */
+ useEffect(() => {
+ CameraRoll.getPhotos({first: 1})
+ .then((lastPhoto) => {
+ if (lastPhoto.edges.length > 0) {
+ const image = lastPhoto.edges[0].node.image;
+ setMostRecentPhoto(image.uri);
+ }
+ })
+ .catch((_err) =>
+ console.log('Unable to fetch preview photo for gallery'),
+ );
+ }, [capturedImage]);
+
+ /*
+ * Appears once a picture has been captured to navigate to the caption screen
+ */
+ const handleNext = () => {
+ navigation.navigate('CaptionScreen', {
+ screenType,
+ title,
+ image: {
+ filename: capturedImage,
+ path: capturedImage,
+ },
+ });
+ };
+
+ /*
+ * 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);
+ setCapturedImage('');
+ } else {
+ navigation.goBack();
+ }
+ };
+
+ return (
+ <View style={styles.container}>
+ <TouchableOpacity style={styles.closeButton} onPress={handleClose}>
+ <CloseIcon height={25} width={25} color={'white'} />
+ </TouchableOpacity>
+ <FlashButton flashMode={flashMode} setFlashMode={setFlashMode} />
+ <RNCamera
+ ref={cameraRef}
+ style={styles.camera}
+ type={cameraType}
+ flashMode={flashMode}
+ />
+ <View style={[styles.bottomContainer, {bottom: tabBarHeight}]}>
+ {showSaveButton ? (
+ <SaveButton capturedImageURI={capturedImage} />
+ ) : (
+ <FlipButton cameraType={cameraType} setCameraType={setCameraType} />
+ )}
+ <TouchableOpacity
+ onPress={() =>
+ takePicture(cameraRef, setShowSaveButton, setCapturedImage)
+ }
+ style={styles.captureButtonContainer}>
+ <View style={styles.captureButton} />
+ </TouchableOpacity>
+ <View style={styles.bottomRightContainer}>
+ {capturedImage ? (
+ <TaggSquareButton
+ onPress={handleNext}
+ title={'Next'}
+ buttonStyle={'large'}
+ buttonColor={'blue'}
+ labelColor={'white'}
+ style={styles.nextButton}
+ labelStyle={styles.nextButtonLabel}
+ />
+ ) : (
+ <GalleryIcon
+ mostRecentPhotoUri={mostRecentPhoto}
+ screenType={screenType}
+ title={title}
+ />
+ )}
+ </View>
+ </View>
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ camera: {
+ flex: 1,
+ justifyContent: 'space-between',
+ },
+ container: {
+ flex: 1,
+ flexDirection: 'column',
+ backgroundColor: 'black',
+ },
+ preview: {
+ flex: 1,
+ justifyContent: 'flex-end',
+ alignItems: 'center',
+ },
+ captureButtonContainer: {
+ alignSelf: 'center',
+ backgroundColor: 'transparent',
+ borderRadius: 100,
+ borderWidth: 4,
+ borderColor: '#fff',
+ width: 93,
+ height: 93,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ captureButton: {
+ backgroundColor: '#fff',
+ width: 68,
+ height: 68,
+ borderRadius: 74,
+ },
+ closeButton: {
+ position: 'absolute',
+ top: 0,
+ paddingTop: HeaderHeight,
+ zIndex: 1,
+ marginLeft: '5%',
+ },
+ bottomContainer: {
+ position: 'absolute',
+ width: SCREEN_WIDTH,
+ flexDirection: 'row',
+ justifyContent: 'center',
+ },
+ bottomRightContainer: {
+ flexDirection: 'column',
+ justifyContent: 'center',
+ 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/moments/index.ts b/src/screens/moments/index.ts
index aac2ddeb..07d55192 100644
--- a/src/screens/moments/index.ts
+++ b/src/screens/moments/index.ts
@@ -1,2 +1,3 @@
export {default as TagSelectionScreen} from './TagSelectionScreen';
export {default as TagFriendsScreen} from './TagFriendsScreen';
+export {default as CameraScreen} from './CameraScreen';
diff --git a/src/utils/camera.ts b/src/utils/camera.ts
new file mode 100644
index 00000000..73461ad7
--- /dev/null
+++ b/src/utils/camera.ts
@@ -0,0 +1,65 @@
+import CameraRoll from '@react-native-community/cameraroll';
+import {Dispatch, RefObject, SetStateAction} from 'react';
+import {Alert} from 'react-native';
+import {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,
+ writeExif: false,
+ };
+ 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 = (
+ navigation: any,
+ screenType: ScreenType,
+ title: string,
+) => {
+ ImagePicker.openPicker({
+ smartAlbums: [
+ 'Favorites',
+ 'RecentlyAdded',
+ 'SelfPortraits',
+ 'Screenshots',
+ 'UserLibrary',
+ ],
+ mediaType: 'photo',
+ })
+ .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);
+ }
+ });
+};