aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ios/Frontend/Info.plist2
-rw-r--r--package.json1
-rw-r--r--src/App.tsx12
-rw-r--r--src/assets/icons/findFriends/find-friend-icon.pngbin0 -> 100964 bytes
-rw-r--r--src/assets/icons/findFriends/lock-icon.pngbin0 -> 7272 bytes
-rw-r--r--src/assets/icons/findFriends/phone-cross-icon.pngbin0 -> 16729 bytes
-rw-r--r--src/components/common/FriendsButton.tsx111
-rw-r--r--src/components/common/TaggSquareButton.tsx4
-rw-r--r--src/components/common/index.ts1
-rw-r--r--src/components/moments/IndividualMomentTitleBar.tsx7
-rw-r--r--src/components/notifications/Notification.tsx149
-rw-r--r--src/components/profile/Content.tsx28
-rw-r--r--src/components/profile/Friends.tsx133
-rw-r--r--src/components/profile/ProfileBody.tsx47
-rw-r--r--src/components/profile/ProfilePreview.tsx32
-rw-r--r--src/constants/constants.ts5
-rw-r--r--src/routes/Routes.tsx8
-rw-r--r--src/routes/main/MainStackNavigator.tsx3
-rw-r--r--src/routes/main/MainStackScreen.tsx36
-rw-r--r--src/screens/main/NotificationsScreen.tsx5
-rw-r--r--src/screens/onboarding/CategorySelection.tsx5
-rw-r--r--src/screens/onboarding/ProfileOnboarding.tsx3
-rw-r--r--src/screens/profile/FriendsListScreen.tsx99
-rw-r--r--src/screens/profile/MomentCommentsScreen.tsx8
-rw-r--r--src/screens/profile/ProfileScreen.tsx6
-rw-r--r--src/screens/search/RequestContactsAccess.tsx186
-rw-r--r--src/screens/search/index.ts1
-rw-r--r--src/services/UserFriendsService.ts29
-rw-r--r--src/store/actions/userFriends.ts38
-rw-r--r--src/store/reducers/userFriendsReducer.ts10
-rw-r--r--src/types/types.ts24
-rw-r--r--src/utils/friends.ts54
-rw-r--r--src/utils/index.ts1
33 files changed, 784 insertions, 264 deletions
diff --git a/ios/Frontend/Info.plist b/ios/Frontend/Info.plist
index dd9ebb83..0d75ca35 100644
--- a/ios/Frontend/Info.plist
+++ b/ios/Frontend/Info.plist
@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
+ <key>NSContactsUsageDescription</key>
+ <string>This helps you quickly get in touch with friends on the app and more</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
diff --git a/package.json b/package.json
index a40111a0..0451e743 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"react-native": "0.63.3",
"react-native-animatable": "^1.3.3",
"react-native-confirmation-code-field": "^6.5.0",
+ "react-native-contacts": "^6.0.4",
"react-native-date-picker": "^3.2.5",
"react-native-device-info": "^7.3.1",
"react-native-elements": "^2.3.2",
diff --git a/src/App.tsx b/src/App.tsx
index 18fadf64..ea3617dc 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,17 +1,11 @@
-import React, {useEffect} from 'react';
import {NavigationContainer} from '@react-navigation/native';
-import Routes from './routes';
+import React from 'react';
import {Provider} from 'react-redux';
-import store from './store/configureStore';
-import {fcmService} from './services/FCMService';
import {navigationRef} from './RootNavigation';
+import Routes from './routes';
+import store from './store/configureStore';
const App = () => {
- useEffect(() => {
- fcmService.setUpPushNotifications();
- // TODO: If permissions are not there, deactivateFcmService
- });
-
return (
/**
* This is the provider from the redux store, it acts as the root provider for our application
diff --git a/src/assets/icons/findFriends/find-friend-icon.png b/src/assets/icons/findFriends/find-friend-icon.png
new file mode 100644
index 00000000..62abcac0
--- /dev/null
+++ b/src/assets/icons/findFriends/find-friend-icon.png
Binary files differ
diff --git a/src/assets/icons/findFriends/lock-icon.png b/src/assets/icons/findFriends/lock-icon.png
new file mode 100644
index 00000000..20ccbe2f
--- /dev/null
+++ b/src/assets/icons/findFriends/lock-icon.png
Binary files differ
diff --git a/src/assets/icons/findFriends/phone-cross-icon.png b/src/assets/icons/findFriends/phone-cross-icon.png
new file mode 100644
index 00000000..0801e9f1
--- /dev/null
+++ b/src/assets/icons/findFriends/phone-cross-icon.png
Binary files differ
diff --git a/src/components/common/FriendsButton.tsx b/src/components/common/FriendsButton.tsx
new file mode 100644
index 00000000..6ef23a96
--- /dev/null
+++ b/src/components/common/FriendsButton.tsx
@@ -0,0 +1,111 @@
+import React from 'react';
+import {StyleSheet} from 'react-native';
+import {Button} from 'react-native-elements';
+import {ScreenType} from '../../types';
+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';
+
+interface ProfileBodyProps {
+ userXId: string | undefined;
+ screenType: ScreenType;
+}
+const FriendsButton: React.FC<ProfileBodyProps> = ({userXId, screenType}) => {
+ const dispatch = useDispatch();
+
+ const {user = NO_USER, profile = NO_PROFILE} = userXId
+ ? useSelector((state: RootState) => state.userX[screenType][userXId])
+ : useSelector((state: RootState) => state.user);
+
+ const {user: loggedInUser = NO_USER} = useSelector(
+ (state: RootState) => state.user,
+ );
+
+ 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(
+ screenType,
+ user,
+ profile,
+ dispatch,
+ state,
+ loggedInUser,
+ )
+ } // unfriend, no record status
+ />
+ )}
+ </>
+ );
+};
+
+const styles = StyleSheet.create({
+ requestedButton: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: SCREEN_WIDTH * 0.4,
+ height: SCREEN_WIDTH * 0.075,
+ borderColor: TAGG_LIGHT_BLUE,
+ borderWidth: 2,
+ borderRadius: 0,
+ marginRight: '2%',
+ marginLeft: '1%',
+ 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: 0,
+ marginRight: '2%',
+ marginLeft: '1%',
+ backgroundColor: TAGG_LIGHT_BLUE,
+ },
+});
+
+export default FriendsButton;
diff --git a/src/components/common/TaggSquareButton.tsx b/src/components/common/TaggSquareButton.tsx
index c6064b92..78a90554 100644
--- a/src/components/common/TaggSquareButton.tsx
+++ b/src/components/common/TaggSquareButton.tsx
@@ -8,7 +8,7 @@ import {
ViewStyle,
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
-import {BACKGROUND_GRADIENT_MAP} from '../../constants';
+import {BACKGROUND_GRADIENT_MAP, TAGG_PURPLE} from '../../constants';
import {normalize, SCREEN_WIDTH} from '../../utils';
interface TaggSquareButtonProps extends ViewProps {
@@ -23,7 +23,7 @@ const TaggSquareButton: React.FC<TaggSquareButtonProps> = (props) => {
const buttonStyles = (() => {
switch (props.color) {
case 'purple':
- return {backgroundColor: '#8F01FF'};
+ return {backgroundColor: TAGG_PURPLE};
case 'white':
default:
return {backgroundColor: 'white'};
diff --git a/src/components/common/index.ts b/src/components/common/index.ts
index a5718c1e..95854ba8 100644
--- a/src/components/common/index.ts
+++ b/src/components/common/index.ts
@@ -20,4 +20,5 @@ export {default as GenericMoreInfoDrawer} from './GenericMoreInfoDrawer';
export {default as TaggPopUp} from './TaggPopup';
export {default as TaggPrompt} from './TaggPrompt';
export {default as AcceptDeclineButtons} from './AcceptDeclineButtons';
+export {default as FriendsButton} from './FriendsButton';
export {default as TaggSquareButton} from './TaggSquareButton';
diff --git a/src/components/moments/IndividualMomentTitleBar.tsx b/src/components/moments/IndividualMomentTitleBar.tsx
index bd5b307f..6cdfe0e8 100644
--- a/src/components/moments/IndividualMomentTitleBar.tsx
+++ b/src/components/moments/IndividualMomentTitleBar.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import {TouchableOpacity} from 'react-native';
import {Text, View, StyleSheet, ViewProps} from 'react-native';
+import {normalize} from '../../utils';
import CloseIcon from '../../assets/ionicons/close-outline.svg';
interface IndividualMomentTitleBarProps extends ViewProps {
@@ -30,9 +31,11 @@ const styles = StyleSheet.create({
height: '5%',
},
header: {
- fontSize: 20,
- fontWeight: 'bold',
color: 'white',
+ fontSize: normalize(18),
+ fontWeight: '700',
+ lineHeight: normalize(21.48),
+ letterSpacing: normalize(1.3),
},
closeButton: {
position: 'absolute',
diff --git a/src/components/notifications/Notification.tsx b/src/components/notifications/Notification.tsx
index 951a5bf6..c2c6c4e4 100644
--- a/src/components/notifications/Notification.tsx
+++ b/src/components/notifications/Notification.tsx
@@ -6,11 +6,7 @@ import LinearGradient from 'react-native-linear-gradient';
import {useDispatch, useStore} from 'react-redux';
import {BACKGROUND_GRADIENT_MAP} from '../../constants';
import {ERROR_DELETED_OBJECT} from '../../constants/strings';
-import {
- loadImageFromURL,
- loadMoments,
- loadMomentThumbnail,
-} from '../../services';
+import {loadImageFromURL} from '../../services';
import {
acceptFriendRequest,
declineFriendRequest,
@@ -19,19 +15,22 @@ import {
updateUserXFriends,
} from '../../store/actions';
import {RootState} from '../../store/rootReducer';
-import {MomentType, NotificationType, ScreenType} from '../../types';
import {
- fetchUserX,
- getTokenOrLogout,
- SCREEN_HEIGHT,
- userXInStore,
-} from '../../utils';
+ CommentNotificationType,
+ CommentThreadType,
+ MomentType,
+ NotificationType,
+ ScreenType,
+ ThreadNotificationType,
+ UserType,
+} from '../../types';
+import {fetchUserX, SCREEN_HEIGHT, userXInStore} from '../../utils';
import AcceptDeclineButtons from '../common/AcceptDeclineButtons';
interface NotificationProps {
item: NotificationType;
screenType: ScreenType;
- moments: MomentType[];
+ loggedInUser: UserType;
}
const Notification: React.FC<NotificationProps> = (props) => {
@@ -44,7 +43,7 @@ const Notification: React.FC<NotificationProps> = (props) => {
unread,
},
screenType,
- moments: loggedInUserMoments,
+ loggedInUser,
} = props;
const navigation = useNavigation();
@@ -53,7 +52,6 @@ const Notification: React.FC<NotificationProps> = (props) => {
const [avatar, setAvatar] = useState<string | undefined>(undefined);
const [momentURI, setMomentURI] = useState<string | undefined>(undefined);
- const [onTapLoadProfile, setOnTapLoadProfile] = useState<boolean>(false);
useEffect(() => {
(async () => {
@@ -67,40 +65,22 @@ const Notification: React.FC<NotificationProps> = (props) => {
}, []);
useEffect(() => {
- if (onTapLoadProfile) {
- fetchUserX(dispatch, {userId: id, username: username}, screenType);
- }
- return () => {
- setOnTapLoadProfile(false);
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [onTapLoadProfile]);
-
- useEffect(() => {
- let mounted = true;
- const loadMomentImage = async (moment_id: string) => {
- const response = await loadMomentThumbnail(moment_id);
- if (mounted && response) {
- setMomentURI(response);
- } else {
- // if not set to empty, it will re-use the previous notification's state
- setMomentURI(undefined);
- }
- };
- if (
- (notification_type === 'CMT' ||
+ if (notification_object) {
+ let url: string | undefined;
+ let obj;
+ if (
notification_type === 'MOM_3+' ||
- notification_type === 'MOM_FRIEND') &&
- notification_object
- ) {
- loadMomentImage(
- notification_object.moment_id
- ? notification_object.moment_id
- : notification_object.parent_comment.moment_id,
- );
- return () => {
- mounted = false;
- };
+ notification_type === 'MOM_FRIEND'
+ ) {
+ obj = notification_object as MomentType;
+ url = obj.thumbnail_url;
+ } else if (notification_type === 'CMT') {
+ obj = notification_object as CommentNotificationType;
+ url = obj.notification_data.thumbnail_url;
+ }
+ if (url) {
+ setMomentURI(url);
+ }
}
}, [id, notification_object, notification_type]);
@@ -126,53 +106,64 @@ const Notification: React.FC<NotificationProps> = (props) => {
Alert.alert(ERROR_DELETED_OBJECT);
break;
}
- let {moment_id} = notification_object;
- let {comment_id} = notification_object;
- //If this is a thread, get comment_id and moment_id from parent_comment
- if (!notification_object?.moment_id) {
- moment_id = notification_object?.parent_comment?.moment_id;
- comment_id = notification_object?.parent_comment?.comment_id;
- }
+ /**
+ * Notification object knows
+ * 1 - Which comment
+ * 2 - Which user
+ * The comment / reply belongs to
+ * STEP 1 : Populate reply / comment
+ * STEP 2 : Load user data if moment does not belong to the logged in user
+ * STEP 3 : Navigate to relevant moment
+ */
- // Now find the moment we need to display
- let moment: MomentType | undefined = loggedInUserMoments?.find(
- (m) => m.moment_id === moment_id,
- );
+ let comment_id: string;
+ let not_object;
+ let reply: CommentThreadType | undefined;
let userXId;
- // If moment does not belong to the logged in user, then the comment was probably a reply to logged in user's comment
- // on userX's moment
- // Load moments for userX
- if (!moment) {
- let moments: MomentType[] = [];
- try {
- //Populate local state in the mean time
- setOnTapLoadProfile(true);
- const token = await getTokenOrLogout(dispatch);
- moments = await loadMoments(id, token);
- } catch (err) {
- console.log(err);
- }
- moment = moments?.find((m) => m.moment_id === moment_id);
- userXId = id;
+ // STEP 1
+ if ('parent_comment' in notification_object) {
+ //This is a reply
+ not_object = notification_object as ThreadNotificationType;
+ comment_id = not_object.parent_comment;
+ reply = {
+ parent_comment: {comment_id: comment_id},
+ comment_id: not_object.comment_id,
+ };
+ } else {
+ not_object = notification_object as CommentNotificationType;
+ comment_id = not_object.comment_id;
}
- //Now if moment was found, navigate to the respective moment
+ //STEP 2
+ const {user, ...moment} = not_object.notification_data;
+ if (user.id !== loggedInUser.userId) {
+ fetchUserX(
+ dispatch,
+ {userId: user.id, username: user.username},
+ screenType,
+ );
+ userXId = user.id;
+ }
+
+ const {moment_id} = moment;
+
+ //STEP 3
if (moment) {
- if (notification_object?.parent_comment) {
- dispatch(updateReplyPosted(notification_object));
+ if (reply) {
+ dispatch(updateReplyPosted(reply));
}
navigation.push('IndividualMoment', {
moment,
- userXId: userXId, // we're only viewing our own moment here
+ userXId,
screenType,
});
setTimeout(() => {
navigation.push('MomentCommentsScreen', {
- moment_id: moment_id,
+ moment_id,
screenType,
- comment_id: comment_id,
+ comment_id,
});
}, 500);
}
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx
index a35a5820..28000dd7 100644
--- a/src/components/profile/Content.tsx
+++ b/src/components/profile/Content.tsx
@@ -25,11 +25,9 @@ import {
import {
blockUnblockUser,
deleteUserMomentsForCategory,
- friendUnfriendUser,
loadFriendsData,
updateMomentCategories,
updateUserXFriends,
- updateUserXProfileAllScreens,
} from '../../store/actions';
import {
EMPTY_MOMENTS_LIST,
@@ -216,31 +214,6 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
}
}, [blockedUsers, user]);
- // Handles click on friend/requested/unfriend button
- /*
- * When user logged in clicks on the friend button:
- A request is sent.
- Which means you have to update the status of their friendshpi to requested
- When the status is changed to requested the button should change to requested.
- When the button is changed to requested and thr user clicks on it,
- a request much go to the backend to delete that request
- When that succeeds, their friendship must be updated to no-record again;
- When the button is changed to no_record, the add friends button should be displayed again
- */
- const handleFriendUnfriend = async () => {
- const {friendship_status} = profile;
- await dispatch(
- friendUnfriendUser(
- loggedInUser,
- getUserAsProfilePreviewType(user, profile),
- friendship_status,
- screenType,
- ),
- );
- await dispatch(updateUserXFriends(user.userId, state));
- dispatch(updateUserXProfileAllScreens(user.userId, state));
- };
-
/**
* Handles a click on the block / unblock button.
* loadFriends updates friends list for the logged in user
@@ -332,7 +305,6 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
userXId,
screenType,
isFriend,
- handleFriendUnfriend,
handleBlockUnblock,
isBlocked,
}}
diff --git a/src/components/profile/Friends.tsx b/src/components/profile/Friends.tsx
index 23ce28fe..9b9e5302 100644
--- a/src/components/profile/Friends.tsx
+++ b/src/components/profile/Friends.tsx
@@ -1,64 +1,123 @@
import React from 'react';
-import {View, StyleSheet, Text} from 'react-native';
+import {View, StyleSheet, ScrollView, Text} from 'react-native';
import {ProfilePreviewType, ScreenType} from '../../types';
import {ProfilePreview} from '..';
-import {useNavigation} from '@react-navigation/native';
import {Button} from 'react-native-elements';
+import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import {TAGG_LIGHT_BLUE} from '../../constants';
+import {RootState} from '../../store/rootReducer';
+import {useDispatch, useStore} from 'react-redux';
+import {handleUnfriend} from '../../utils/friends';
+import {NO_USER} from '../../store/initialStates';
+import {TouchableOpacity} from 'react-native-gesture-handler';
interface FriendsProps {
result: Array<ProfilePreviewType>;
screenType: ScreenType;
+ userId: string;
}
-const Friends: React.FC<FriendsProps> = ({result, screenType}) => {
- const navigation = useNavigation();
+const Friends: React.FC<FriendsProps> = ({result, screenType, userId}) => {
+ const state: RootState = useStore().getState();
+ const dispatch = useDispatch();
+
+ const {user: loggedInUser = NO_USER} = state;
+
return (
<>
- <View style={styles.header}>
- <Button
- title="X"
- buttonStyle={styles.button}
- titleStyle={styles.buttonText}
- onPress={() => {
- navigation.pop();
- }}
- />
- <Text style={styles.title}>Friends</Text>
+ <View style={styles.subheader}>
+ <Text style={styles.subheaderText}>Friends</Text>
</View>
- {result.map((profilePreview) => (
- <ProfilePreview
- style={styles.friend}
- key={profilePreview.id}
- {...{profilePreview}}
- previewType={'Friend'}
- screenType={screenType}
- />
- ))}
+ <ScrollView
+ keyboardShouldPersistTaps={'always'}
+ stickyHeaderIndices={[4]}
+ style={styles.scrollView}
+ contentContainerStyle={styles.scrollViewContent}
+ showsVerticalScrollIndicator={false}>
+ {result.map((profilePreview) => (
+ <View key={profilePreview.id} style={styles.container}>
+ <View style={styles.friend}>
+ <ProfilePreview
+ {...{profilePreview}}
+ previewType={'Friend'}
+ screenType={screenType}
+ />
+ </View>
+ {loggedInUser.userId === userId && (
+ <TouchableOpacity
+ style={styles.button}
+ onPress={() =>
+ handleUnfriend(screenType, profilePreview, dispatch, state)
+ }>
+ <Text style={styles.buttonTitle}>Unfriend</Text>
+ </TouchableOpacity>
+ )}
+ </View>
+ ))}
+ </ScrollView>
</>
);
};
const styles = StyleSheet.create({
+ scrollView: {
+ flexDirection: 'column',
+ alignSelf: 'center',
+ width: SCREEN_WIDTH * 0.85,
+ height: SCREEN_HEIGHT,
+ },
+ scrollViewContent: {
+ alignSelf: 'center',
+ paddingBottom: SCREEN_HEIGHT / 15,
+ width: SCREEN_WIDTH * 0.85,
+ height: SCREEN_HEIGHT * 0.75,
+ marginTop: '1%',
+ },
header: {flexDirection: 'row'},
- friend: {
- marginVertical: 10,
+ subheader: {
+ alignSelf: 'center',
+ width: SCREEN_WIDTH * 0.85,
+ marginVertical: '3%',
+ },
+ subheaderText: {
+ color: '#828282',
+ fontSize: normalize(12),
+ fontWeight: '600',
+ lineHeight: normalize(14.32),
},
- title: {
- position: 'relative',
- fontSize: 17,
- fontWeight: 'bold',
- paddingBottom: 10,
- paddingTop: 10,
- flexGrow: 1,
- paddingLeft: '26%',
+ container: {
+ alignSelf: 'center',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ width: '100%',
+ height: normalize(42),
+ alignItems: 'center',
+ marginBottom: '5%',
+ },
+ friend: {
+ alignSelf: 'center',
+ height: '100%',
},
button: {
+ alignSelf: 'center',
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: '100%',
+ height: '55%',
+ borderColor: TAGG_LIGHT_BLUE,
+ borderWidth: 2,
+ borderRadius: 2,
+ padding: 0,
backgroundColor: 'transparent',
},
- buttonText: {
- color: 'black',
- fontSize: 18,
- fontWeight: '400',
+ buttonTitle: {
+ color: TAGG_LIGHT_BLUE,
+ padding: 0,
+ fontSize: normalize(11),
+ fontWeight: '700',
+ lineHeight: normalize(13.13),
+ letterSpacing: normalize(0.6),
+ paddingHorizontal: '3.8%',
},
});
diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx
index f2d75519..106b20a7 100644
--- a/src/components/profile/ProfileBody.tsx
+++ b/src/components/profile/ProfileBody.tsx
@@ -9,14 +9,17 @@ import {
import ToggleButton from './ToggleButton';
import {RootState} from '../../store/rootReducer';
import {useDispatch, useSelector, useStore} from 'react-redux';
-import {FriendshipStatusType, ScreenType} from '../../types';
-import {NO_PROFILE} from '../../store/initialStates';
-import {getUserAsProfilePreviewType, SCREEN_WIDTH} from '../../utils';
-import {AcceptDeclineButtons} from '../common';
+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,
- loadUserNotifications,
updateUserXFriends,
updateUserXProfileAllScreens,
} from '../../store/actions';
@@ -24,7 +27,6 @@ import {
interface ProfileBodyProps {
onLayout: (event: LayoutChangeEvent) => void;
isBlocked: boolean;
- handleFriendUnfriend: () => void;
handleBlockUnblock: () => void;
userXId: string | undefined;
screenType: ScreenType;
@@ -32,7 +34,6 @@ interface ProfileBodyProps {
const ProfileBody: React.FC<ProfileBodyProps> = ({
onLayout,
isBlocked,
- handleFriendUnfriend,
handleBlockUnblock,
userXId,
screenType,
@@ -41,6 +42,10 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({
? useSelector((state: RootState) => state.userX[screenType][userXId])
: useSelector((state: RootState) => state.user);
+ const {user: loggedInUser = NO_USER} = useSelector(
+ (state: RootState) => state.user,
+ );
+
const {
biography,
website,
@@ -94,29 +99,23 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({
)}
{userXId && !isBlocked && (
<View style={styles.buttonsContainer}>
- {friendship_status === 'no_record' && (
- <Button
- title={'Add Friend'}
- buttonStyle={styles.button}
- titleStyle={styles.buttonTitle}
- onPress={handleFriendUnfriend} // requested, requested status
- />
- )}
- {friendship_status === 'friends' && (
- <Button
- title={'Unfriend'}
- buttonStyle={styles.requestedButton}
- titleStyle={styles.requestedButtonTitle}
- onPress={handleFriendUnfriend} // unfriend, no record status
- />
- )}
+ <FriendsButton userXId={userXId} screenType={screenType} />
{(friendship_status === 'requested' &&
friendship_requester_id !== userXId && (
<Button
title={'Requested'}
buttonStyle={styles.requestedButton}
titleStyle={styles.requestedButtonTitle}
- onPress={handleFriendUnfriend} // delete request, no record status
+ onPress={() =>
+ handleFriendUnfriend(
+ screenType,
+ user,
+ profile,
+ dispatch,
+ state,
+ loggedInUser,
+ )
+ } // delete request, no record status
/>
)) ||
(friendship_status === 'requested' &&
diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx
index 38defb8d..fad3ec09 100644
--- a/src/components/profile/ProfilePreview.tsx
+++ b/src/components/profile/ProfilePreview.tsx
@@ -15,7 +15,13 @@ import {ERROR_UNABLE_TO_VIEW_PROFILE} from '../../constants/strings';
import {loadImageFromURL} from '../../services';
import {RootState} from '../../store/rootreducer';
import {PreviewType, ProfilePreviewType, ScreenType} from '../../types';
-import {checkIfUserIsBlocked, fetchUserX, userXInStore} from '../../utils';
+import {
+ checkIfUserIsBlocked,
+ fetchUserX,
+ normalize,
+ SCREEN_WIDTH,
+ userXInStore,
+} from '../../utils';
/**
* This component returns user's profile picture friended by username as a touchable component.
@@ -308,29 +314,31 @@ const styles = StyleSheet.create({
friendContainer: {
flexDirection: 'row',
alignItems: 'center',
- marginVertical: 10,
+ justifyContent: 'flex-start',
+ width: SCREEN_WIDTH * 0.6,
},
friendAvatar: {
- height: 42,
- width: 42,
+ height: normalize(42),
+ width: normalize(42),
marginRight: 15,
- borderRadius: 20,
+ borderRadius: 50,
},
friendNameContainer: {
+ height: '100%',
justifyContent: 'space-evenly',
alignSelf: 'stretch',
},
friendUsername: {
- fontSize: 14,
- fontWeight: '700',
- color: '#3C3C3C',
- letterSpacing: 0.6,
+ fontSize: normalize(14),
+ fontWeight: '500',
+ color: '#000',
+ letterSpacing: normalize(0.1),
},
friendName: {
- fontSize: 12,
- fontWeight: '500',
+ fontSize: normalize(12),
+ fontWeight: '400',
color: '#6C6C6C',
- letterSpacing: 0.5,
+ letterSpacing: normalize(0.1),
},
});
diff --git a/src/constants/constants.ts b/src/constants/constants.ts
index 7fcc457f..f58aa686 100644
--- a/src/constants/constants.ts
+++ b/src/constants/constants.ts
@@ -60,6 +60,7 @@ export const LINKEDIN_FONT_COLOR: string = '#78B5FD';
export const SNAPCHAT_FONT_COLOR: string = '#FFFC00';
export const YOUTUBE_FONT_COLOR: string = '#FCA4A4';
+export const TAGG_PURPLE = '#8F01FF';
export const TAGG_DARK_BLUE = '#4E699C';
export const TAGG_LIGHT_BLUE: string = '#698DD3';
export const TAGG_LIGHT_PURPLE = '#F4DDFF';
@@ -131,8 +132,8 @@ export const BACKGROUND_GRADIENT_MAP: Record<
[BackgroundGradientType.Light]: ['#9F00FF', '#27EAE9'],
[BackgroundGradientType.Dark]: ['#421566', '#385D5E'],
[BackgroundGradientType.Notification]: [
- 'rgba(143, 1, 255, 0.5)',
- 'rgba(110, 231, 231, 0.5)',
+ 'rgba(143, 1, 255, 0.4)',
+ 'rgba(110, 231, 231, 0.4)',
],
};
diff --git a/src/routes/Routes.tsx b/src/routes/Routes.tsx
index a14f1576..a5383a47 100644
--- a/src/routes/Routes.tsx
+++ b/src/routes/Routes.tsx
@@ -7,6 +7,7 @@ import {userLogin} from '../utils';
import SplashScreen from 'react-native-splash-screen';
import messaging from '@react-native-firebase/messaging';
import {updateNewNotificationReceived} from '../store/actions';
+import {fcmService} from '../services';
const Routes: React.FC = () => {
const {
@@ -39,6 +40,13 @@ const Routes: React.FC = () => {
}
}, [dispatch, userId]);
+ useEffect(() => {
+ if (userId) {
+ fcmService.setUpPushNotifications();
+ fcmService.sendFcmTokenToServer();
+ }
+ });
+
return userId ? <NavigationBar /> : <Onboarding />;
};
diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx
index 663aeaea..74993af9 100644
--- a/src/routes/main/MainStackNavigator.tsx
+++ b/src/routes/main/MainStackNavigator.tsx
@@ -14,6 +14,9 @@ export type MainStackParams = {
Search: {
screenType: ScreenType;
};
+ RequestContactsAccess: {
+ screenType: ScreenType;
+ };
Profile: {
userXId: string | undefined;
screenType: ScreenType;
diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx
index 3e425101..c0cef3ea 100644
--- a/src/routes/main/MainStackScreen.tsx
+++ b/src/routes/main/MainStackScreen.tsx
@@ -1,6 +1,7 @@
+import AsyncStorage from '@react-native-community/async-storage';
import {RouteProp} from '@react-navigation/native';
import {StackNavigationOptions} from '@react-navigation/stack';
-import React from 'react';
+import React, {useState} from 'react';
import {
CaptionScreen,
CategorySelection,
@@ -12,6 +13,7 @@ import {
MomentUploadPromptScreen,
NotificationsScreen,
ProfileScreen,
+ RequestContactsAccess,
SearchScreen,
SocialMediaTaggs,
} from '../../screens';
@@ -42,6 +44,14 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
const isSearchTab = screenType === ScreenType.Search;
const isNotificationsTab = screenType === ScreenType.Notifications;
+ AsyncStorage.getItem('respondedToAccessContacts').then((value) =>
+ setRespondedToAccessContacts(value ? value : 'false'),
+ );
+
+ const [respondedToAccessContacts, setRespondedToAccessContacts] = useState(
+ 'false',
+ );
+
const initialRouteName = (() => {
switch (screenType) {
case ScreenType.Profile:
@@ -90,13 +100,20 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
screenType,
}}
/>
- {isSearchTab && (
- <MainStack.Screen
- name="Search"
- component={SearchScreen}
- initialParams={{screenType}}
- />
- )}
+ {isSearchTab &&
+ (respondedToAccessContacts && respondedToAccessContacts === 'true' ? (
+ <MainStack.Screen
+ name="Search"
+ component={SearchScreen}
+ initialParams={{screenType}}
+ />
+ ) : (
+ <MainStack.Screen
+ name="Search"
+ component={RequestContactsAccess}
+ initialParams={{screenType}}
+ />
+ ))}
{isNotificationsTab && (
<MainStack.Screen
name="Notifications"
@@ -180,9 +197,6 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
<MainStack.Screen
name="FriendsListScreen"
component={FriendsListScreen}
- options={{
- ...modalStyle,
- }}
initialParams={{screenType}}
/>
<MainStack.Screen
diff --git a/src/screens/main/NotificationsScreen.tsx b/src/screens/main/NotificationsScreen.tsx
index d9952aa8..f35bb22c 100644
--- a/src/screens/main/NotificationsScreen.tsx
+++ b/src/screens/main/NotificationsScreen.tsx
@@ -35,6 +35,9 @@ const NotificationsScreen: React.FC = () => {
const {notifications} = useSelector(
(state: RootState) => state.notifications,
);
+
+ const {user: loggedInUser} = useSelector((state: RootState) => state.user);
+
const [sectionedNotifications, setSectionedNotifications] = useState<
{title: 'Today' | 'Yesterday' | 'This Week'; data: NotificationType[]}[]
>([]);
@@ -130,7 +133,7 @@ const NotificationsScreen: React.FC = () => {
<Notification
item={item}
screenType={ScreenType.Notifications}
- moments={item.notification_type === 'CMT' ? loggedInUserMoments : []}
+ loggedInUser={loggedInUser}
/>
);
diff --git a/src/screens/onboarding/CategorySelection.tsx b/src/screens/onboarding/CategorySelection.tsx
index a3acbbb7..9d5fbe4d 100644
--- a/src/screens/onboarding/CategorySelection.tsx
+++ b/src/screens/onboarding/CategorySelection.tsx
@@ -17,7 +17,7 @@ import {Background, MomentCategory} from '../../components';
import {MOMENT_CATEGORIES} from '../../constants';
import {ERROR_SOMETHING_WENT_WRONG} from '../../constants/strings';
import {OnboardingStackParams} from '../../routes';
-import {fcmService, postMomentCategories} from '../../services';
+import {postMomentCategories} from '../../services';
import {
updateIsOnboardedUser,
updateMomentCategories,
@@ -169,7 +169,6 @@ const CategorySelection: React.FC<CategorySelectionProps> = ({
const token = await getTokenOrLogout(dispatch);
await postMomentCategories(selectedCategories, token);
userLogin(dispatch, {userId: userId, username: username});
- fcmService.sendFcmTokenToServer();
} else {
dispatch(
updateMomentCategories(
@@ -309,7 +308,7 @@ const styles = StyleSheet.create({
height: 40,
borderRadius: 5,
borderWidth: 1,
- borderColor: '#8F01FF',
+ borderColor: 'white',
marginBottom: '25%',
},
finalActionLabel: {
diff --git a/src/screens/onboarding/ProfileOnboarding.tsx b/src/screens/onboarding/ProfileOnboarding.tsx
index 127cd9cd..26ad93a7 100644
--- a/src/screens/onboarding/ProfileOnboarding.tsx
+++ b/src/screens/onboarding/ProfileOnboarding.tsx
@@ -26,6 +26,7 @@ import {
bioRegex,
genderRegex,
CLASS_YEAR_LIST,
+ TAGG_PURPLE,
} from '../../constants';
import AsyncStorage from '@react-native-community/async-storage';
import {BackgroundGradientType} from '../../types';
@@ -549,7 +550,7 @@ const styles = StyleSheet.create({
borderRadius: 55,
},
submitBtn: {
- backgroundColor: '#8F01FF',
+ backgroundColor: TAGG_PURPLE,
justifyContent: 'center',
alignItems: 'center',
width: SCREEN_WIDTH / 2.5,
diff --git a/src/screens/profile/FriendsListScreen.tsx b/src/screens/profile/FriendsListScreen.tsx
index f7192d10..26d19e60 100644
--- a/src/screens/profile/FriendsListScreen.tsx
+++ b/src/screens/profile/FriendsListScreen.tsx
@@ -1,14 +1,18 @@
-import React, {useState} from 'react';
-import {RouteProp} from '@react-navigation/native';
-import {TabsGradient, Friends, CenteredView} from '../../components';
-import {ScrollView} from 'react-native-gesture-handler';
-import {SCREEN_HEIGHT} from '../../utils';
-import {StyleSheet, View} from 'react-native';
+import React from 'react';
+import {RouteProp, useNavigation} from '@react-navigation/native';
+import {TabsGradient, Friends} from '../../components';
+import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import {
+ SafeAreaView,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
import {ProfileStackParams} from '../../routes';
-import {ProfilePreviewType} from '../../types';
-import {EMPTY_PROFILE_PREVIEW_LIST} from '../../store/initialStates';
import {useSelector} from 'react-redux';
import {RootState} from '../../store/rootReducer';
+import BackIcon from '../../assets/icons/back-arrow.svg';
type FriendsListScreenRouteProp = RouteProp<
ProfileStackParams,
@@ -20,51 +24,66 @@ interface FriendsListScreenProps {
const FriendsListScreen: React.FC<FriendsListScreenProps> = ({route}) => {
const {userXId, screenType} = route.params;
+ const navigation = useNavigation();
const {friends} = userXId
? useSelector((state: RootState) => state.userX[screenType][userXId])
: useSelector((state: RootState) => state.friends);
return (
- <CenteredView>
- <View style={styles.modalView}>
- <ScrollView
- keyboardShouldPersistTaps={'always'}
- stickyHeaderIndices={[4]}
- contentContainerStyle={styles.contentContainer}
- showsVerticalScrollIndicator={false}>
- <Friends result={friends} screenType={screenType} />
- </ScrollView>
- <TabsGradient />
- </View>
- </CenteredView>
+ <View style={styles.background}>
+ <SafeAreaView>
+ <View style={styles.header}>
+ <TouchableOpacity
+ style={styles.headerButton}
+ onPress={() => {
+ navigation.pop();
+ }}>
+ <BackIcon height={'100%'} width={'100%'} color={'white'} />
+ </TouchableOpacity>
+ <Text style={styles.headerText}>Friends</Text>
+ </View>
+ <View style={styles.body}>
+ <Friends result={friends} screenType={screenType} userId={userXId} />
+ </View>
+ </SafeAreaView>
+ <TabsGradient />
+ </View>
);
};
const styles = StyleSheet.create({
- contentContainer: {
- paddingBottom: SCREEN_HEIGHT / 15,
- paddingHorizontal: 15,
- marginTop: '5%',
+ background: {
+ backgroundColor: 'white',
+ height: '100%',
},
- modalView: {
- width: '85%',
- height: '70%',
- backgroundColor: '#fff',
- shadowColor: '#000',
- shadowOpacity: 30,
- shadowOffset: {width: 0, height: 2},
- shadowRadius: 5,
- borderRadius: 8,
- paddingBottom: 15,
- paddingHorizontal: 20,
- justifyContent: 'space-between',
- },
- modalScrollViewContent: {
+ header: {
+ flexDirection: 'column',
justifyContent: 'center',
+ height: SCREEN_HEIGHT * 0.05,
+ padding: '3%',
+ paddingBottom: 0,
+ marginTop: '1%',
+ },
+ headerText: {
+ position: 'absolute',
+ alignSelf: 'center',
+ fontSize: normalize(18),
+ fontWeight: '700',
+ lineHeight: normalize(21.48),
+ letterSpacing: normalize(1.3),
+ },
+ headerButton: {
+ width: '5%',
+ aspectRatio: 1,
+ padding: 0,
+ marginLeft: '5%',
+ alignSelf: 'flex-start',
+ marginBottom: '1%',
},
- modalScrollView: {
- marginBottom: 10,
+ body: {
+ width: SCREEN_WIDTH,
+ height: SCREEN_HEIGHT * 0.8,
},
});
diff --git a/src/screens/profile/MomentCommentsScreen.tsx b/src/screens/profile/MomentCommentsScreen.tsx
index 5c3b8579..ec193db5 100644
--- a/src/screens/profile/MomentCommentsScreen.tsx
+++ b/src/screens/profile/MomentCommentsScreen.tsx
@@ -9,7 +9,7 @@ import CommentsContainer from '../../components/comments/CommentsContainer';
import {ADD_COMMENT_TEXT} from '../../constants/strings';
import {MainStackParams} from '../../routes/main';
import {CommentType} from '../../types';
-import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
/**
* Comments Screen for an image uploaded
@@ -92,8 +92,10 @@ const styles = StyleSheet.create({
headerText: {
position: 'absolute',
alignSelf: 'center',
- fontSize: 20.5,
- fontWeight: '600',
+ fontSize: normalize(18),
+ fontWeight: '700',
+ lineHeight: normalize(21.48),
+ letterSpacing: normalize(1.3),
},
headerButton: {
width: '5%',
diff --git a/src/screens/profile/ProfileScreen.tsx b/src/screens/profile/ProfileScreen.tsx
index 0ea96cd2..9cdba555 100644
--- a/src/screens/profile/ProfileScreen.tsx
+++ b/src/screens/profile/ProfileScreen.tsx
@@ -29,9 +29,9 @@ const ProfileScreen: React.FC<ProfileOnboardingProps> = ({route}) => {
* This is a double safety check to avoid app crash.
* Checks if the required userXId is present in the store, if not userXId is set to dummy id
*/
- if (userXId && !(userXId in useStore().getState().userX[screenType])) {
- userXId = DUMMY_USERID;
- }
+ // if (userXId && !(userXId in useStore().getState().userX[screenType])) {
+ // userXId = DUMMY_USERID;
+ // }
/**
* Code under useFocusEffect gets executed every time the screen comes under focus / is being viewed by the user.
diff --git a/src/screens/search/RequestContactsAccess.tsx b/src/screens/search/RequestContactsAccess.tsx
new file mode 100644
index 00000000..de023464
--- /dev/null
+++ b/src/screens/search/RequestContactsAccess.tsx
@@ -0,0 +1,186 @@
+import * as React from 'react';
+import {
+ StyleSheet,
+ View,
+ Text,
+ Image,
+ TouchableOpacity,
+ StatusBar,
+} from 'react-native';
+import {BACKGROUND_GRADIENT_MAP} from '../../constants';
+import {isIPhoneX, normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import {BackgroundGradientType} from '../../types';
+import {useNavigation} from '@react-navigation/native';
+import {SafeAreaView} from 'react-native-safe-area-context';
+import Animated from 'react-native-reanimated';
+import LinearGradient from 'react-native-linear-gradient';
+import {checkPermission, requestPermission} from 'react-native-contacts';
+import AsyncStorage from '@react-native-community/async-storage';
+
+const RequestContactsAccess: React.FC = () => {
+ const navigation = useNavigation();
+
+ const handleAllowAccess = async () => {
+ checkPermission().then((permission) => {
+ if (permission === 'undefined') {
+ requestPermission().then((response) => {
+ if (response === 'authorized' || response === 'denied') {
+ navigation.navigate('Search');
+ }
+ });
+ }
+ });
+ await AsyncStorage.setItem('respondedToAccessContacts', 'true');
+ };
+
+ const handleDontAllowAccess = async () => {
+ await AsyncStorage.setItem('respondedToAccessContacts', 'true');
+ navigation.navigate('Search');
+ };
+
+ return (
+ <LinearGradient
+ colors={BACKGROUND_GRADIENT_MAP[BackgroundGradientType.Light]}
+ useAngle={true}
+ angle={154.72}
+ angleCenter={{x: 0.5, y: 0.5}}
+ style={{flex: 1}}>
+ <SafeAreaView>
+ <View style={{height: SCREEN_HEIGHT}}>
+ <Animated.ScrollView
+ showsHorizontalScrollIndicator={false}
+ showsVerticalScrollIndicator={false}
+ scrollEnabled={isIPhoneX() ? false : true}>
+ <StatusBar barStyle="light-content" translucent={false} />
+ <View style={styles.mainContainer}>
+ <Image
+ source={require('../../assets/icons/findFriends/find-friend-icon.png')}
+ style={styles.image}
+ />
+ <Text style={styles.title}>FIND FRIENDS!</Text>
+ <Text style={styles.subtext}>
+ This is so you can find your friends already on here! Isn’t a
+ party better when your favorite people are there?
+ </Text>
+ <View style={styles.bulletPointView}>
+ <Image
+ source={require('../../assets/icons/findFriends/lock-icon.png')}
+ style={styles.icon}
+ />
+ <Text style={styles.bulletPointText}>Always Stays Private</Text>
+ </View>
+ <View style={styles.bulletPointView}>
+ <Image
+ source={require('../../assets/icons/findFriends/phone-cross-icon.png')}
+ style={styles.icon}
+ />
+ <Text style={styles.bulletPointText}>
+ We wouldn’t dare send any messages
+ </Text>
+ </View>
+ <TouchableOpacity
+ onPress={handleAllowAccess}
+ style={styles.allowButton}>
+ <Text style={styles.allowButtonLabel}>Allow Contacts</Text>
+ </TouchableOpacity>
+ <TouchableOpacity
+ accessibilityLabel="Don't allow button"
+ style={styles.dontAllowButton}
+ onPress={handleDontAllowAccess}>
+ <Text style={styles.dontAllowButtonText}>Don’t Allow</Text>
+ </TouchableOpacity>
+ </View>
+ </Animated.ScrollView>
+ </View>
+ </SafeAreaView>
+ </LinearGradient>
+ );
+};
+
+const styles = StyleSheet.create({
+ mainContainer: {
+ flex: 1,
+ flexDirection: 'column',
+ alignContent: 'center',
+ width: SCREEN_WIDTH,
+ height: SCREEN_HEIGHT,
+ marginBottom: '15%',
+ },
+ image: {
+ marginBottom: '2%',
+ width: SCREEN_WIDTH,
+ height: SCREEN_WIDTH * 0.49,
+ },
+ title: {
+ color: '#fff',
+ alignSelf: 'center',
+ fontSize: normalize(28),
+ lineHeight: normalize(35),
+ fontWeight: '600',
+ textAlign: 'center',
+ marginBottom: '2%',
+ },
+ subtext: {
+ color: '#fff',
+ alignSelf: 'center',
+ fontSize: normalize(16),
+ lineHeight: normalize(25),
+ fontWeight: '600',
+ textAlign: 'center',
+ width: '83%',
+ height: '15%',
+ },
+ bulletPointView: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignSelf: 'center',
+ width: SCREEN_WIDTH * 0.55,
+ marginBottom: '7%',
+ },
+ icon: {
+ margin: '1%',
+ width: normalize(38),
+ height: normalize(38),
+ alignSelf: 'flex-start',
+ },
+ bulletPointText: {
+ color: '#fff',
+ fontSize: normalize(15),
+ fontWeight: '500',
+ lineHeight: normalize(20),
+ alignSelf: 'center',
+ width: '75%',
+ textAlign: 'center',
+ },
+ allowButton: {
+ backgroundColor: '#fff',
+ justifyContent: 'center',
+ alignItems: 'center',
+ alignSelf: 'center',
+ width: '41.5%',
+ height: '6%',
+ borderRadius: 5,
+ borderWidth: 1,
+ borderColor: '#fff',
+ marginTop: '8%',
+ marginBottom: '3%',
+ },
+ allowButtonLabel: {
+ fontSize: normalize(17),
+ fontWeight: '600',
+ lineHeight: normalize(20.29),
+ color: '#3C4461',
+ },
+ dontAllowButton: {
+ alignSelf: 'center',
+ borderBottomWidth: 1,
+ borderBottomColor: 'white',
+ },
+ dontAllowButtonText: {
+ fontSize: normalize(15),
+ fontWeight: '500',
+ lineHeight: normalize(20),
+ color: '#fff',
+ },
+});
+export default RequestContactsAccess;
diff --git a/src/screens/search/index.ts b/src/screens/search/index.ts
index b6680aa4..d5eb9c3e 100644
--- a/src/screens/search/index.ts
+++ b/src/screens/search/index.ts
@@ -1 +1,2 @@
export {default as SearchScreen} from './SearchScreen';
+export {default as RequestContactsAccess} from './RequestContactsAccess';
diff --git a/src/services/UserFriendsService.ts b/src/services/UserFriendsService.ts
index f2e15824..99d86d0b 100644
--- a/src/services/UserFriendsService.ts
+++ b/src/services/UserFriendsService.ts
@@ -147,3 +147,32 @@ export const acceptFriendRequestService = async (
return false;
}
};
+
+export const unfriendService = async (
+ user_id: string,
+ token: string | null,
+) => {
+ try {
+ const response = await fetch(FRIENDS_ENDPOINT + `${user_id}/`, {
+ method: 'DELETE',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
+ body: JSON.stringify({
+ reason: 'unfriended',
+ }),
+ });
+ const status = response.status;
+ if (Math.floor(status / 100) === 2) {
+ return true;
+ } else {
+ console.log(await response.json());
+ Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH);
+ return false;
+ }
+ } catch (error) {
+ console.log(error);
+ Alert.alert(ERROR_SOMETHING_WENT_WRONG_REFRESH);
+ return false;
+ }
+};
diff --git a/src/store/actions/userFriends.ts b/src/store/actions/userFriends.ts
index 18ad247c..763f2575 100644
--- a/src/store/actions/userFriends.ts
+++ b/src/store/actions/userFriends.ts
@@ -11,6 +11,7 @@ import {
declineFriendRequestService,
friendOrUnfriendUser,
loadFriends,
+ unfriendService,
} from '../../services';
import {Action, ThunkAction} from '@reduxjs/toolkit';
import {
@@ -61,15 +62,17 @@ export const friendUnfriendUser = (
data = 'requested';
break;
case 'requested': // cancel request: update to no relationship
+ break;
case 'friends': // unfriend: update to no relationship
dispatch({
type: updateFriends.type,
payload: {
- friend,
+ data: friend,
isFriend: true,
},
});
data = 'no_record';
+ break;
}
dispatch({
type: userXFriendshipEdited.type,
@@ -85,6 +88,39 @@ export const friendUnfriendUser = (
}
};
+export const unfriendUser = (
+ friend: ProfilePreviewType, // userX's profile preview
+ screenType: ScreenType, //screentype from content
+): ThunkAction<Promise<void>, RootState, unknown, Action<string>> => async (
+ dispatch,
+) => {
+ try {
+ const token = await getTokenOrLogout(dispatch);
+ // Calls method to send post or delete request
+ const success = await unfriendService(friend.id, token);
+ if (success) {
+ let data = 'no_record';
+ await dispatch({
+ type: updateFriends.type,
+ payload: {
+ data: friend,
+ isFriend: true,
+ },
+ });
+ await dispatch({
+ type: userXFriendshipEdited.type,
+ payload: {
+ userId: friend.id,
+ screenType,
+ data,
+ },
+ });
+ }
+ } catch (error) {
+ console.log(error);
+ }
+};
+
export const acceptFriendRequest = (
requester: ProfilePreviewType,
): ThunkAction<Promise<void>, RootState, unknown, Action<string>> => async (
diff --git a/src/store/reducers/userFriendsReducer.ts b/src/store/reducers/userFriendsReducer.ts
index 2041a181..92402db1 100644
--- a/src/store/reducers/userFriendsReducer.ts
+++ b/src/store/reducers/userFriendsReducer.ts
@@ -11,8 +11,14 @@ const userFriendsSlice = createSlice({
updateFriends: (state, action) => {
const {isFriend, data} = action.payload;
- if (!isFriend) state.friends.push(data);
- else {
+ if (!isFriend) {
+ const friendInList: boolean = state.friends.some(
+ (friend) => friend.username === data.username,
+ );
+ if (!friendInList) {
+ state.friends.push(data);
+ }
+ } else {
state.friends = state.friends.filter(
(friend) => friend.username !== data.username,
);
diff --git a/src/types/types.ts b/src/types/types.ts
index f1ba12f4..ab995292 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -87,7 +87,6 @@ export interface MomentType {
moment_url: string;
thumbnail_url: string;
}
-
export interface CommentBaseType {
comment_id: string;
comment: string;
@@ -165,7 +164,7 @@ export enum CategorySelectionScreenType {
export enum BackgroundGradientType {
Light,
Dark,
- Notification
+ Notification,
}
/**
@@ -177,11 +176,28 @@ export type TaggPopupType = {
next?: TaggPopupType;
};
+export interface MomentWithUserType extends MomentType {
+ user: ProfilePreviewType;
+}
+
+export interface CommentNotificationType {
+ comment_id: string;
+ notification_data: MomentWithUserType;
+}
+
+export interface ThreadNotificationType extends CommentNotificationType {
+ parent_comment: string;
+}
+
export type NotificationType = {
actor: ProfilePreviewType;
verbage: string;
notification_type: TypeOfNotification;
- notification_object: CommentType | CommentThreadType | MomentType | undefined;
+ notification_object:
+ | CommentNotificationType
+ | ThreadNotificationType
+ | MomentType
+ | undefined;
timestamp: string;
unread: boolean;
};
@@ -196,7 +212,7 @@ export type TypeOfNotification =
| 'FRD_ACPT'
// notification_object is undefined
| 'FRD_DEC'
- // notification_object is CommentType || CommentThreadType
+ // notification_object is CommentNotificationType || ThreadNotificationType
| 'CMT'
// notification_object is MomentType
| 'MOM_3+'
diff --git a/src/utils/friends.ts b/src/utils/friends.ts
new file mode 100644
index 00000000..ba15e087
--- /dev/null
+++ b/src/utils/friends.ts
@@ -0,0 +1,54 @@
+// Handles click on friend/requested/unfriend button
+
+import {RootState} from '../store/rootReducer';
+import {ProfilePreviewType, ProfileType, ScreenType, UserType} from '../types';
+import {AppDispatch} from '../store/configureStore';
+import {
+ friendUnfriendUser,
+ unfriendUser,
+ updateUserXFriends,
+ updateUserXProfileAllScreens,
+} from '../store/actions';
+import {getUserAsProfilePreviewType} from './users';
+
+/*
+ * When user logged in clicks on the friend button:
+ A request is sent.
+ Which means you have to update the status of their friendshpi to requested
+ When the status is changed to requested the button should change to requested.
+ When the button is changed to requested and thr user clicks on it,
+ a request much go to the backend to delete that request
+ When that succeeds, their friendship must be updated to no-record again;
+ When the button is changed to no_record, the add friends button should be displayed again
+ */
+export const handleFriendUnfriend = async (
+ screenType: ScreenType,
+ user: UserType,
+ profile: ProfileType,
+ dispatch: AppDispatch,
+ state: RootState,
+ loggedInUser: UserType,
+) => {
+ const {friendship_status} = profile;
+ await dispatch(
+ friendUnfriendUser(
+ loggedInUser,
+ getUserAsProfilePreviewType(user, profile),
+ friendship_status,
+ screenType,
+ ),
+ );
+ await dispatch(updateUserXFriends(user.userId, state));
+ dispatch(updateUserXProfileAllScreens(user.userId, state));
+};
+
+export const handleUnfriend = async (
+ screenType: ScreenType,
+ friend: ProfilePreviewType,
+ dispatch: AppDispatch,
+ state: RootState,
+) => {
+ await dispatch(unfriendUser(friend, screenType));
+ await dispatch(updateUserXFriends(friend.id, state));
+ dispatch(updateUserXProfileAllScreens(friend.id, state));
+};
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 629a0091..82c94100 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -2,3 +2,4 @@ export * from './layouts';
export * from './moments';
export * from './common';
export * from './users';
+export * from './friends';