diff options
Diffstat (limited to 'src')
26 files changed, 551 insertions, 265 deletions
diff --git a/src/assets/images/images.png b/src/assets/images/images.png Binary files differnew file mode 100644 index 00000000..43f42556 --- /dev/null +++ b/src/assets/images/images.png diff --git a/src/assets/images/suggested-people-preview-large.png b/src/assets/images/suggested-people-preview-large.png Binary files differnew file mode 100644 index 00000000..b80eda27 --- /dev/null +++ b/src/assets/images/suggested-people-preview-large.png diff --git a/src/assets/images/suggested-people-preview-silhouette.png b/src/assets/images/suggested-people-preview-silhouette.png Binary files differnew file mode 100644 index 00000000..7c496013 --- /dev/null +++ b/src/assets/images/suggested-people-preview-silhouette.png diff --git a/src/assets/images/suggested-people-preview-small.png b/src/assets/images/suggested-people-preview-small.png Binary files differnew file mode 100644 index 00000000..bb1aef89 --- /dev/null +++ b/src/assets/images/suggested-people-preview-small.png diff --git a/src/assets/images/tagg-logo.png b/src/assets/images/tagg-logo.png Binary files differnew file mode 100644 index 00000000..8d67f1f7 --- /dev/null +++ b/src/assets/images/tagg-logo.png diff --git a/src/components/common/FriendsButton.tsx b/src/components/common/FriendsButton.tsx index 243a551d..a1e107c5 100644 --- a/src/components/common/FriendsButton.tsx +++ b/src/components/common/FriendsButton.tsx @@ -1,18 +1,34 @@ -import React from 'react'; -import {StyleSheet} from 'react-native'; -import {Button} from 'react-native-elements'; -import {ScreenType} from '../../types'; +import React, {Fragment} from 'react'; +import { + StyleProp, + StyleSheet, + Text, + TextStyle, + View, + ViewStyle, +} from 'react-native'; +import {TouchableOpacity} from 'react-native-gesture-handler'; +import {useDispatch, useSelector, useStore} from 'react-redux'; import {TAGG_LIGHT_BLUE} from '../../constants'; -import {handleFriendUnfriend, SCREEN_WIDTH} from '../../utils'; import {NO_PROFILE, NO_USER} from '../../store/initialStates'; -import {useDispatch, useSelector, useStore} from 'react-redux'; import {RootState} from '../../store/rootReducer'; +import {ScreenType} from '../../types'; +import {handleFriendUnfriend, normalize, SCREEN_WIDTH} from '../../utils'; -interface ProfileBodyProps { +interface FriendsButtonProps { userXId: string | undefined; screenType: ScreenType; + friendship_requester_id: string; + onAcceptRequest: () => void; + onRejectRequest: () => void; } -const FriendsButton: React.FC<ProfileBodyProps> = ({userXId, screenType}) => { +const FriendsButton: React.FC<FriendsButtonProps> = ({ + userXId, + screenType, + friendship_requester_id, + onAcceptRequest, + onRejectRequest, +}) => { const dispatch = useDispatch(); const {user = NO_USER, profile = NO_PROFILE} = userXId @@ -24,35 +40,29 @@ const FriendsButton: React.FC<ProfileBodyProps> = ({userXId, screenType}) => { ); const state = useStore().getState(); - const {friendship_status} = profile; - return ( - <> - {friendship_status === 'no_record' && ( - <Button - title={'Add Friend'} - buttonStyle={styles.button} - titleStyle={styles.buttonTitle} - onPress={() => - handleFriendUnfriend( - screenType, - user, - profile, - dispatch, - state, - loggedInUser, - ) - } // requested, requested status - /> - )} - {friendship_status === 'friends' && ( - <Button - title={'Unfriend'} - buttonStyle={styles.requestedButton} - titleStyle={styles.requestedButtonTitle} - onPress={() => - handleFriendUnfriend( + const outlineButton: [StyleProp<ViewStyle>, StyleProp<TextStyle>] = [ + [styles.button, styles.transparentBG], + [styles.label, styles.blueLabel], + ]; + + const filledButton: [StyleProp<ViewStyle>, StyleProp<TextStyle>] = [ + [styles.button, styles.lightBlueBG], + [styles.label, styles.whiteLabel], + ]; + + const renderButton = ( + title: string, + style: [StyleProp<ViewStyle>, StyleProp<TextStyle>], + onPress?: () => void, + ) => ( + <TouchableOpacity + style={style[0]} + onPress={() => + onPress + ? onPress() + : handleFriendUnfriend( screenType, user, profile, @@ -60,50 +70,64 @@ const FriendsButton: React.FC<ProfileBodyProps> = ({userXId, screenType}) => { state, loggedInUser, ) - } // unfriend, no record status - /> - )} - </> + }> + <Text style={style[1]}>{title}</Text> + </TouchableOpacity> ); + + switch (friendship_status) { + case 'friends': + return renderButton('Unfriend', outlineButton); + case 'requested': + if (friendship_requester_id !== userXId) { + return renderButton('Requested', outlineButton); + } else { + return ( + <View style={styles.row}> + {renderButton('Accept', filledButton, onAcceptRequest)} + {renderButton('Reject', outlineButton, onRejectRequest)} + </View> + ); + } + case 'no_record': + return renderButton('Add Friend', filledButton); + default: + return <Fragment />; + } }; const styles = StyleSheet.create({ - requestedButton: { + button: { justifyContent: 'center', alignItems: 'center', width: SCREEN_WIDTH * 0.4, - height: SCREEN_WIDTH * 0.075, - borderColor: TAGG_LIGHT_BLUE, + aspectRatio: 154 / 33, borderWidth: 2, + borderColor: TAGG_LIGHT_BLUE, borderRadius: 3, marginRight: '2%', marginLeft: '1%', + }, + transparentBG: { backgroundColor: 'transparent', }, - requestedButtonTitle: { - color: TAGG_LIGHT_BLUE, - padding: 0, - fontSize: 14, + lightBlueBG: { + backgroundColor: TAGG_LIGHT_BLUE, + }, + label: { + fontSize: normalize(15), fontWeight: '700', + letterSpacing: 1, }, - buttonTitle: { + blueLabel: { + color: TAGG_LIGHT_BLUE, + }, + whiteLabel: { color: 'white', - padding: 0, - fontSize: 14, - fontWeight: '700', }, - button: { - justifyContent: 'center', - alignItems: 'center', - width: SCREEN_WIDTH * 0.4, - height: SCREEN_WIDTH * 0.075, - padding: 0, - borderWidth: 2, - borderColor: TAGG_LIGHT_BLUE, - borderRadius: 3, - marginRight: '2%', - marginLeft: '1%', - backgroundColor: TAGG_LIGHT_BLUE, + row: { + flex: 1, + flexDirection: 'row', }, }); diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx index 106b20a7..646be3e0 100644 --- a/src/components/profile/ProfileBody.tsx +++ b/src/components/profile/ProfileBody.tsx @@ -1,28 +1,24 @@ import React from 'react'; -import {StyleSheet, View, Text, LayoutChangeEvent, Linking} from 'react-native'; -import {Button, normalize} from 'react-native-elements'; +import {LayoutChangeEvent, Linking, StyleSheet, Text, View} from 'react-native'; +import {normalize} from 'react-native-elements'; +import {useDispatch, useSelector, useStore} from 'react-redux'; import { TAGG_DARK_BLUE, TAGG_LIGHT_BLUE, TOGGLE_BUTTON_TYPE, } from '../../constants'; -import ToggleButton from './ToggleButton'; -import {RootState} from '../../store/rootReducer'; -import {useDispatch, useSelector, useStore} from 'react-redux'; -import {ScreenType} from '../../types'; -import {NO_PROFILE, NO_USER} from '../../store/initialStates'; -import { - getUserAsProfilePreviewType, - handleFriendUnfriend, - SCREEN_WIDTH, -} from '../../utils'; -import {AcceptDeclineButtons, FriendsButton} from '../common'; import { acceptFriendRequest, declineFriendRequest, updateUserXFriends, updateUserXProfileAllScreens, } from '../../store/actions'; +import {NO_PROFILE, NO_USER} from '../../store/initialStates'; +import {RootState} from '../../store/rootReducer'; +import {ScreenType} from '../../types'; +import {getUserAsProfilePreviewType, SCREEN_WIDTH} from '../../utils'; +import {FriendsButton} from '../common'; +import ToggleButton from './ToggleButton'; interface ProfileBodyProps { onLayout: (event: LayoutChangeEvent) => void; @@ -99,37 +95,13 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({ )} {userXId && !isBlocked && ( <View style={styles.buttonsContainer}> - <FriendsButton userXId={userXId} screenType={screenType} /> - {(friendship_status === 'requested' && - friendship_requester_id !== userXId && ( - <Button - title={'Requested'} - buttonStyle={styles.requestedButton} - titleStyle={styles.requestedButtonTitle} - onPress={() => - handleFriendUnfriend( - screenType, - user, - profile, - dispatch, - state, - loggedInUser, - ) - } // delete request, no record status - /> - )) || - (friendship_status === 'requested' && - friendship_requester_id === userXId && ( - <AcceptDeclineButtons - requester={getUserAsProfilePreviewType( - {userId: userXId, username}, - profile, - )} - onAccept={handleAcceptRequest} - onReject={handleDeclineFriendRequest} - externalStyles={{container: styles.acceptRejectContainer}} - /> - ))} + <FriendsButton + userXId={userXId} + screenType={screenType} + friendship_requester_id={friendship_requester_id} + onAcceptRequest={handleAcceptRequest} + onRejectRequest={handleDeclineFriendRequest} + /> </View> )} </View> @@ -143,11 +115,7 @@ const styles = StyleSheet.create({ paddingTop: '3.5%', paddingBottom: '2%', }, - acceptRejectContainer: { - flexDirection: 'row', - }, buttonsContainer: { - flexDirection: 'row', flex: 1, paddingTop: '3.5%', paddingBottom: '2%', @@ -171,42 +139,6 @@ const styles = StyleSheet.create({ color: TAGG_DARK_BLUE, marginBottom: '1%', }, - requestedButton: { - justifyContent: 'center', - alignItems: 'center', - width: SCREEN_WIDTH * 0.4, - height: SCREEN_WIDTH * 0.075, - borderColor: TAGG_LIGHT_BLUE, - borderWidth: 2, - borderRadius: 3, - marginRight: '2%', - padding: 0, - backgroundColor: 'transparent', - }, - requestedButtonTitle: { - color: TAGG_LIGHT_BLUE, - padding: 0, - fontSize: 14, - fontWeight: '700', - }, - buttonTitle: { - color: 'white', - padding: 0, - fontSize: 14, - fontWeight: '700', - }, - button: { - justifyContent: 'center', - alignItems: 'center', - width: SCREEN_WIDTH * 0.4, - height: SCREEN_WIDTH * 0.075, - padding: 0, - borderWidth: 2, - borderColor: TAGG_LIGHT_BLUE, - borderRadius: 3, - marginRight: '2%', - backgroundColor: TAGG_LIGHT_BLUE, - }, }); export default ProfileBody; diff --git a/src/constants/api.ts b/src/constants/api.ts index 165bd550..215dadc0 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ // const BASE_URL: string = 'http://3.22.188.127/'; // prod server const BASE_URL: string = 'http://127.0.0.1:8000/'; // local server @@ -32,6 +33,9 @@ export const DISCOVER_ENDPOINT: string = API_URL + 'discover/'; export const WAITLIST_USER_ENDPOINT: string = API_URL + 'waitlist-user/'; export const COMMENT_THREAD_ENDPOINT: string = API_URL + 'reply/'; +// Suggested People +export const SP_UPDATE_PICTURE: string = API_URL + 'suggested_people/update_picture/'; + // Register as FCM device export const FCM_ENDPOINT: string = API_URL + 'fcm/'; diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 3c43fb6c..fbf03744 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -181,3 +181,6 @@ export const EXPLORE_SECTION_TITLES: ExploreSectionType[] = [ "Brown '22", "Brown '23", ]; + +export const SP_WIDTH = 375; +export const SP_HEIGHT = 812; diff --git a/src/constants/strings.ts b/src/constants/strings.ts index 5a9be5fc..0965bad0 100644 --- a/src/constants/strings.ts +++ b/src/constants/strings.ts @@ -1,4 +1,3 @@ - /* eslint-disable */ // Below is the regex to convert this into a csv for the Google Sheet // export const (.*) = .*?(['|"|`])(.*)\2; diff --git a/src/routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackNavigator.tsx b/src/routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackNavigator.tsx new file mode 100644 index 00000000..e957e48c --- /dev/null +++ b/src/routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackNavigator.tsx @@ -0,0 +1,8 @@ +import {createStackNavigator} from '@react-navigation/stack'; + +export type SuggestedPeopleOnboardingStackParams = { + WelcomeScreen: undefined; + UploadPicture: undefined; +}; + +export const SuggestedPeopleOnboardingStack = createStackNavigator<SuggestedPeopleOnboardingStackParams>(); diff --git a/src/routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackScreen.tsx b/src/routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackScreen.tsx new file mode 100644 index 00000000..970982c4 --- /dev/null +++ b/src/routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackScreen.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import {SuggestedPeopleOnboardingStack} from '.'; +import { + SuggestedPeopleWelcomeScreen, + SuggestedPeopleUploadPictureScreen, +} from '../../screens'; +import {SCREEN_WIDTH} from '../../utils'; +import {headerBarOptions} from '../main'; + +const SuggestedPeopleOnboardingStackScreen: React.FC = () => { + return ( + <SuggestedPeopleOnboardingStack.Navigator + initialRouteName="WelcomeScreen" + screenOptions={{ + headerShown: false, + gestureResponseDistance: {horizontal: SCREEN_WIDTH * 0.6}, + }}> + <SuggestedPeopleOnboardingStack.Screen + name="WelcomeScreen" + component={SuggestedPeopleWelcomeScreen} + options={{ + ...headerBarOptions('white', ''), + }} + /> + <SuggestedPeopleOnboardingStack.Screen + name="UploadPicture" + component={SuggestedPeopleUploadPictureScreen} + options={{ + ...headerBarOptions('white', ''), + }} + /> + </SuggestedPeopleOnboardingStack.Navigator> + ); +}; + +export default SuggestedPeopleOnboardingStackScreen; diff --git a/src/routes/suggestedPeopleOnboarding/index.ts b/src/routes/suggestedPeopleOnboarding/index.ts new file mode 100644 index 00000000..df711493 --- /dev/null +++ b/src/routes/suggestedPeopleOnboarding/index.ts @@ -0,0 +1,2 @@ +export * from './SuggestedPeopleOnboardingStackNavigator'; +export * from './SuggestedPeopleOnboardingStackScreen'; diff --git a/src/screens/index.ts b/src/screens/index.ts index c34cd571..faf3d0b7 100644 --- a/src/screens/index.ts +++ b/src/screens/index.ts @@ -3,3 +3,4 @@ export * from './onboarding'; export * from './profile'; export * from './search'; export * from './suggestedPeople'; +export * from './suggestedPeopleOnboarding'; diff --git a/src/screens/suggestedPeople/AnimatedTutorial.tsx b/src/screens/suggestedPeople/AnimatedTutorial.tsx index 8ebdaea6..bf34ba6e 100644 --- a/src/screens/suggestedPeople/AnimatedTutorial.tsx +++ b/src/screens/suggestedPeople/AnimatedTutorial.tsx @@ -1,13 +1,13 @@ -import * as React from 'react'; -import CloseIcon from '../../assets/ionicons/close-outline.svg'; +import {useNavigation} from '@react-navigation/native'; +import React from 'react'; import {StyleSheet, Text, View} from 'react-native'; import {Image} from 'react-native-animatable'; -import {isIPhoneX, SCREEN_WIDTH} from '../../utils'; import {SafeAreaView} from 'react-native-safe-area-context'; -import {useNavigation} from '@react-navigation/native'; import {useDispatch, useSelector} from 'react-redux'; +import CloseIcon from '../../assets/ionicons/close-outline.svg'; +import {suggestedPeopleAnimatedTutorialFinished} from '../../store/actions/user'; import {RootState} from '../../store/rootReducer'; -import {updateSPSwipeTutorial} from '../../store/actions/user'; +import {isIPhoneX, SCREEN_WIDTH} from '../../utils'; const AnimatedTutorial: React.FC = () => { const navigation = useNavigation(); @@ -15,11 +15,7 @@ const AnimatedTutorial: React.FC = () => { const {user} = useSelector((state: RootState) => state.user); const handleCloseAnimationTutorial = async () => { - /* In user's store, check if profile.sp_swipe_tutorial === 0 - * Make call to edit profile endpoint with suggested people === 1 - */ - const data = 1; - dispatch(updateSPSwipeTutorial(user, data)); + dispatch(suggestedPeopleAnimatedTutorialFinished(user.userId)); navigation.pop(); }; return ( diff --git a/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx b/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx index 4d8607a4..3437cd85 100644 --- a/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx +++ b/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx @@ -1,3 +1,4 @@ +import {useFocusEffect, useNavigation} from '@react-navigation/native'; import React, {useCallback} from 'react'; import { StatusBar, @@ -7,16 +8,15 @@ import { View, } from 'react-native'; import {Image} from 'react-native-animatable'; -import {isIPhoneX, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; -import {TabsGradient, TaggsBar} from '../../components'; -import {SafeAreaView} from 'react-native-safe-area-context'; -import {normalize} from '../../utils'; import Animated from 'react-native-reanimated'; -import {ScreenType} from '../../types'; +import {SafeAreaView} from 'react-native-safe-area-context'; import {useSelector} from 'react-redux'; -import {RootState} from '../../store/rootReducer'; -import {useFocusEffect, useNavigation} from '@react-navigation/native'; +import {TabsGradient, TaggsBar} from '../../components'; import {MutualFriends} from '../../components/suggestedPeople'; +import SuggestedPeopleOnboardingStackScreen from '../../routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackScreen'; +import {RootState} from '../../store/rootReducer'; +import {ScreenType} from '../../types'; +import {isIPhoneX, normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; /** * Bare bones for suggested people consisting of: @@ -24,75 +24,71 @@ import {MutualFriends} from '../../components/suggestedPeople'; */ const SuggestedPeopleScreen: React.FC = () => { + const {suggested_people_linked} = useSelector( + (state: RootState) => state.user.profile, + ) ?? {suggested_people_linked: -1}; + const y = Animated.useValue(0); + // Can be removed once firstname, username props are received const firstName = 'Sarah'; // Adviced to maintain username as a variable here to append @ symbol for maintainability const username = '@' + 'sarahmiller'; const navigation = useNavigation(); - const screenType = ScreenType.SuggestedPeople; - const { - profile: {sp_swipe_tutorial}, - } = useSelector((state: RootState) => state.user); useFocusEffect( useCallback(() => { const navigateToAnimatedTutorial = () => { - /* In user's store, check if profile.sp_swipe_tutorial === 0 - * If, true show tutorial. - */ - if (sp_swipe_tutorial === 0) { + // if the user has finished the previous SP onboarding + if (suggested_people_linked === 1) { navigation.navigate('AnimatedTutorial'); } }; navigateToAnimatedTutorial(); - }, [sp_swipe_tutorial, navigation]), + }, [navigation, suggested_people_linked]), ); - return ( - <> - <SafeAreaView> - <StatusBar barStyle={'light-content'} /> - <Image - // !!! Displaying Sarah Miller's image - source={require('../../assets/images/sarah_miller_full.jpeg')} - style={styles.image} - /> - <View style={styles.mainContainer}> - <Text style={styles.title}>Suggested People</Text> - <View style={styles.body}> - {/* First row contaning name, username, add button (w/o functionality) */} - <View style={styles.addUserContainer}> - <View style={styles.nameInfoContainer}> - <Text style={styles.firstName}>{firstName}</Text> - <Text style={styles.username}>{username}</Text> - </View> - <TouchableOpacity - activeOpacity={0.5} - // TODO: Call function to Add Friend - onPress={() => console.log('Call add friend function')}> - <View style={styles.addButton}> - <Text style={styles.addButtonTitle}>{'Add Friend'}</Text> - </View> - </TouchableOpacity> + const mainContent = () => ( + <SafeAreaView> + <StatusBar barStyle={'light-content'} /> + <Image + // !!! Displaying Sarah Miller's image + source={require('../../assets/images/sarah_miller_full.jpeg')} + style={styles.image} + /> + <View style={styles.mainContainer}> + <Text style={styles.title}>Suggested People</Text> + <View style={styles.body}> + <View style={styles.addUserContainer}> + <View style={styles.nameInfoContainer}> + <Text style={styles.firstName}>{firstName}</Text> + <Text style={styles.username}>{username}</Text> </View> - {/* Taggs Bar. Displays only linked profiles for user while viewing their own profile. */} - <TaggsBar - y={Animated.useValue(0)} - // :: For testing purposes, to view user's own profile, pass userXId='' - userXId={''} - profileBodyHeight={0} - screenType={screenType} - /> - {/* TODO: Pass mutual friends to component and render only if mutual friends exist / display no mutual friends - * Needs to be displayed only if userX !user himself - */} - <MutualFriends /> + <TouchableOpacity + activeOpacity={0.5} + onPress={() => console.log('Call add friend function')}> + <View style={styles.addButton}> + <Text style={styles.addButtonTitle}>{'Add Friend'}</Text> + </View> + </TouchableOpacity> </View> + <TaggsBar + y={y} + userXId={undefined} + profileBodyHeight={0} + screenType={ScreenType.SuggestedPeople} + /> + <MutualFriends /> </View> - <TabsGradient /> - </SafeAreaView> - </> + </View> + <TabsGradient /> + </SafeAreaView> + ); + + return suggested_people_linked === 0 ? ( + <SuggestedPeopleOnboardingStackScreen /> + ) : ( + mainContent() ); }; diff --git a/src/screens/suggestedPeopleOnboarding/SuggestedPeopleUploadPictureScreen.tsx b/src/screens/suggestedPeopleOnboarding/SuggestedPeopleUploadPictureScreen.tsx new file mode 100644 index 00000000..1b30c72f --- /dev/null +++ b/src/screens/suggestedPeopleOnboarding/SuggestedPeopleUploadPictureScreen.tsx @@ -0,0 +1,164 @@ +import React, {useState} from 'react'; +import { + Alert, + Image, + ImageBackground, + StatusBar, + StyleSheet, +} from 'react-native'; +import {Text} from 'react-native-animatable'; +import {TouchableOpacity} from 'react-native-gesture-handler'; +import ImagePicker from 'react-native-image-crop-picker'; +import {SafeAreaView} from 'react-native-safe-area-context'; +import {useDispatch} from 'react-redux'; +import {TaggSquareButton} from '../../components'; +import TaggLoadingIndicator from '../../components/common/TaggLoadingIndicator'; +import {SP_HEIGHT, SP_WIDTH} from '../../constants'; +import {ERROR_UPLOAD} from '../../constants/strings'; +import {sendSuggestedPeoplePhoto} from '../../services'; +import {uploadedSuggestedPeoplePhoto} from '../../store/actions'; +import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; + +const SuggestedPeopleUploadPictureScreen: React.FC = () => { + const [image, setImage] = useState<string | undefined>(undefined); + const [loading, setLoading] = useState(false); + const dispatch = useDispatch(); + + const openImagePicker = () => { + ImagePicker.openPicker({ + smartAlbums: [ + 'Favorites', + 'RecentlyAdded', + 'SelfPortraits', + 'Screenshots', + 'UserLibrary', + ], + width: SP_WIDTH, + height: SP_HEIGHT, + cropping: true, + cropperToolbarTitle: 'Select Photo', + mediaType: 'photo', + }) + .then((picture) => { + if ('path' in picture) { + setImage(picture.path); + } + }) + .catch((_) => {}); + }; + + const uploadImage = async () => { + setLoading(true); + if (image) { + const success = await sendSuggestedPeoplePhoto(image); + if (success) { + dispatch(uploadedSuggestedPeoplePhoto()); + } else { + Alert.alert(ERROR_UPLOAD); + } + } + setLoading(false); + }; + + return ( + <> + {loading && <TaggLoadingIndicator fullscreen />} + <StatusBar barStyle={'light-content'} /> + <SafeAreaView style={styles.container}> + <Text style={styles.title}>PHOTO</Text> + {image ? ( + <Text style={styles.body}>Tap again to choose another photo</Text> + ) : ( + <Text style={styles.body}> + Upload a photo, this is what other users will see + </Text> + )} + {image ? ( + <TouchableOpacity onPress={openImagePicker}> + <ImageBackground + source={{uri: image}} + style={[styles.imageContainer, styles.overlay]} + borderRadius={30}> + <Image + style={styles.overlay} + source={require('../../assets/images/suggested-people-preview-silhouette.png')} + /> + </ImageBackground> + </TouchableOpacity> + ) : ( + <TouchableOpacity onPress={openImagePicker}> + <ImageBackground + source={require('../../assets/images/suggested-people-preview-silhouette.png')} + style={[styles.imageContainer, styles.overlay]}> + <Image + style={styles.images} + source={require('../../assets/images/images.png')} + /> + <Text style={styles.body}>Upload Photo</Text> + </ImageBackground> + </TouchableOpacity> + )} + {image && ( + <TaggSquareButton + onPress={uploadImage} + title={'Done'} + buttonStyle={'normal'} + buttonColor={'purple'} + labelColor={'white'} + style={styles.buttonStyle} + labelStyle={styles.buttonLabel} + /> + )} + </SafeAreaView> + </> + ); +}; + +const styles = StyleSheet.create({ + container: { + width: '100%', + height: '100%', + backgroundColor: '#878787', + alignItems: 'center', + }, + title: { + marginTop: '5%', + fontSize: normalize(25), + lineHeight: normalize(30), + fontWeight: '600', + color: 'white', + }, + body: { + fontSize: normalize(15), + lineHeight: normalize(18), + textAlign: 'center', + fontWeight: '600', + color: 'white', + marginTop: '5%', + width: SCREEN_WIDTH * 0.7, + }, + buttonLabel: { + fontWeight: '600', + fontSize: normalize(15), + }, + buttonStyle: { + width: '40%', + }, + imageContainer: { + marginTop: '10%', + backgroundColor: 'black', + borderRadius: 30, + alignItems: 'center', + }, + overlay: { + height: SCREEN_HEIGHT * 0.6, + aspectRatio: SP_WIDTH / SP_HEIGHT, + }, + images: { + width: normalize(100), + height: normalize(100), + marginTop: '30%', + marginBottom: '10%', + }, +}); +export default SuggestedPeopleUploadPictureScreen; diff --git a/src/screens/suggestedPeopleOnboarding/SuggestedPeopleWelcomeScreen.tsx b/src/screens/suggestedPeopleOnboarding/SuggestedPeopleWelcomeScreen.tsx new file mode 100644 index 00000000..10f3b3a5 --- /dev/null +++ b/src/screens/suggestedPeopleOnboarding/SuggestedPeopleWelcomeScreen.tsx @@ -0,0 +1,81 @@ +import {BlurView} from '@react-native-community/blur'; +import {useNavigation} from '@react-navigation/native'; +import React from 'react'; +import {Image, StatusBar, StyleSheet, TouchableOpacity} from 'react-native'; +import {Text} from 'react-native-animatable'; +import {SafeAreaView} from 'react-native-safe-area-context'; +import UpArrow from '../../assets/icons/up_arrow.svg'; +import {isIPhoneX, normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; + +const SuggestedPeopleWelcomeScreen: React.FC = () => { + const navigation = useNavigation(); + return ( + <> + <StatusBar barStyle={'light-content'} /> + <Image + style={styles.backgroundImage} + source={ + isIPhoneX() + ? require('../../assets/images/suggested-people-preview-large.png') + : require('../../assets/images/suggested-people-preview-small.png') + } + resizeMode={'cover'} + /> + <BlurView blurType={'light'} blurAmount={1}> + <SafeAreaView style={styles.container}> + <Image + style={styles.logo} + source={require('../../assets/images/tagg-logo.png')} + /> + <Text style={styles.body}> + Welcome to the suggested people's page where you can discover new + people!{'\n\n'}Let's get you set up! + </Text> + </SafeAreaView> + </BlurView> + <TouchableOpacity + style={styles.nextButton} + onPress={() => navigation.push('UploadPicture')}> + <UpArrow color={'white'} /> + </TouchableOpacity> + </> + ); +}; + +const styles = StyleSheet.create({ + backgroundImage: { + width: SCREEN_WIDTH, + height: SCREEN_HEIGHT, + position: 'absolute', + }, + container: { + width: '100%', + height: '100%', + backgroundColor: 'rgba(0,0,0,0.65)', + alignItems: 'center', + }, + logo: { + width: normalize(120), + height: normalize(120), + marginTop: '25%', + }, + body: { + fontSize: normalize(20), + lineHeight: normalize(25), + textAlign: 'center', + fontWeight: '700', + color: 'white', + marginTop: '5%', + width: SCREEN_WIDTH * 0.8, + }, + nextButton: { + position: 'absolute', + color: 'white', + transform: [{rotate: '90deg'}], + width: normalize(70), + aspectRatio: 0.86, + bottom: SCREEN_HEIGHT * 0.1, + right: '5%', + }, +}); +export default SuggestedPeopleWelcomeScreen; diff --git a/src/screens/suggestedPeopleOnboarding/index.ts b/src/screens/suggestedPeopleOnboarding/index.ts new file mode 100644 index 00000000..6bd9f1b2 --- /dev/null +++ b/src/screens/suggestedPeopleOnboarding/index.ts @@ -0,0 +1,2 @@ +export {default as SuggestedPeopleWelcomeScreen} from './SuggestedPeopleWelcomeScreen'; +export {default as SuggestedPeopleUploadPictureScreen} from './SuggestedPeopleUploadPictureScreen'; diff --git a/src/services/SuggestedPeopleService.ts b/src/services/SuggestedPeopleService.ts new file mode 100644 index 00000000..7e43c3b6 --- /dev/null +++ b/src/services/SuggestedPeopleService.ts @@ -0,0 +1,50 @@ +import AsyncStorage from '@react-native-community/async-storage'; +import {EDIT_PROFILE_ENDPOINT, SP_UPDATE_PICTURE} from '../constants'; + +export const sendSuggestedPeopleLinked = async ( + userId: string, + stage: number, +) => { + try { + const request = new FormData(); + request.append('suggested_people_linked', stage); + const endpoint = EDIT_PROFILE_ENDPOINT + `${userId}/`; + const token = await AsyncStorage.getItem('token'); + let response = await fetch(endpoint, { + method: 'PATCH', + headers: { + 'Content-Type': 'multipart/form-data', + Authorization: 'Token ' + token, + }, + body: request, + }); + return response.status === 200; + } catch (error) { + console.log('Error updating animated tutorial close button press'); + return false; + } +}; + +export const sendSuggestedPeoplePhoto = async (photoUri: string) => { + try { + const token = await AsyncStorage.getItem('token'); + const form = new FormData(); + form.append('suggested_people', { + uri: photoUri, + name: 'sp_photo.jpg', + type: 'image/jpg', + }); + const response = await fetch(SP_UPDATE_PICTURE, { + method: 'POST', + headers: { + 'Content-Type': 'multipart/form-data', + Authorization: 'Token ' + token, + }, + body: form, + }); + return response.status === 201; + } catch (error) { + console.log('Error uploading SP photo'); + return false; + } +}; diff --git a/src/services/UserProfileService.ts b/src/services/UserProfileService.ts index 3bca66f3..fff35370 100644 --- a/src/services/UserProfileService.ts +++ b/src/services/UserProfileService.ts @@ -2,20 +2,18 @@ import AsyncStorage from '@react-native-community/async-storage'; import moment from 'moment'; import {Alert} from 'react-native'; import RNFetchBlob from 'rn-fetch-blob'; -import {SocialAccountType, UserType} from '../types'; import { - PROFILE_PHOTO_ENDPOINT, - HEADER_PHOTO_ENDPOINT, GET_FB_POSTS_ENDPOINT, GET_IG_POSTS_ENDPOINT, GET_TWITTER_POSTS_ENDPOINT, - PROFILE_INFO_ENDPOINT, + HEADER_PHOTO_ENDPOINT, PASSWORD_RESET_ENDPOINT, + PROFILE_INFO_ENDPOINT, + PROFILE_PHOTO_ENDPOINT, + PROFILE_PHOTO_THUMBNAIL_ENDPOINT, + SEND_OTP_ENDPOINT, TAGG_CUSTOMER_SUPPORT, VERIFY_OTP_ENDPOINT, - SEND_OTP_ENDPOINT, - PROFILE_PHOTO_THUMBNAIL_ENDPOINT, - EDIT_PROFILE_ENDPOINT, } from '../constants'; import { ERROR_DOUBLE_CHECK_CONNECTION, @@ -28,6 +26,7 @@ import { SUCCESS_PWD_RESET, SUCCESS_VERIFICATION_CODE_SENT, } from '../constants/strings'; +import {SocialAccountType} from '../types'; export const loadProfileInfo = async (token: string, userId: string) => { try { @@ -50,15 +49,11 @@ export const loadProfileInfo = async (token: string, userId: string) => { tiktok, university_class, profile_completion_stage, - sp_swipe_tutorial, + suggested_people_linked, friendship_status, friendship_requester_id, } = info; birthday = birthday && moment(birthday).format('YYYY-MM-DD'); - console.log( - 'Suggested People loaded from backend for logged in user: ', - sp_swipe_tutorial, - ); return { name, biography, @@ -69,7 +64,7 @@ export const loadProfileInfo = async (token: string, userId: string) => { tiktok, university_class, profile_completion_stage, - sp_swipe_tutorial, + suggested_people_linked, friendship_status, friendship_requester_id, }; @@ -320,27 +315,3 @@ export const sendOtp = async (phone: string) => { return false; } }; - -export const editSPSwipeTutorial = async (user: UserType) => { - try { - const request = new FormData(); - request.append('sp_swipe_tutorial', 1); - const endpoint = EDIT_PROFILE_ENDPOINT + `${user.userId}/`; - const token = await AsyncStorage.getItem('token'); - let response = await fetch(endpoint, { - method: 'PATCH', - headers: { - 'Content-Type': 'multipart/form-data', - Authorization: 'Token ' + token, - }, - body: request, - }); - if (response.status === 200) { - return true; - } else { - return false; - } - } catch (error) { - console.log('Error updating animated tutorial close button press'); - } -}; diff --git a/src/services/index.ts b/src/services/index.ts index 9c168d4f..ef71233a 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -11,3 +11,4 @@ export * from './FCMService'; export * from './WaitlistUserService'; export * from './CommonService'; export * from './CommentService'; +export * from './SuggestedPeopleService'; diff --git a/src/store/actions/user.ts b/src/store/actions/user.ts index 990f9260..ef134dc5 100644 --- a/src/store/actions/user.ts +++ b/src/store/actions/user.ts @@ -1,9 +1,9 @@ import {Action, ThunkAction} from '@reduxjs/toolkit'; import { - editSPSwipeTutorial, loadAvatar, loadCover, loadProfileInfo, + sendSuggestedPeopleLinked, } from '../../services'; import {UserType} from '../../types/types'; import {getTokenOrLogout} from '../../utils'; @@ -13,11 +13,11 @@ import { setNewNotificationReceived, setNewVersionAvailable, setReplyPosted, + setSuggestedPeopleLinked, socialEdited, userDetailsFetched, userLoggedIn, } from '../reducers'; -import {spSwipeTutorialUpdated} from '../reducers/userReducer'; import {RootState} from '../rootReducer'; import {CommentThreadType} from './../../types/types'; @@ -163,9 +163,24 @@ export const logout = (): ThunkAction< } }; -export const updateSPSwipeTutorial = ( - user: UserType, - data: number, +export const uploadedSuggestedPeoplePhoto = (): ThunkAction< + Promise<void>, + RootState, + unknown, + Action<string> +> => async (dispatch) => { + try { + dispatch({ + type: setSuggestedPeopleLinked.type, + payload: {stage: 1}, + }); + } catch (error) { + console.log(error); + } +}; + +export const suggestedPeopleAnimatedTutorialFinished = ( + userId: string, ): ThunkAction< Promise<boolean | undefined>, RootState, @@ -173,12 +188,13 @@ export const updateSPSwipeTutorial = ( Action<string> > => async (dispatch) => { try { - // update store first, assume success + // update store first, assume request is successful dispatch({ - type: spSwipeTutorialUpdated.type, - payload: {sp_swipe_tutorial: data}, + type: setSuggestedPeopleLinked.type, + payload: {stage: 2}, }); - return await editSPSwipeTutorial(user); + // need to tell the server that the stage is now 2 + return await sendSuggestedPeopleLinked(userId, 2); } catch (error) { console.log('Error while updating suggested people linked state: ', error); } diff --git a/src/store/initialStates.ts b/src/store/initialStates.ts index 93b1bc6e..10fdad25 100644 --- a/src/store/initialStates.ts +++ b/src/store/initialStates.ts @@ -21,7 +21,7 @@ export const NO_PROFILE: ProfileType = { //Default to an invalid value and ignore it gracefully while showing tutorials / popups. profile_completion_stage: -1, - sp_swipe_tutorial: 0, + suggested_people_linked: -1, snapchat: '', tiktok: '', friendship_status: 'no_record', diff --git a/src/store/reducers/userReducer.ts b/src/store/reducers/userReducer.ts index 773977db..5203fa3c 100644 --- a/src/store/reducers/userReducer.ts +++ b/src/store/reducers/userReducer.ts @@ -46,8 +46,8 @@ const userDataSlice = createSlice({ state.profile.profile_completion_stage = action.payload.stage; }, - spSwipeTutorialUpdated: (state, action) => { - state.profile.sp_swipe_tutorial = action.payload.sp_swipe_tutorial; + setSuggestedPeopleLinked: (state, action) => { + state.profile.suggested_people_linked = action.payload.stage; }, setIsOnboardedUser: (state, action) => { @@ -73,10 +73,10 @@ export const { userDetailsFetched, socialEdited, profileCompletionStageUpdated, + setSuggestedPeopleLinked, setIsOnboardedUser, setNewVersionAvailable, setNewNotificationReceived, setReplyPosted, - spSwipeTutorialUpdated, } = userDataSlice.actions; export const userDataReducer = userDataSlice.reducer; diff --git a/src/types/types.ts b/src/types/types.ts index 874b9eb7..3c17cfa4 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -23,7 +23,7 @@ export interface ProfileType { gender: string; university_class: number; profile_completion_stage: number; - sp_swipe_tutorial: number; + suggested_people_linked: number; birthday: Date | undefined; snapchat: string; tiktok: string; |