aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/common/BadgeDetailView.tsx303
-rw-r--r--src/components/messages/ChannelPreview.tsx5
-rw-r--r--src/components/profile/Content.tsx8
-rw-r--r--src/components/profile/FriendsCount.tsx1
-rw-r--r--src/components/profile/ProfileHeader.tsx28
-rw-r--r--src/components/profile/UniversityIcon.tsx28
-rw-r--r--src/constants/api.ts2
-rw-r--r--src/routes/main/MainStackScreen.tsx47
-rw-r--r--src/screens/badge/BadgeSelection.tsx12
-rw-r--r--src/screens/suggestedPeople/SuggestedPeopleScreen.tsx9
-rw-r--r--src/services/SuggestedPeopleService.ts32
11 files changed, 428 insertions, 47 deletions
diff --git a/src/components/common/BadgeDetailView.tsx b/src/components/common/BadgeDetailView.tsx
new file mode 100644
index 00000000..437b1914
--- /dev/null
+++ b/src/components/common/BadgeDetailView.tsx
@@ -0,0 +1,303 @@
+import {useNavigation} from '@react-navigation/core';
+import React, {useEffect, useState} from 'react';
+import {
+ ActivityIndicator,
+ FlatList,
+ Image,
+ Modal,
+ StyleSheet,
+ Text,
+ View,
+} from 'react-native';
+import {TouchableOpacity} from 'react-native-gesture-handler';
+import LinearGradient from 'react-native-linear-gradient';
+import {useSelector} from 'react-redux';
+import CloseIcon from '../../assets/ionicons/close-outline.svg';
+import {BADGE_GRADIENT_FIRST} from '../../constants';
+import {BADGE_DATA} from '../../constants/badges';
+import {getSuggestedPeopleProfile, removeBadgesService} from '../../services';
+import {RootState} from '../../store/rootreducer';
+import {UniversityBadge, UniversityType} from '../../types';
+import {getUniversityBadge, normalize} from '../../utils';
+
+interface BadgeDetailModalProps {
+ isEditable: boolean;
+ setBadgeViewVisible: Function;
+ name?: string;
+}
+
+const BadgeDetailView: React.FC<BadgeDetailModalProps> = ({
+ name,
+ isEditable = true,
+ setBadgeViewVisible,
+}) => {
+ const {
+ user: {userId = ''},
+ } = useSelector((state: RootState) => state.user);
+ const navigation = useNavigation();
+ const [selectedBadges, setSelectedBadges] = useState<UniversityBadge[]>([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [selectedBadgesWithImage, setSelectedBadgesWithImage] = useState<any[]>(
+ [],
+ );
+
+ const fetchBadges = async () => {
+ if (userId) {
+ const response = await getSuggestedPeopleProfile(userId);
+ if (response) {
+ const data = response.badges;
+ let extractedBadgeNames: UniversityBadge[] = [];
+ data.forEach((badge) => {
+ extractedBadgeNames.push(badge);
+ });
+ setSelectedBadges(extractedBadgeNames);
+ }
+ }
+ };
+
+ useEffect(() => {
+ setIsLoading(true);
+ fetchBadges();
+ }, []);
+
+ useEffect(() => {
+ let badgesWithImage = [];
+ selectedBadges.forEach((e) => {
+ const uniData = BADGE_DATA[e.university];
+ const categoryData = uniData.filter((u) => {
+ return u.title === e.category;
+ });
+ const badgeData = categoryData[0].data.filter((c) => {
+ return c.badgeName === e.name;
+ });
+ badgeData.forEach((c) => {
+ const obj = {...e, badgeImage: c.badgeImage};
+ badgesWithImage.push(obj);
+ });
+ });
+ setTimeout(() => {
+ setSelectedBadgesWithImage(badgesWithImage);
+ setIsLoading(false);
+ }, 250);
+ }, [selectedBadges]);
+
+ const removeBadgeCell = async (badge: string) => {
+ setIsLoading(true);
+ await removeBadgesService([badge], userId);
+ fetchBadges();
+ };
+
+ const badgeEditCell = ({item: {id, name, badgeImage}}) => {
+ return (
+ <TouchableOpacity
+ style={styles.badgeCellContainerStyles}
+ onPress={() => {
+ setBadgeViewVisible(false);
+ navigation.navigate('MutualBadgeHolders', {
+ badge_id: id,
+ badge_title: name,
+ badge_img: badgeImage,
+ });
+ }}>
+ <View
+ style={
+ isEditable
+ ? styles.badgeCellImageContainerStyles
+ : styles.badgeCellImageNoEditContainerStyles
+ }>
+ <LinearGradient
+ colors={BADGE_GRADIENT_FIRST}
+ useAngle={true}
+ style={styles.badgeCellImageStyles}
+ angle={136.69}>
+ <Image
+ resizeMode="cover"
+ style={styles.badgeImageStyles}
+ source={badgeImage}
+ />
+ </LinearGradient>
+ {isEditable && (
+ <TouchableOpacity
+ onPress={() => {
+ removeBadgeCell(name);
+ }}>
+ <CloseIcon height={25} width={25} color="gray" />
+ </TouchableOpacity>
+ )}
+ </View>
+ <View style={styles.userNameContainerStyles}>
+ <Text style={styles.badgeCellTextStyles}>{name}</Text>
+ </View>
+ </TouchableOpacity>
+ );
+ };
+
+ const addButton = () => {
+ return (
+ <TouchableOpacity
+ onPress={() => {
+ setBadgeViewVisible(false);
+ navigation.navigate('BadgeSelection', {editing: true});
+ }}
+ style={styles.addButtonStyles}>
+ <Text style={styles.addButtonTextStyles}>Add Badges</Text>
+ </TouchableOpacity>
+ );
+ };
+
+ const modalHeader = () => {
+ const heading = isEditable ? 'Edit your badges!' : name;
+ const subheading = isEditable
+ ? 'Add or delete your badges'
+ : 'View badges to discover groups!';
+ return (
+ <View>
+ <Text style={styles.modalHeadingStyles}>{heading}</Text>
+ <Text style={styles.modalSubheadingStyles}>{subheading}</Text>
+ </View>
+ );
+ };
+
+ const _modalContent = () => {
+ return (
+ <View style={styles.viewWrapper}>
+ <View style={styles.modalView}>
+ <View style={styles.modalUpperContainerStyles}>
+ <TouchableOpacity
+ style={styles.crossButtonStyles}
+ onPress={() => setBadgeViewVisible(false)}>
+ <CloseIcon height={25} width={25} color="gray" />
+ </TouchableOpacity>
+ <View style={styles.modalImageContainerStyles}>
+ <Image
+ style={styles.modalImageStyles}
+ source={getUniversityBadge(UniversityType.Brown, 'Crest')}
+ />
+ </View>
+ </View>
+ {modalHeader()}
+ {!isLoading && (
+ <View>
+ <FlatList
+ contentContainerStyle={styles.modalListStyles}
+ scrollEnabled={false}
+ data={selectedBadgesWithImage}
+ numColumns={3}
+ renderItem={badgeEditCell}
+ keyExtractor={(item) => item.id.toString()}
+ />
+ </View>
+ )}
+ {isLoading && _loaderView()}
+ {isEditable && addButton()}
+ </View>
+ </View>
+ );
+ };
+
+ const _loaderView = () => {
+ return (
+ <View style={styles.loaderStyles}>
+ <ActivityIndicator animating={isLoading} size="large" color="black" />
+ </View>
+ );
+ };
+
+ return (
+ <Modal
+ animationType="fade"
+ transparent
+ visible={true}
+ presentationStyle="overFullScreen">
+ {_modalContent()}
+ </Modal>
+ );
+};
+
+const styles = StyleSheet.create({
+ badgeCellContainerStyles: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ paddingVertical: 10,
+ },
+ badgeCellImageContainerStyles: {
+ flexDirection: 'row',
+ marginLeft: 25,
+ },
+ badgeImageStyles: {
+ width: '50%',
+ height: '50%',
+ alignSelf: 'center',
+ },
+ badgeCellImageNoEditContainerStyles: {
+ flexDirection: 'row',
+ marginHorizontal: 25,
+ },
+ badgeCellImageStyles: {
+ borderRadius: 50,
+ width: 70,
+ height: 70,
+ justifyContent: 'center',
+ },
+ badgeCellTextStyles: {
+ fontWeight: '600',
+ fontSize: normalize(12),
+ lineHeight: normalize(16),
+ textAlign: 'center',
+ },
+ userNameContainerStyles: {marginTop: 10},
+ crossButtonStyles: {marginTop: 10, marginLeft: 10},
+ addButtonStyles: {
+ height: 40,
+ borderRadius: 5,
+ marginBottom: 45,
+ width: '40%',
+ marginTop: 14,
+ alignItems: 'center',
+ justifyContent: 'center',
+ alignSelf: 'center',
+ backgroundColor: '#698DD3',
+ },
+ addButtonTextStyles: {color: 'white'},
+ modalHeadingStyles: {
+ fontWeight: '600',
+ fontSize: normalize(17),
+ lineHeight: normalize(20.29),
+ textAlign: 'center',
+ },
+ loaderStyles: {justifyContent: 'center', marginVertical: 20},
+ modalSubheadingStyles: {
+ fontWeight: '600',
+ fontSize: normalize(11),
+ lineHeight: normalize(15),
+ textAlign: 'center',
+ color: '#828282',
+ },
+ modalUpperContainerStyles: {
+ flexDirection: 'row',
+ alignItems: 'flex-start',
+ },
+ modalImageContainerStyles: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ modalImageStyles: {left: -15, top: -30},
+ modalListStyles: {
+ alignSelf: 'center',
+ marginVertical: 35,
+ },
+ viewWrapper: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: 'rgba(0, 0, 0, 0.2)',
+ },
+ modalView: {
+ width: '95%',
+ backgroundColor: '#fff',
+ borderRadius: 7,
+ },
+});
+
+export default BadgeDetailView;
diff --git a/src/components/messages/ChannelPreview.tsx b/src/components/messages/ChannelPreview.tsx
index 87b94179..3d31d42a 100644
--- a/src/components/messages/ChannelPreview.tsx
+++ b/src/components/messages/ChannelPreview.tsx
@@ -46,6 +46,11 @@ const ChannelPreview: React.FC<
? require('../../assets/images/unmute.png')
: require('../../assets/images/mute.png');
+ // Hide channel if no message was exchanged
+ if (channel.state.messages.length === 0) {
+ return null;
+ }
+
return (
<Swipeable
overshootLeft={false}
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx
index c70d6df5..8298dc9a 100644
--- a/src/components/profile/Content.tsx
+++ b/src/components/profile/Content.tsx
@@ -145,7 +145,13 @@ const Content: React.FC<ContentProps> = ({userXId, screenType}) => {
<Cover {...{userXId, screenType}} />
<ProfileCutout />
<ProfileHeader
- {...{userXId, screenType, handleBlockUnblock, isBlocked}}
+ {...{
+ userXId,
+ screenType,
+ handleBlockUnblock,
+ isBlocked,
+ isPrivate: !canViewProfile(state, userXId, screenType),
+ }}
/>
<ProfileBody
{...{
diff --git a/src/components/profile/FriendsCount.tsx b/src/components/profile/FriendsCount.tsx
index 4790743b..8252266e 100644
--- a/src/components/profile/FriendsCount.tsx
+++ b/src/components/profile/FriendsCount.tsx
@@ -62,6 +62,7 @@ const styles = StyleSheet.create({
fontSize: normalize(14),
},
label: {
+ marginTop: 4,
fontWeight: '500',
fontSize: normalize(14),
},
diff --git a/src/components/profile/ProfileHeader.tsx b/src/components/profile/ProfileHeader.tsx
index 2c623c2b..494b33bd 100644
--- a/src/components/profile/ProfileHeader.tsx
+++ b/src/components/profile/ProfileHeader.tsx
@@ -1,10 +1,12 @@
import React, {useState} from 'react';
import {StyleSheet, Text, View} from 'react-native';
+import {TouchableOpacity} from 'react-native-gesture-handler';
import {useSelector} from 'react-redux';
import {PROFILE_CUTOUT_TOP_Y} from '../../constants';
import {RootState} from '../../store/rootreducer';
import {ScreenType} from '../../types';
import {normalize} from '../../utils';
+import BadgeDetailView from '../common/BadgeDetailView';
import Avatar from './Avatar';
import FriendsCount from './FriendsCount';
import ProfileMoreInfoDrawer from './ProfileMoreInfoDrawer';
@@ -15,6 +17,7 @@ type ProfileHeaderProps = {
screenType: ScreenType;
isBlocked: boolean;
handleBlockUnblock: () => void;
+ isPrivate?: boolean;
};
const ProfileHeader: React.FC<ProfileHeaderProps> = ({
@@ -22,6 +25,7 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({
screenType,
isBlocked,
handleBlockUnblock,
+ isPrivate,
}) => {
const {
profile: {name = '', university_class = 2021, university},
@@ -29,8 +33,14 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({
} = useSelector((state: RootState) =>
userXId ? state.userX[screenType][userXId] : state.user,
);
+
+ const {
+ user: {username = ''},
+ } = useSelector((state: RootState) => state.user);
const [drawerVisible, setDrawerVisible] = useState(false);
+ const [showBadgeView, setBadgeViewVisible] = useState(false);
const [firstName, lastName] = [...name.split(' ')];
+
return (
<View style={styles.container}>
<ProfileMoreInfoDrawer
@@ -59,7 +69,23 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({
)}
<View style={styles.friendsAndUniversity}>
<FriendsCount screenType={screenType} userXId={userXId} />
- <UniversityIcon {...{university, university_class}} />
+ <TouchableOpacity
+ onPress={() => {
+ if (!isPrivate) {
+ setBadgeViewVisible(true);
+ }
+ }}>
+ <UniversityIcon
+ {...{university, university_class, needsShadow: true}}
+ />
+ </TouchableOpacity>
+ {showBadgeView && (
+ <BadgeDetailView
+ isEditable={userXName === username}
+ name={name}
+ setBadgeViewVisible={setBadgeViewVisible}
+ />
+ )}
</View>
</View>
</View>
diff --git a/src/components/profile/UniversityIcon.tsx b/src/components/profile/UniversityIcon.tsx
index 4cb1abe6..cfe1366d 100644
--- a/src/components/profile/UniversityIcon.tsx
+++ b/src/components/profile/UniversityIcon.tsx
@@ -8,6 +8,7 @@ export interface UniversityIconProps extends ViewProps {
university: UniversityType;
university_class?: number;
imageStyle?: StyleProp<ImageStyle>;
+ needsShadow?: boolean;
}
/**
@@ -18,13 +19,16 @@ const UniversityIcon: React.FC<UniversityIconProps> = ({
university,
university_class,
imageStyle,
+ needsShadow = false,
}) => {
return (
<View style={[styles.container, style]}>
- <Image
- source={getUniversityBadge(university, 'Crest')}
- style={[styles.icon, imageStyle]}
- />
+ <View style={needsShadow && styles.shadowStyle}>
+ <Image
+ source={getUniversityBadge(university, 'Crest')}
+ style={[styles.icon, imageStyle]}
+ />
+ </View>
{university_class && (
<Text style={styles.univClass}>
{getUniversityClass(university_class)}
@@ -47,8 +51,20 @@ const styles = StyleSheet.create({
fontWeight: '500',
},
icon: {
- width: normalize(17),
- height: normalize(19),
+ width: normalize(12),
+ height: normalize(13),
+ },
+ shadowStyle: {
+ padding: 5,
+ borderRadius: 30,
+ shadowOffset: {
+ width: 1,
+ height: 1,
+ },
+ shadowOpacity: 1,
+ shadowRadius: 3,
+ shadowColor: 'rgba(0, 0, 0, 0.3)',
+ backgroundColor: 'white',
},
});
diff --git a/src/constants/api.ts b/src/constants/api.ts
index dd934f0e..45b6e8ae 100644
--- a/src/constants/api.ts
+++ b/src/constants/api.ts
@@ -58,6 +58,8 @@ export const SP_MUTUAL_BADGE_HOLDERS_ENDPOINT: string =
export const ADD_BADGES_ENDPOINT: string = SP_USERS_ENDPOINT + 'add_badges/';
export const UPDATE_BADGES_ENDPOINT: string =
SP_USERS_ENDPOINT + 'update_badges/';
+ export const REMOVE_BADGES_ENDPOINT: string =
+ SP_USERS_ENDPOINT + 'remove_badges/';
// Register as FCM device
export const FCM_ENDPOINT: string = API_URL + 'fcm/';
diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx
index 37867151..f5100e58 100644
--- a/src/routes/main/MainStackScreen.tsx
+++ b/src/routes/main/MainStackScreen.tsx
@@ -1,7 +1,6 @@
-import AsyncStorage from '@react-native-community/async-storage';
import {RouteProp} from '@react-navigation/native';
import {StackNavigationOptions} from '@react-navigation/stack';
-import React, {useEffect, useState} from 'react';
+import React from 'react';
import {StyleSheet, Text} from 'react-native';
import {normalize} from 'react-native-elements';
import BackIcon from '../../assets/icons/back-arrow.svg';
@@ -56,28 +55,9 @@ interface MainStackProps {
const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
const {screenType} = route.params;
- // const isProfileTab = screenType === ScreenType.Profile;
const isSearchTab = screenType === ScreenType.Search;
const isNotificationsTab = screenType === ScreenType.Notifications;
const isSuggestedPeopleTab = screenType === ScreenType.SuggestedPeople;
- const [respondedToAccessContacts, setRespondedToAccessContacts] = useState(
- 'true',
- );
-
- useEffect(() => {
- loadResponseToAccessContacts();
- }, []);
-
- const loadResponseToAccessContacts = () => {
- AsyncStorage.getItem('respondedToAccessContacts')
- .then((value) => {
- setRespondedToAccessContacts(value ? value : 'false');
- })
- .catch((error) => {
- console.log('Something went wrong', error);
- setRespondedToAccessContacts('true');
- });
- };
const initialRouteName = (() => {
switch (screenType) {
@@ -131,20 +111,13 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
...headerBarOptions('white', ''),
}}
/>
- {isSuggestedPeopleTab &&
- (respondedToAccessContacts && respondedToAccessContacts === 'true' ? (
- <MainStack.Screen
- name="SuggestedPeople"
- component={SuggestedPeopleScreen}
- initialParams={{screenType}}
- />
- ) : (
- <MainStack.Screen
- name="SuggestedPeople"
- component={RequestContactsAccess}
- initialParams={{screenType}}
- />
- ))}
+ {isSuggestedPeopleTab && (
+ <MainStack.Screen
+ name="SuggestedPeople"
+ component={SuggestedPeopleScreen}
+ initialParams={{screenType}}
+ />
+ )}
{isNotificationsTab && (
<MainStack.Screen
name="Notifications"
@@ -271,6 +244,10 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
name="RequestContactsAccess"
component={RequestContactsAccess}
initialParams={{screenType}}
+ options={{
+ ...modalStyle,
+ gestureEnabled: false,
+ }}
/>
<MainStack.Screen
name="EditProfile"
diff --git a/src/screens/badge/BadgeSelection.tsx b/src/screens/badge/BadgeSelection.tsx
index 91617377..38a2b01c 100644
--- a/src/screens/badge/BadgeSelection.tsx
+++ b/src/screens/badge/BadgeSelection.tsx
@@ -66,10 +66,14 @@ const BadgeSelection: React.FC<BadgeSelectionProps> = ({route}) => {
style={styles.rightButtonContainer}
onPress={async () => {
if (editing) {
- updateBadgesService(selectedBadges, university);
- navigation.navigate('UpdateSPPicture', {
- editing: true,
- });
+ await updateBadgesService(selectedBadges, university);
+ if (navigation.canGoBack()) {
+ navigation.goBack();
+ } else {
+ navigation.navigate('UpdateSPPicture', {
+ editing: true,
+ });
+ }
} else {
if (selectedBadges.length !== 0) {
const success = await addBadgesService(
diff --git a/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx b/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx
index 388a1ba7..f1981111 100644
--- a/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx
+++ b/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx
@@ -1,3 +1,4 @@
+import AsyncStorage from '@react-native-community/async-storage';
import {useFocusEffect, useNavigation} from '@react-navigation/native';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {FlatList, RefreshControl, StatusBar, ViewToken} from 'react-native';
@@ -57,6 +58,14 @@ const SuggestedPeopleScreen: React.FC = () => {
const stausBarRef = useRef(hideStatusBar);
+ useEffect(() => {
+ AsyncStorage.getItem('respondedToAccessContacts').then((value) => {
+ if (value === null) {
+ navigation.navigate('RequestContactsAccess');
+ }
+ });
+ }, []);
+
// loads data and append it to users based on current page
useEffect(() => {
loadMore();
diff --git a/src/services/SuggestedPeopleService.ts b/src/services/SuggestedPeopleService.ts
index 617f3970..7f5b4b8c 100644
--- a/src/services/SuggestedPeopleService.ts
+++ b/src/services/SuggestedPeopleService.ts
@@ -7,6 +7,7 @@ import {
import {
ADD_BADGES_ENDPOINT,
EDIT_PROFILE_ENDPOINT,
+ REMOVE_BADGES_ENDPOINT,
SP_MUTUAL_BADGE_HOLDERS_ENDPOINT,
SP_UPDATE_PICTURE_ENDPOINT,
SP_USERS_ENDPOINT,
@@ -195,3 +196,34 @@ export const updateBadgesService = async (
Alert.alert(ERROR_UPLOAD_BADGES);
}
};
+
+export const removeBadgesService = async (
+ removableBadges: string[],
+ userId: string,
+) => {
+ try {
+ const token = await AsyncStorage.getItem('token');
+ const form = new FormData();
+ form.append('badges', JSON.stringify(removableBadges));
+ form.append('user', JSON.stringify(userId));
+ const response = await fetch(REMOVE_BADGES_ENDPOINT, {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ Authorization: 'Token ' + token,
+ },
+ body: form,
+ });
+ if (response.status === 400) {
+ Alert.alert(ERROR_BADGES_EXCEED_LIMIT);
+ return;
+ }
+ if (response.status === 200) {
+ Alert.alert(SUCCESS_BADGES_UPDATE);
+ return;
+ }
+ } catch (error) {
+ console.log(error);
+ Alert.alert(ERROR_UPLOAD_BADGES);
+ }
+};