aboutsummaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/comments/AddComment.tsx198
-rw-r--r--src/components/comments/CommentTile.tsx261
-rw-r--r--src/components/comments/CommentsContainer.tsx171
-rw-r--r--src/components/common/AcceptDeclineButtons.tsx8
-rw-r--r--src/components/common/GenericMoreInfoDrawer.tsx4
-rw-r--r--src/components/common/SocialLinkModal.tsx4
-rw-r--r--src/components/common/TaggDatePicker.tsx2
-rw-r--r--src/components/moments/Moment.tsx19
-rw-r--r--src/components/moments/MomentPostContent.tsx9
-rw-r--r--src/components/moments/MomentTile.tsx3
-rw-r--r--src/components/notifications/Notification.tsx114
-rw-r--r--src/components/onboarding/BirthDatePicker.tsx2
-rw-r--r--src/components/onboarding/TaggBigInput.tsx12
-rw-r--r--src/components/profile/Content.tsx91
-rw-r--r--src/components/profile/FriendsCount.tsx11
-rw-r--r--src/components/profile/ProfileBody.tsx35
-rw-r--r--src/components/profile/ProfileHeader.tsx44
-rw-r--r--src/components/profile/ProfileMoreInfoDrawer.tsx5
-rw-r--r--src/components/profile/ProfilePreview.tsx61
-rw-r--r--src/components/profile/ToggleButton.tsx8
-rw-r--r--src/components/profile/UniversityIcon.tsx12
-rw-r--r--src/components/search/Explore.tsx11
-rw-r--r--src/components/search/ExploreSection.tsx18
-rw-r--r--src/components/search/ExploreSectionUser.tsx24
-rw-r--r--src/components/search/RecentSearches.tsx4
-rw-r--r--src/components/taggs/Tagg.tsx3
-rw-r--r--src/components/taggs/TwitterTaggPost.tsx4
27 files changed, 804 insertions, 334 deletions
diff --git a/src/components/comments/AddComment.tsx b/src/components/comments/AddComment.tsx
index f8c0b6bc..56011f05 100644
--- a/src/components/comments/AddComment.tsx
+++ b/src/components/comments/AddComment.tsx
@@ -1,17 +1,20 @@
-import * as React from 'react';
+import React, {useEffect, useRef} from 'react';
import {
Image,
+ Keyboard,
KeyboardAvoidingView,
Platform,
StyleSheet,
View,
} from 'react-native';
-import AsyncStorage from '@react-native-community/async-storage';
-import {TaggBigInput} from '../onboarding';
-import {postMomentComment} from '../../services';
-import {logout} from '../../store/actions';
-import {useSelector, useDispatch} from 'react-redux';
+import {TextInput, TouchableOpacity} from 'react-native-gesture-handler';
+import {useDispatch, useSelector} from 'react-redux';
+import UpArrowIcon from '../../assets/icons/up_arrow.svg';
+import {TAGG_LIGHT_BLUE} from '../../constants';
+import {postComment} from '../../services';
+import {updateReplyPosted} from '../../store/actions';
import {RootState} from '../../store/rootreducer';
+import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
/**
* This file provides the add comment view for a user.
@@ -21,95 +24,154 @@ import {RootState} from '../../store/rootreducer';
export interface AddCommentProps {
setNewCommentsAvailable: Function;
- moment_id: string;
+ objectId: string;
+ placeholderText: string;
+ isCommentInFocus: boolean;
}
const AddComment: React.FC<AddCommentProps> = ({
setNewCommentsAvailable,
- moment_id,
+ objectId,
+ placeholderText,
+ isCommentInFocus,
}) => {
const [comment, setComment] = React.useState('');
+ const [keyboardVisible, setKeyboardVisible] = React.useState(false);
+ const {avatar} = useSelector((state: RootState) => state.user);
const dispatch = useDispatch();
- const {
- avatar,
- user: {userId},
- } = useSelector((state: RootState) => state.user);
- const handleCommentUpdate = (comment: string) => {
- setComment(comment);
- };
-
- const postComment = async () => {
- try {
- const token = await AsyncStorage.getItem('token');
- if (!token) {
- dispatch(logout());
- return;
- }
- const postedComment = await postMomentComment(
- userId,
- comment,
- moment_id,
- token,
- );
+ const addComment = async () => {
+ const trimmed = comment.trim();
+ if (trimmed === '') {
+ return;
+ }
+ const postedComment = await postComment(
+ trimmed,
+ objectId,
+ isCommentInFocus,
+ );
- if (postedComment) {
- //Set the current comment to en empty string if the comment was posted successfully.
- handleCommentUpdate('');
+ if (postedComment) {
+ setComment('');
- //Indicate the MomentCommentsScreen that it needs to download the new comments again
- setNewCommentsAvailable(true);
+ //Set new reply posted object
+ //This helps us show the latest reply on top
+ //Data set is kind of stale but it works
+ if (isCommentInFocus) {
+ dispatch(
+ updateReplyPosted({
+ comment_id: postedComment.comment_id,
+ parent_comment: {comment_id: objectId},
+ }),
+ );
}
- } catch (err) {
- console.log('Error while posting comment!');
+ setNewCommentsAvailable(true);
}
};
+ useEffect(() => {
+ const showKeyboard = () => setKeyboardVisible(true);
+ Keyboard.addListener('keyboardWillShow', showKeyboard);
+ return () => Keyboard.removeListener('keyboardWillShow', showKeyboard);
+ }, []);
+
+ useEffect(() => {
+ const hideKeyboard = () => setKeyboardVisible(false);
+ Keyboard.addListener('keyboardWillHide', hideKeyboard);
+ return () => Keyboard.removeListener('keyboardWillHide', hideKeyboard);
+ }, []);
+
+ const ref = useRef<TextInput>(null);
+
+ //If a comment is in Focus, bring the keyboard up so user is able to type in a reply
+ useEffect(() => {
+ if (isCommentInFocus) {
+ ref.current?.focus();
+ }
+ }, [isCommentInFocus]);
+
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
- keyboardVerticalOffset={130}>
- <View style={styles.container}>
- <Image
- style={styles.avatar}
- source={
- avatar
- ? {uri: avatar}
- : require('../../assets/images/avatar-placeholder.png')
- }
- />
- <TaggBigInput
- style={styles.text}
- multiline
- placeholder="Add a comment....."
- placeholderTextColor="gray"
- onChangeText={handleCommentUpdate}
- onSubmitEditing={postComment}
- value={comment}
- />
+ keyboardVerticalOffset={SCREEN_HEIGHT * 0.1}>
+ <View
+ style={[
+ styles.container,
+ keyboardVisible ? styles.whiteBackround : {},
+ ]}>
+ <View style={styles.textContainer}>
+ <Image
+ style={styles.avatar}
+ source={
+ avatar
+ ? {uri: avatar}
+ : require('../../assets/images/avatar-placeholder.png')
+ }
+ />
+ <TextInput
+ style={styles.text}
+ placeholder={placeholderText}
+ placeholderTextColor="grey"
+ onChangeText={setComment}
+ value={comment}
+ autoCorrect={false}
+ multiline={true}
+ ref={ref}
+ />
+ <View style={styles.submitButton}>
+ <TouchableOpacity style={styles.submitButton} onPress={addComment}>
+ <UpArrowIcon width={35} height={35} color={'white'} />
+ </TouchableOpacity>
+ </View>
+ </View>
</View>
</KeyboardAvoidingView>
);
};
+
const styles = StyleSheet.create({
- container: {flexDirection: 'row'},
+ container: {
+ backgroundColor: '#f7f7f7',
+ alignItems: 'center',
+ width: SCREEN_WIDTH,
+ },
+ textContainer: {
+ width: '95%',
+ flexDirection: 'row',
+ backgroundColor: '#e8e8e8',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ margin: '3%',
+ borderRadius: 25,
+ },
text: {
- position: 'relative',
- right: '18%',
- backgroundColor: 'white',
- width: '70%',
- paddingLeft: '2%',
- paddingRight: '2%',
- paddingBottom: '1%',
- paddingTop: '1%',
- height: 60,
+ flex: 1,
+ padding: '1%',
+ marginHorizontal: '1%',
},
avatar: {
- height: 40,
- width: 40,
+ height: 35,
+ width: 35,
borderRadius: 30,
- marginRight: 15,
+ marginRight: 10,
+ marginLeft: '3%',
+ marginVertical: '2%',
+ alignSelf: 'flex-end',
+ },
+ submitButton: {
+ height: 35,
+ width: 35,
+ backgroundColor: TAGG_LIGHT_BLUE,
+ borderRadius: 999,
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginRight: '3%',
+ marginVertical: '2%',
+ alignSelf: 'flex-end',
+ },
+ whiteBackround: {
+ backgroundColor: '#fff',
},
});
diff --git a/src/components/comments/CommentTile.tsx b/src/components/comments/CommentTile.tsx
index 47f25a53..be113523 100644
--- a/src/components/comments/CommentTile.tsx
+++ b/src/components/comments/CommentTile.tsx
@@ -1,10 +1,21 @@
-import React from 'react';
+/* eslint-disable radix */
+import React, {Fragment, useEffect, useRef, useState} from 'react';
import {Text, View} from 'react-native-animatable';
import {ProfilePreview} from '../profile';
-import {CommentType, ScreenType} from '../../types';
-import {StyleSheet} from 'react-native';
-import {getTimePosted} from '../../utils';
+import {CommentType, ScreenType, TypeOfComment} from '../../types';
+import {Alert, Animated, StyleSheet} from 'react-native';
import ClockIcon from '../../assets/icons/clock-icon-01.svg';
+import {TAGG_LIGHT_BLUE} from '../../constants';
+import {RectButton, TouchableOpacity} from 'react-native-gesture-handler';
+import {getTimePosted, normalize, SCREEN_WIDTH} from '../../utils';
+import Arrow from '../../assets/icons/back-arrow-colored.svg';
+import Trash from '../../assets/ionicons/trash-outline.svg';
+import CommentsContainer from './CommentsContainer';
+import Swipeable from 'react-native-gesture-handler/Swipeable';
+import {deleteComment, getCommentsCount} from '../../services';
+import {ERROR_FAILED_TO_DELETE_COMMENT} from '../../constants/strings';
+import {useSelector} from 'react-redux';
+import {RootState} from '../../store/rootReducer';
/**
* Displays users's profile picture, comment posted by them and the time difference between now and when a comment was posted.
@@ -13,54 +24,205 @@ import ClockIcon from '../../assets/icons/clock-icon-01.svg';
interface CommentTileProps {
comment_object: CommentType;
screenType: ScreenType;
+ typeOfComment: TypeOfComment;
+ setCommentObjectInFocus?: (comment: CommentType | undefined) => void;
+ newCommentsAvailable: boolean;
+ setNewCommentsAvailable: (available: boolean) => void;
+ canDelete: boolean;
}
const CommentTile: React.FC<CommentTileProps> = ({
comment_object,
screenType,
+ typeOfComment,
+ setCommentObjectInFocus,
+ newCommentsAvailable,
+ setNewCommentsAvailable,
+ canDelete,
}) => {
const timePosted = getTimePosted(comment_object.date_created);
+ const [showReplies, setShowReplies] = useState<boolean>(false);
+ const [showKeyboard, setShowKeyboard] = useState<boolean>(false);
+ const [newThreadAvailable, setNewThreadAvailable] = useState(true);
+ const swipeRef = useRef<Swipeable>(null);
+ const isThread = typeOfComment === 'Thread';
+
+ const {replyPosted} = useSelector((state: RootState) => state.user);
+
+ /**
+ * Bubbling up, for handling a new comment in a thread.
+ */
+ useEffect(() => {
+ if (newCommentsAvailable) {
+ setNewThreadAvailable(true);
+ }
+ }, [newCommentsAvailable]);
+
+ useEffect(() => {
+ if (replyPosted && typeOfComment === 'Comment') {
+ if (replyPosted.parent_comment.comment_id === comment_object.comment_id) {
+ setShowReplies(true);
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [replyPosted]);
+
+ /**
+ * Case : A COMMENT IS IN FOCUS && REPLY SECTION IS HIDDEN
+ * Bring the current comment to focus
+ * Case : No COMMENT IS IN FOCUS && REPLY SECTION IS SHOWN
+ * Unfocus comment in focus
+ */
+ const toggleAddComment = () => {
+ //Do not allow user to reply to a thread
+ if (!isThread) {
+ if (setCommentObjectInFocus) {
+ if (!showKeyboard) {
+ setCommentObjectInFocus(comment_object);
+ } else {
+ setCommentObjectInFocus(undefined);
+ }
+ }
+ setShowKeyboard(!showKeyboard);
+ }
+ };
+
+ const toggleReplies = async () => {
+ if (showReplies) {
+ //To update count of replies in case we deleted a reply
+ comment_object.replies_count = parseInt(
+ await getCommentsCount(comment_object.comment_id, true),
+ );
+ }
+ setNewThreadAvailable(true);
+ setShowReplies(!showReplies);
+ };
+
+ /**
+ * Method to compute text to be shown for replies button
+ */
+ const getRepliesText = () =>
+ showReplies
+ ? 'Hide'
+ : comment_object.replies_count > 0
+ ? `Replies (${comment_object.replies_count})`
+ : 'Replies';
+
+ const renderRightAction = (text: string, color: string, progress) => {
+ const pressHandler = async () => {
+ swipeRef.current?.close();
+ const success = await deleteComment(comment_object.comment_id, isThread);
+ if (success) {
+ setNewCommentsAvailable(true);
+ } else {
+ Alert.alert(ERROR_FAILED_TO_DELETE_COMMENT);
+ }
+ };
+ return (
+ <Animated.View>
+ <RectButton
+ style={[styles.rightAction, {backgroundColor: color}]}
+ onPress={pressHandler}>
+ <Trash width={normalize(25)} height={normalize(25)} color={'white'} />
+ <Text style={styles.actionText}>{text}</Text>
+ </RectButton>
+ </Animated.View>
+ );
+ };
+
+ const renderRightActions = (progress: Animated.AnimatedInterpolation) =>
+ canDelete ? (
+ <View style={styles.swipeActions}>
+ {renderRightAction('Delete', '#c42634', progress)}
+ </View>
+ ) : (
+ <Fragment />
+ );
+
return (
- <View style={styles.container}>
- <ProfilePreview
- profilePreview={{
- id: comment_object.commenter.id,
- username: comment_object.commenter.username,
- first_name: comment_object.commenter.first_name,
- last_name: comment_object.commenter.last_name,
- }}
- previewType={'Comment'}
- screenType={screenType}
- />
- <View style={styles.body}>
- <Text style={styles.comment}>{comment_object.comment}</Text>
- <View style={styles.clockIconAndTime}>
- <ClockIcon style={styles.clockIcon} />
- <Text style={styles.date_time}>{' ' + timePosted}</Text>
- </View>
+ <Swipeable
+ ref={swipeRef}
+ renderRightActions={renderRightActions}
+ rightThreshold={40}
+ friction={2}
+ containerStyle={styles.swipableContainer}>
+ <View
+ style={[styles.container, isThread ? styles.moreMarginWithThread : {}]}>
+ <ProfilePreview
+ profilePreview={comment_object.commenter}
+ previewType={'Comment'}
+ screenType={screenType}
+ />
+ <TouchableOpacity style={styles.body} onPress={toggleAddComment}>
+ <Text style={styles.comment}>{comment_object.comment}</Text>
+ <View style={styles.clockIconAndTime}>
+ <ClockIcon style={styles.clockIcon} />
+ <Text style={styles.date_time}>{' ' + timePosted}</Text>
+ <View style={styles.flexer} />
+ </View>
+ </TouchableOpacity>
+ {/*** Show replies text only if there are some replies present */}
+ {typeOfComment === 'Comment' && comment_object.replies_count > 0 && (
+ <TouchableOpacity
+ style={styles.repliesTextAndIconContainer}
+ onPress={toggleReplies}>
+ <Text style={styles.repliesText}>{getRepliesText()}</Text>
+ <Arrow
+ width={12}
+ height={11}
+ color={TAGG_LIGHT_BLUE}
+ style={
+ !showReplies ? styles.repliesDownArrow : styles.repliesUpArrow
+ }
+ />
+ </TouchableOpacity>
+ )}
</View>
- </View>
+
+ {/*** Show replies if toggle state is true */}
+ {showReplies && (
+ <View>
+ <CommentsContainer
+ objectId={comment_object.comment_id}
+ screenType={screenType}
+ setNewCommentsAvailable={setNewThreadAvailable}
+ newCommentsAvailable={newThreadAvailable}
+ typeOfComment={'Thread'}
+ commentId={replyPosted?.comment_id}
+ />
+ </View>
+ )}
+ </Swipeable>
);
};
const styles = StyleSheet.create({
container: {
- marginLeft: '3%',
- marginRight: '3%',
borderBottomWidth: 1,
borderColor: 'lightgray',
- marginBottom: '3%',
+ backgroundColor: 'white',
+ flexDirection: 'column',
+ flex: 1,
+ paddingTop: '3%',
+ paddingBottom: '5%',
+ marginLeft: '7%',
+ },
+ swipeActions: {
+ flexDirection: 'row',
+ },
+ moreMarginWithThread: {
+ marginLeft: '14%',
},
body: {
marginLeft: 56,
},
comment: {
- position: 'relative',
- top: -5,
marginBottom: '2%',
+ marginRight: '2%',
},
date_time: {
color: 'gray',
+ fontSize: normalize(12),
},
clockIcon: {
width: 12,
@@ -69,7 +231,50 @@ const styles = StyleSheet.create({
},
clockIconAndTime: {
flexDirection: 'row',
- marginBottom: '3%',
+ marginTop: '3%',
+ },
+ flexer: {
+ flex: 1,
+ },
+ repliesTextAndIconContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginTop: '5%',
+ marginLeft: 56,
+ },
+ repliesText: {
+ color: TAGG_LIGHT_BLUE,
+ fontWeight: '500',
+ fontSize: normalize(12),
+ marginRight: '1%',
+ },
+ repliesBody: {
+ width: SCREEN_WIDTH,
+ },
+ repliesDownArrow: {
+ transform: [{rotate: '270deg'}],
+ marginTop: '1%',
+ },
+ repliesUpArrow: {
+ transform: [{rotate: '90deg'}],
+ marginTop: '1%',
+ },
+ actionText: {
+ color: 'white',
+ fontSize: normalize(12),
+ fontWeight: '500',
+ backgroundColor: 'transparent',
+ paddingHorizontal: '5%',
+ marginTop: '5%',
+ },
+ rightAction: {
+ alignItems: 'center',
+ flex: 1,
+ justifyContent: 'center',
+ flexDirection: 'column',
+ },
+ swipableContainer: {
+ backgroundColor: 'white',
},
});
diff --git a/src/components/comments/CommentsContainer.tsx b/src/components/comments/CommentsContainer.tsx
new file mode 100644
index 00000000..c72da2b7
--- /dev/null
+++ b/src/components/comments/CommentsContainer.tsx
@@ -0,0 +1,171 @@
+import React, {useEffect, useRef, useState} from 'react';
+import {StyleSheet} from 'react-native';
+import {FlatList} from 'react-native-gesture-handler';
+import {useDispatch, useSelector} from 'react-redux';
+import {CommentTile} from '.';
+import {getComments} from '../../services';
+import {updateReplyPosted} from '../../store/actions';
+import {RootState} from '../../store/rootReducer';
+import {CommentType, ScreenType, TypeOfComment} from '../../types';
+import {SCREEN_HEIGHT} from '../../utils';
+export type CommentsContainerProps = {
+ screenType: ScreenType;
+ //objectId can be either moment_id or comment_id
+ objectId: string;
+ commentId?: string;
+ setCommentsLength?: (count: number) => void;
+ newCommentsAvailable: boolean;
+ setNewCommentsAvailable: (value: boolean) => void;
+ typeOfComment: TypeOfComment;
+ setCommentObjectInFocus?: (comment: CommentType | undefined) => void;
+ commentObjectInFocus?: CommentType;
+};
+
+/**
+ * Comments Container to be used for both comments and replies
+ */
+
+const CommentsContainer: React.FC<CommentsContainerProps> = ({
+ screenType,
+ objectId,
+ setCommentsLength,
+ newCommentsAvailable,
+ setNewCommentsAvailable,
+ typeOfComment,
+ setCommentObjectInFocus,
+ commentObjectInFocus,
+ commentId,
+}) => {
+ const {username: loggedInUsername} = useSelector(
+ (state: RootState) => state.user.user,
+ );
+ const [commentsList, setCommentsList] = useState<CommentType[]>([]);
+ const dispatch = useDispatch();
+ const ref = useRef<FlatList<CommentType>>(null);
+
+ useEffect(() => {
+ const loadComments = async () => {
+ await getComments(objectId, typeOfComment === 'Thread').then(
+ (comments) => {
+ if (comments && subscribedToLoadComments) {
+ setCommentsList(comments);
+ if (setCommentsLength) {
+ setCommentsLength(comments.length);
+ }
+ setNewCommentsAvailable(false);
+ }
+ },
+ );
+ };
+ let subscribedToLoadComments = true;
+ if (newCommentsAvailable) {
+ loadComments();
+ }
+ return () => {
+ subscribedToLoadComments = false;
+ };
+ }, [
+ dispatch,
+ objectId,
+ newCommentsAvailable,
+ setNewCommentsAvailable,
+ setCommentsLength,
+ typeOfComment,
+ ]);
+
+ // eslint-disable-next-line no-shadow
+ const swapCommentTo = (commentId: string, toIndex: number) => {
+ const index = commentsList.findIndex(
+ (item) => item.comment_id === commentId,
+ );
+ if (index > 0) {
+ let comments = [...commentsList];
+ const temp = comments[index];
+ comments[index] = comments[toIndex];
+ comments[toIndex] = temp;
+ setCommentsList(comments);
+ }
+ };
+
+ useEffect(() => {
+ //Scroll only if a new comment and not a reply was posted
+ const shouldScroll = () =>
+ typeOfComment === 'Comment' && !commentObjectInFocus;
+
+ const performAction = () => {
+ if (commentId) {
+ swapCommentTo(commentId, 0);
+ } else if (shouldScroll()) {
+ setTimeout(() => {
+ ref.current?.scrollToEnd({animated: true});
+ }, 500);
+ }
+ };
+ if (commentsList) {
+ //Bring the relevant comment to top if a comment id is present else scroll if necessary
+ performAction();
+ }
+
+ //Clean up the reply id present in store
+ return () => {
+ if (commentId && typeOfComment === 'Thread') {
+ setTimeout(() => {
+ dispatch(updateReplyPosted(undefined));
+ }, 200);
+ }
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [commentsList, commentId]);
+
+ //WIP : TODO : Bring the comment in focus above the keyboard
+ // useEffect(() => {
+ // if (commentObjectInFocus && commentsList.length >= 3) {
+ // swapCommentTo(commentObjectInFocus.comment_id, 2);
+ // }
+ // // eslint-disable-next-line react-hooks/exhaustive-deps
+ // }, [commentObjectInFocus]);
+
+ const ITEM_HEIGHT = SCREEN_HEIGHT / 7.0;
+
+ const renderComment = ({item}: {item: CommentType}) => (
+ <CommentTile
+ key={item.comment_id}
+ comment_object={item}
+ screenType={screenType}
+ typeOfComment={typeOfComment}
+ setCommentObjectInFocus={setCommentObjectInFocus}
+ newCommentsAvailable={newCommentsAvailable}
+ setNewCommentsAvailable={setNewCommentsAvailable}
+ canDelete={item.commenter.username === loggedInUsername}
+ />
+ );
+
+ return (
+ <FlatList
+ data={commentsList}
+ ref={ref}
+ keyExtractor={(item, index) => index.toString()}
+ decelerationRate={'fast'}
+ snapToAlignment={'start'}
+ snapToInterval={ITEM_HEIGHT}
+ renderItem={renderComment}
+ showsVerticalScrollIndicator={false}
+ contentContainerStyle={styles.scrollViewContent}
+ getItemLayout={(data, index) => ({
+ length: ITEM_HEIGHT,
+ offset: ITEM_HEIGHT * index,
+ index,
+ })}
+ pagingEnabled
+ />
+ );
+};
+
+const styles = StyleSheet.create({
+ scrollView: {},
+ scrollViewContent: {
+ justifyContent: 'center',
+ },
+});
+
+export default CommentsContainer;
diff --git a/src/components/common/AcceptDeclineButtons.tsx b/src/components/common/AcceptDeclineButtons.tsx
index 221056c0..9caaffca 100644
--- a/src/components/common/AcceptDeclineButtons.tsx
+++ b/src/components/common/AcceptDeclineButtons.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import {StyleProp, StyleSheet, Text, View, ViewStyle} from 'react-native';
-import {TAGG_TEXT_LIGHT_BLUE} from '../../constants';
+import {TAGG_LIGHT_BLUE} from '../../constants';
import {ProfilePreviewType} from '../../types';
import {SCREEN_WIDTH} from '../../utils';
import {TouchableOpacity} from 'react-native-gesture-handler';
@@ -55,18 +55,18 @@ const styles = StyleSheet.create({
},
acceptButton: {
padding: 0,
- backgroundColor: TAGG_TEXT_LIGHT_BLUE,
+ backgroundColor: TAGG_LIGHT_BLUE,
},
rejectButton: {
borderWidth: 1,
backgroundColor: 'white',
- borderColor: TAGG_TEXT_LIGHT_BLUE,
+ borderColor: TAGG_LIGHT_BLUE,
},
acceptButtonTitleColor: {
color: 'white',
},
rejectButtonTitleColor: {
- color: TAGG_TEXT_LIGHT_BLUE,
+ color: TAGG_LIGHT_BLUE,
},
buttonTitle: {
padding: 0,
diff --git a/src/components/common/GenericMoreInfoDrawer.tsx b/src/components/common/GenericMoreInfoDrawer.tsx
index 098482ae..a23d7736 100644
--- a/src/components/common/GenericMoreInfoDrawer.tsx
+++ b/src/components/common/GenericMoreInfoDrawer.tsx
@@ -10,7 +10,7 @@ import {
} from 'react-native';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {BottomDrawer} from '.';
-import {TAGG_TEXT_LIGHT_BLUE} from '../../constants';
+import {TAGG_LIGHT_BLUE} from '../../constants';
import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
// conforms the JSX onPress attribute type
@@ -87,7 +87,7 @@ const styles = StyleSheet.create({
panelButtonTitleCancel: {
fontSize: 18,
fontWeight: 'bold',
- color: TAGG_TEXT_LIGHT_BLUE,
+ color: TAGG_LIGHT_BLUE,
},
divider: {height: 1, borderWidth: 1, borderColor: '#e7e7e7'},
});
diff --git a/src/components/common/SocialLinkModal.tsx b/src/components/common/SocialLinkModal.tsx
index b307a62c..41b044fe 100644
--- a/src/components/common/SocialLinkModal.tsx
+++ b/src/components/common/SocialLinkModal.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import {Modal, StyleSheet, Text, TouchableHighlight, View} from 'react-native';
import {TextInput} from 'react-native-gesture-handler';
-import { TAGG_TEXT_LIGHT_BLUE } from '../../constants';
+import { TAGG_LIGHT_BLUE } from '../../constants';
import {SCREEN_WIDTH} from '../../utils';
interface SocialLinkModalProps {
@@ -105,7 +105,7 @@ const styles = StyleSheet.create({
fontSize: 14,
/* identical to box height */
textAlign: 'center',
- color: TAGG_TEXT_LIGHT_BLUE,
+ color: TAGG_LIGHT_BLUE,
},
textInput: {
height: 20,
diff --git a/src/components/common/TaggDatePicker.tsx b/src/components/common/TaggDatePicker.tsx
index 059bf620..f929b41d 100644
--- a/src/components/common/TaggDatePicker.tsx
+++ b/src/components/common/TaggDatePicker.tsx
@@ -12,7 +12,7 @@ interface TaggDatePickerProps {
const TaggDatePicker: React.FC<TaggDatePickerProps> = (props) => {
const [date, setDate] = useState(
props.date
- ? new Date(moment(props.date).add(1, 'day').format('YYYY-MM-DD'))
+ ? new Date(moment(props.date).add(1, 'day').format('MM-DD-YYYY'))
: undefined,
);
return (
diff --git a/src/components/moments/Moment.tsx b/src/components/moments/Moment.tsx
index 7905e8a9..a6b553b1 100644
--- a/src/components/moments/Moment.tsx
+++ b/src/components/moments/Moment.tsx
@@ -11,9 +11,9 @@ import DownIcon from '../../assets/icons/down_icon.svg';
import PlusIcon from '../../assets/icons/plus_icon-01.svg';
import BigPlusIcon from '../../assets/icons/plus_icon-02.svg';
import UpIcon from '../../assets/icons/up_icon.svg';
-import {TAGG_TEXT_LIGHT_BLUE} from '../../constants';
+import {TAGG_LIGHT_BLUE} from '../../constants';
import {ERROR_UPLOAD_MOMENT_SHORT} from '../../constants/strings';
-import {SCREEN_WIDTH} from '../../utils';
+import {normalize, SCREEN_WIDTH} from '../../utils';
import MomentTile from './MomentTile';
interface MomentProps {
@@ -87,7 +87,7 @@ const Moment: React.FC<MomentProps> = ({
width={19}
height={19}
onPress={() => move('up', title)}
- color={TAGG_TEXT_LIGHT_BLUE}
+ color={TAGG_LIGHT_BLUE}
style={{marginLeft: 5}}
/>
)}
@@ -96,7 +96,7 @@ const Moment: React.FC<MomentProps> = ({
width={19}
height={19}
onPress={() => move('down', title)}
- color={TAGG_TEXT_LIGHT_BLUE}
+ color={TAGG_LIGHT_BLUE}
style={{marginLeft: 5}}
/>
)}
@@ -111,7 +111,7 @@ const Moment: React.FC<MomentProps> = ({
width={21}
height={21}
onPress={() => navigateToImagePicker()}
- color={TAGG_TEXT_LIGHT_BLUE}
+ color={TAGG_LIGHT_BLUE}
style={{marginRight: 10}}
/>
{shouldAllowDeletion && (
@@ -171,15 +171,10 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
titleText: {
- fontSize: 16,
+ fontSize: normalize(16),
fontWeight: 'bold',
- color: TAGG_TEXT_LIGHT_BLUE,
+ color: TAGG_LIGHT_BLUE,
},
- // titleContainer: {
- // flex: 1,
- // flexDirection: 'row',
- // justifyContent: 'flex-end',
- // },
flexer: {
flex: 1,
},
diff --git a/src/components/moments/MomentPostContent.tsx b/src/components/moments/MomentPostContent.tsx
index 93271fa1..d68ceaa3 100644
--- a/src/components/moments/MomentPostContent.tsx
+++ b/src/components/moments/MomentPostContent.tsx
@@ -1,6 +1,6 @@
import React, {useEffect} from 'react';
import {Image, StyleSheet, Text, View, ViewProps} from 'react-native';
-import {getMomentCommentsCount} from '../../services';
+import {getCommentsCount} from '../../services';
import {ScreenType} from '../../types';
import {getTimePosted, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
import {CommentsCount} from '../comments';
@@ -24,9 +24,14 @@ const MomentPostContent: React.FC<MomentPostContentProps> = ({
const [comments_count, setCommentsCount] = React.useState('');
useEffect(() => {
+ const fetchCommentsCount = async () => {
+ const count = await getCommentsCount(momentId, false);
+ setCommentsCount(count);
+ };
setElapsedTime(getTimePosted(dateTime));
- getMomentCommentsCount(momentId, setCommentsCount);
+ fetchCommentsCount();
}, [dateTime, momentId]);
+
return (
<View style={[styles.container, style]}>
<Image
diff --git a/src/components/moments/MomentTile.tsx b/src/components/moments/MomentTile.tsx
index 16e91c82..69701192 100644
--- a/src/components/moments/MomentTile.tsx
+++ b/src/components/moments/MomentTile.tsx
@@ -15,7 +15,6 @@ const MomentTile: React.FC<MomentTileProps> = ({
}) => {
const navigation = useNavigation();
- const {path_hash} = moment;
return (
<TouchableOpacity
onPress={() => {
@@ -26,7 +25,7 @@ const MomentTile: React.FC<MomentTileProps> = ({
});
}}>
<View style={styles.image}>
- <Image style={styles.image} source={{uri: path_hash}} />
+ <Image style={styles.image} source={{uri: moment.thumbnail_url}} />
</View>
</TouchableOpacity>
);
diff --git a/src/components/notifications/Notification.tsx b/src/components/notifications/Notification.tsx
index e6d16f82..e0ae231e 100644
--- a/src/components/notifications/Notification.tsx
+++ b/src/components/notifications/Notification.tsx
@@ -1,25 +1,30 @@
import {useNavigation} from '@react-navigation/native';
import React, {useEffect, useState} from 'react';
-import {Image, StyleSheet, Text, View} from 'react-native';
-import {Button} from 'react-native-elements';
+import {Alert, Image, StyleSheet, Text, View} from 'react-native';
import {TouchableWithoutFeedback} from 'react-native-gesture-handler';
import {useDispatch, useStore} from 'react-redux';
+import {ERROR_DELETED_OBJECT} from '../../constants/strings';
import {
+ loadImageFromURL,
+ loadMoments,
+ loadMomentThumbnail,
+} from '../../services';
+import {
+ acceptFriendRequest,
declineFriendRequest,
loadUserNotifications,
+ updateReplyPosted,
updateUserXFriends,
} from '../../store/actions';
-import {acceptFriendRequest} from '../../store/actions';
-import {NotificationType, ProfilePreviewType, ScreenType, MomentType} from '../../types';
+import {RootState} from '../../store/rootReducer';
+import {MomentType, NotificationType, ScreenType} from '../../types';
import {
fetchUserX,
+ getTokenOrLogout,
SCREEN_HEIGHT,
- SCREEN_WIDTH,
userXInStore,
} from '../../utils';
import AcceptDeclineButtons from '../common/AcceptDeclineButtons';
-import {loadAvatar, loadMomentThumbnail} from '../../services';
-
interface NotificationProps {
item: NotificationType;
@@ -30,7 +35,7 @@ interface NotificationProps {
const Notification: React.FC<NotificationProps> = (props) => {
const {
item: {
- actor: {id, username, first_name, last_name},
+ actor: {id, username, first_name, last_name, thumbnail_url},
verbage,
notification_type,
notification_object,
@@ -44,22 +49,29 @@ const Notification: React.FC<NotificationProps> = (props) => {
const state: RootState = useStore().getState();
const dispatch = useDispatch();
- const [avatarURI, setAvatarURI] = useState<string | undefined>(undefined);
+ const [avatar, setAvatar] = useState<string | undefined>(undefined);
const [momentURI, setMomentURI] = useState<string | undefined>(undefined);
const backgroundColor = unread ? '#DCF1F1' : 'rgba(0,0,0,0)';
+ const [onTapLoadProfile, setOnTapLoadProfile] = useState<boolean>(false);
+
useEffect(() => {
- let mounted = true;
- const loadAvatarImage = async (user_id: string) => {
- const response = await loadAvatar(user_id, true);
- if (mounted) {
- setAvatarURI(response);
+ (async () => {
+ const response = await loadImageFromURL(thumbnail_url);
+ if (response) {
+ setAvatar(response);
}
- };
- loadAvatarImage(id);
+ })();
+ }, []);
+
+ useEffect(() => {
+ if (onTapLoadProfile) {
+ fetchUserX(dispatch, {userId: id, username: username}, screenType);
+ }
return () => {
- mounted = false;
+ setOnTapLoadProfile(false);
};
- }, [id]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [onTapLoadProfile]);
useEffect(() => {
let mounted = true;
@@ -70,7 +82,11 @@ const Notification: React.FC<NotificationProps> = (props) => {
}
};
if (notification_type === 'CMT' && notification_object) {
- loadMomentImage(notification_object.moment_id);
+ loadMomentImage(
+ notification_object.moment_id
+ ? notification_object.moment_id
+ : notification_object.parent_comment.moment_id,
+ );
return () => {
mounted = false;
};
@@ -94,20 +110,58 @@ const Notification: React.FC<NotificationProps> = (props) => {
});
break;
case 'CMT':
- // find the moment we need to display
- const moment = loggedInUserMoments?.find(
- (m) => m.moment_id === notification_object?.moment_id,
+ //Notification object is set to null if the comment / comment_thread / moment gets deleted
+ if (!notification_object) {
+ 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;
+ }
+
+ // Now find the moment we need to display
+ let moment: MomentType | undefined = loggedInUserMoments?.find(
+ (m) => m.moment_id === moment_id,
);
+ 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;
+ }
+
+ //Now if moment was found, navigate to the respective moment
if (moment) {
+ if (notification_object?.parent_comment) {
+ dispatch(updateReplyPosted(notification_object));
+ }
navigation.push('IndividualMoment', {
moment,
- userXId: undefined, // we're only viewing our own moment here
+ userXId: userXId, // we're only viewing our own moment here
screenType,
});
setTimeout(() => {
navigation.push('MomentCommentsScreen', {
- moment_id: moment.moment_id,
+ moment_id: moment_id,
screenType,
+ comment_id: comment_id,
});
}, 500);
}
@@ -118,7 +172,9 @@ const Notification: React.FC<NotificationProps> = (props) => {
};
const handleAcceptRequest = async () => {
- await dispatch(acceptFriendRequest({id, username, first_name, last_name}));
+ await dispatch(
+ acceptFriendRequest({id, username, first_name, last_name, thumbnail_url}),
+ );
await dispatch(updateUserXFriends(id, state));
dispatch(loadUserNotifications());
};
@@ -137,8 +193,8 @@ const Notification: React.FC<NotificationProps> = (props) => {
<Image
style={styles.avatar}
source={
- avatarURI
- ? {uri: avatarURI, cache: 'only-if-cached'}
+ avatar
+ ? {uri: avatar}
: require('../../assets/images/avatar-placeholder.png')
}
/>
@@ -159,8 +215,8 @@ const Notification: React.FC<NotificationProps> = (props) => {
</View>
)}
{notification_type === 'CMT' && notification_object && (
- <Image style={styles.moment} source={{uri: momentURI}} />
- )}
+ <Image style={styles.moment} source={{uri: momentURI}} />
+ )}
</TouchableWithoutFeedback>
</>
);
diff --git a/src/components/onboarding/BirthDatePicker.tsx b/src/components/onboarding/BirthDatePicker.tsx
index 0fc597c3..6bef5798 100644
--- a/src/components/onboarding/BirthDatePicker.tsx
+++ b/src/components/onboarding/BirthDatePicker.tsx
@@ -45,7 +45,7 @@ const BirthDatePicker = React.forwardRef(
ref={ref}
{...props}>
{(updated || props.showPresetdate) && date
- ? moment(date).format('YYYY-MM-DD')
+ ? moment(date).format('MM-DD-YYYY')
: 'Date of Birth'}
</Text>
</TouchableOpacity>
diff --git a/src/components/onboarding/TaggBigInput.tsx b/src/components/onboarding/TaggBigInput.tsx
index 4e8e1ef7..0e42bd13 100644
--- a/src/components/onboarding/TaggBigInput.tsx
+++ b/src/components/onboarding/TaggBigInput.tsx
@@ -1,5 +1,11 @@
import React from 'react';
-import {View, TextInput, StyleSheet, TextInputProps} from 'react-native';
+import {
+ View,
+ TextInput,
+ StyleSheet,
+ TextInputProps,
+ ViewStyle,
+} from 'react-native';
import * as Animatable from 'react-native-animatable';
import {TAGG_LIGHT_PURPLE} from '../../constants';
@@ -8,13 +14,15 @@ interface TaggBigInputProps extends TextInputProps {
invalidWarning?: string;
attemptedSubmit?: boolean;
width?: number | string;
+ containerStyle?: ViewStyle;
}
/**
* An input component that receives all props a normal TextInput component does. TaggInput components grow to 60% of their parent's width by default, but this can be set using the `width` prop.
*/
const TaggBigInput = React.forwardRef((props: TaggBigInputProps, ref: any) => {
return (
- <View style={styles.container}>
+ <View
+ style={props.containerStyle ? props.containerStyle : styles.container}>
<TextInput
style={[{width: props.width}, styles.input]}
placeholderTextColor="#ddd"
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx
index e7fb566b..a35a5820 100644
--- a/src/components/profile/Content.tsx
+++ b/src/components/profile/Content.tsx
@@ -1,3 +1,4 @@
+import {useFocusEffect, useNavigation} from '@react-navigation/native';
import React, {useCallback, useEffect, useState} from 'react';
import {
Alert,
@@ -9,52 +10,49 @@ import {
Text,
View,
} from 'react-native';
+import {TouchableOpacity} from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
+import {useDispatch, useSelector, useStore} from 'react-redux';
+import {Cover} from '.';
+import GreyPlusLogo from '../../assets/icons/grey-plus-logo.svg';
+import {COVER_HEIGHT, TAGG_LIGHT_BLUE} from '../../constants';
import {
- CategorySelectionScreenType,
- FriendshipStatusType,
- MomentCategoryType,
- MomentType,
- ProfilePreviewType,
- ProfileType,
- ScreenType,
- UserType,
-} from '../../types';
-import {COVER_HEIGHT, TAGG_TEXT_LIGHT_BLUE} from '../../constants';
+ UPLOAD_MOMENT_PROMPT_THREE_HEADER,
+ UPLOAD_MOMENT_PROMPT_THREE_MESSAGE,
+ UPLOAD_MOMENT_PROMPT_TWO_HEADER,
+ UPLOAD_MOMENT_PROMPT_TWO_MESSAGE,
+} from '../../constants/strings';
+import {
+ blockUnblockUser,
+ deleteUserMomentsForCategory,
+ friendUnfriendUser,
+ loadFriendsData,
+ updateMomentCategories,
+ updateUserXFriends,
+ updateUserXProfileAllScreens,
+} from '../../store/actions';
+import {
+ EMPTY_MOMENTS_LIST,
+ EMPTY_PROFILE_PREVIEW_LIST,
+ NO_PROFILE,
+ NO_USER,
+} from '../../store/initialStates';
+import {RootState} from '../../store/rootreducer';
+import {CategorySelectionScreenType, MomentType, ScreenType} from '../../types';
import {
fetchUserX,
getUserAsProfilePreviewType,
moveCategory,
+ normalize,
SCREEN_HEIGHT,
userLogin,
} from '../../utils';
-import TaggsBar from '../taggs/TaggsBar';
+import {TaggPrompt} from '../common';
import {Moment} from '../moments';
+import TaggsBar from '../taggs/TaggsBar';
import ProfileBody from './ProfileBody';
import ProfileCutout from './ProfileCutout';
import ProfileHeader from './ProfileHeader';
-import {useDispatch, useSelector, useStore} from 'react-redux';
-import {RootState} from '../../store/rootreducer';
-import {
- friendUnfriendUser,
- blockUnblockUser,
- loadFriendsData,
- updateUserXFriends,
- updateMomentCategories,
- deleteUserMomentsForCategory,
- updateUserXProfileAllScreens,
-} from '../../store/actions';
-import {
- NO_USER,
- NO_PROFILE,
- EMPTY_PROFILE_PREVIEW_LIST,
- EMPTY_MOMENTS_LIST,
-} from '../../store/initialStates';
-import {Cover} from '.';
-import {TouchableOpacity} from 'react-native-gesture-handler';
-import {useFocusEffect, useNavigation} from '@react-navigation/native';
-import GreyPlusLogo from '../../assets/icons/grey-plus-logo.svg';
-import {TaggPrompt} from '../common';
interface ContentProps {
y: Animated.Value<number>;
@@ -113,9 +111,10 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
const [isStageOnePromptClosed, setIsStageOnePromptClosed] = useState<boolean>(
false,
);
- const [isStageThreePromptClosed, setIsStageThreePromptClosed] = useState<
- boolean
- >(false);
+ const [
+ isStageThreePromptClosed,
+ setIsStageThreePromptClosed,
+ ] = useState<boolean>(false);
const onRefresh = useCallback(() => {
const refrestState = async () => {
@@ -284,7 +283,7 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
momentCategories.filter((mc) => mc !== category),
false,
),
- )
+ );
dispatch(deleteUserMomentsForCategory(category));
},
},
@@ -352,10 +351,8 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
profile.profile_completion_stage === 2 &&
!isStageTwoPromptClosed && (
<TaggPrompt
- messageHeader="Create a new category"
- messageBody={
- 'Post your first moment to continue building your digital identity!'
- }
+ messageHeader={UPLOAD_MOMENT_PROMPT_TWO_HEADER}
+ messageBody={UPLOAD_MOMENT_PROMPT_TWO_MESSAGE}
logoType=""
onClose={() => {
setIsStageTwoPromptClosed(true);
@@ -366,10 +363,8 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
profile.profile_completion_stage === 3 &&
!isStageThreePromptClosed && (
<TaggPrompt
- messageHeader="Continue to build your profile"
- messageBody={
- 'Continue to personalize your own digital space in\nthis community by filling your profile with\ncategories and moments!'
- }
+ messageHeader={UPLOAD_MOMENT_PROMPT_THREE_HEADER}
+ messageBody={UPLOAD_MOMENT_PROMPT_THREE_MESSAGE}
logoType=""
onClose={() => {
setIsStageThreePromptClosed(true);
@@ -423,7 +418,7 @@ const styles = StyleSheet.create({
flexDirection: 'column',
},
createCategoryButton: {
- backgroundColor: TAGG_TEXT_LIGHT_BLUE,
+ backgroundColor: TAGG_LIGHT_BLUE,
justifyContent: 'center',
alignItems: 'center',
width: '70%',
@@ -432,7 +427,7 @@ const styles = StyleSheet.create({
alignSelf: 'center',
},
createCategoryButtonLabel: {
- fontSize: 16,
+ fontSize: normalize(16),
fontWeight: '500',
color: 'white',
},
@@ -443,7 +438,7 @@ const styles = StyleSheet.create({
marginVertical: '10%',
},
noMomentsText: {
- fontSize: 14,
+ fontSize: normalize(14),
fontWeight: 'bold',
color: 'gray',
marginVertical: '8%',
diff --git a/src/components/profile/FriendsCount.tsx b/src/components/profile/FriendsCount.tsx
index 23a24787..851dbc3b 100644
--- a/src/components/profile/FriendsCount.tsx
+++ b/src/components/profile/FriendsCount.tsx
@@ -5,6 +5,7 @@ import {useNavigation} from '@react-navigation/native';
import {RootState} from '../../store/rootReducer';
import {useSelector} from 'react-redux';
import {ScreenType} from '../../types';
+import {normalize} from '../../utils';
interface FriendsCountProps extends ViewProps {
userXId: string | undefined;
@@ -16,10 +17,10 @@ const FriendsCount: React.FC<FriendsCountProps> = ({
userXId,
screenType,
}) => {
- const count = (userXId
+ const {friends} = userXId
? useSelector((state: RootState) => state.userX[screenType][userXId])
- : useSelector((state: RootState) => state.friends)
- )?.friends.length;
+ : useSelector((state: RootState) => state.friends);
+ const count = friends ? friends.length : 0;
const displayedCount: string =
count < 5e3
@@ -55,11 +56,11 @@ const styles = StyleSheet.create({
},
count: {
fontWeight: '700',
- fontSize: 13,
+ fontSize: normalize(14),
},
label: {
fontWeight: '500',
- fontSize: 13,
+ fontSize: normalize(14),
},
});
diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx
index 6284ff59..1ee3ae2b 100644
--- a/src/components/profile/ProfileBody.tsx
+++ b/src/components/profile/ProfileBody.tsx
@@ -1,9 +1,9 @@
import React from 'react';
import {StyleSheet, View, Text, LayoutChangeEvent, Linking} from 'react-native';
-import {Button} from 'react-native-elements';
+import {Button, normalize} from 'react-native-elements';
import {
TAGG_DARK_BLUE,
- TAGG_TEXT_LIGHT_BLUE,
+ TAGG_LIGHT_BLUE,
TOGGLE_BUTTON_TYPE,
} from '../../constants';
import ToggleButton from './ToggleButton';
@@ -105,8 +105,8 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({
{friendship_status === 'friends' && (
<Button
title={'Unfriend'}
- buttonStyle={styles.button}
- titleStyle={styles.buttonTitle}
+ buttonStyle={styles.requestedButton}
+ titleStyle={styles.requestedButtonTitle}
onPress={handleFriendUnfriend} // unfriend, no record status
/>
)}
@@ -160,16 +160,15 @@ const styles = StyleSheet.create({
},
username: {
fontWeight: '600',
- fontSize: 16.5,
+ fontSize: normalize(12),
marginBottom: '1%',
- marginTop: '-3%',
},
biography: {
- fontSize: 16,
+ fontSize: normalize(12),
marginBottom: '1.5%',
},
website: {
- fontSize: 16,
+ fontSize: normalize(12),
color: TAGG_DARK_BLUE,
marginBottom: '1%',
},
@@ -177,16 +176,17 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
width: SCREEN_WIDTH * 0.4,
- height: SCREEN_WIDTH * 0.09,
- borderColor: TAGG_TEXT_LIGHT_BLUE,
- borderWidth: 3,
- borderRadius: 5,
+ height: SCREEN_WIDTH * 0.075,
+ borderColor: TAGG_LIGHT_BLUE,
+ borderWidth: 2,
+ borderRadius: 0,
marginRight: '2%',
+ marginLeft: '1%',
padding: 0,
backgroundColor: 'transparent',
},
requestedButtonTitle: {
- color: TAGG_TEXT_LIGHT_BLUE,
+ color: TAGG_LIGHT_BLUE,
padding: 0,
fontSize: 14,
fontWeight: '700',
@@ -201,11 +201,14 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
width: SCREEN_WIDTH * 0.4,
- height: SCREEN_WIDTH * 0.09,
+ height: SCREEN_WIDTH * 0.075,
padding: 0,
- borderRadius: 5,
+ borderWidth: 2,
+ borderColor: TAGG_LIGHT_BLUE,
+ borderRadius: 0,
marginRight: '2%',
- backgroundColor: TAGG_TEXT_LIGHT_BLUE,
+ marginLeft: '1%',
+ backgroundColor: TAGG_LIGHT_BLUE,
},
});
diff --git a/src/components/profile/ProfileHeader.tsx b/src/components/profile/ProfileHeader.tsx
index 8d502d97..7dad2a68 100644
--- a/src/components/profile/ProfileHeader.tsx
+++ b/src/components/profile/ProfileHeader.tsx
@@ -2,10 +2,10 @@ import React, {useState} from 'react';
import {StyleSheet, Text, View} from 'react-native';
import {useSelector} from 'react-redux';
import {UniversityIcon} from '.';
-import {NO_MOMENTS} from '../../store/initialStates';
+import {PROFILE_CUTOUT_TOP_Y} from '../../constants';
import {RootState} from '../../store/rootreducer';
import {ScreenType} from '../../types';
-import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import {normalize} from '../../utils';
import Avatar from './Avatar';
import FriendsCount from './FriendsCount';
import ProfileMoreInfoDrawer from './ProfileMoreInfoDrawer';
@@ -31,7 +31,6 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({
: useSelector((state: RootState) => state.user);
const [drawerVisible, setDrawerVisible] = useState(false);
const [firstName, lastName] = [...name.split(' ')];
-
return (
<View style={styles.container}>
<ProfileMoreInfoDrawer
@@ -59,13 +58,8 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({
</View>
)}
<View style={styles.friendsAndUniversity}>
- <FriendsCount
- style={styles.friends}
- screenType={screenType}
- userXId={userXId}
- />
+ <FriendsCount screenType={screenType} userXId={userXId} />
<UniversityIcon
- style={styles.university}
university="brown"
university_class={university_class}
/>
@@ -78,7 +72,7 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({
const styles = StyleSheet.create({
container: {
- top: SCREEN_HEIGHT / 2.4,
+ top: PROFILE_CUTOUT_TOP_Y * 1.02,
width: '100%',
position: 'absolute',
},
@@ -87,35 +81,27 @@ const styles = StyleSheet.create({
},
header: {
flexDirection: 'column',
- justifyContent: 'center',
+ justifyContent: 'space-evenly',
alignItems: 'center',
- marginTop: SCREEN_WIDTH / 18.2,
- marginLeft: SCREEN_WIDTH / 8,
- marginBottom: SCREEN_WIDTH / 50,
+ marginRight: '15%',
+ marginLeft: '5%',
+ flex: 1,
},
avatar: {
- bottom: SCREEN_WIDTH / 80,
- left: '10%',
+ marginLeft: '3%',
+ top: '-8%',
},
name: {
- marginLeft: SCREEN_WIDTH / 8,
- fontSize: 17,
+ fontSize: normalize(17),
fontWeight: '500',
alignSelf: 'center',
},
- friends: {
- alignSelf: 'flex-start',
- marginRight: SCREEN_WIDTH / 20,
- },
- university: {
- alignSelf: 'flex-end',
- bottom: 3,
- },
friendsAndUniversity: {
flexDirection: 'row',
- flex: 1,
- marginLeft: SCREEN_WIDTH / 10,
- marginTop: SCREEN_WIDTH / 40,
+ alignItems: 'center',
+ justifyContent: 'space-evenly',
+ width: '100%',
+ height: 50,
},
});
diff --git a/src/components/profile/ProfileMoreInfoDrawer.tsx b/src/components/profile/ProfileMoreInfoDrawer.tsx
index 76f0f27f..daa83eb3 100644
--- a/src/components/profile/ProfileMoreInfoDrawer.tsx
+++ b/src/components/profile/ProfileMoreInfoDrawer.tsx
@@ -4,7 +4,7 @@ import {StyleSheet, TouchableOpacity} from 'react-native';
import {useSelector} from 'react-redux';
import MoreIcon from '../../assets/icons/more_horiz-24px.svg';
import PersonOutline from '../../assets/ionicons/person-outline.svg';
-import {TAGG_DARK_BLUE, TAGG_TEXT_LIGHT_BLUE} from '../../constants';
+import {TAGG_DARK_BLUE, TAGG_LIGHT_BLUE} from '../../constants';
import {RootState} from '../../store/rootreducer';
import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
import {GenericMoreInfoDrawer} from '../common';
@@ -101,13 +101,12 @@ const styles = StyleSheet.create({
panelButtonTitleCancel: {
fontSize: 18,
fontWeight: 'bold',
- color: TAGG_TEXT_LIGHT_BLUE,
+ color: TAGG_LIGHT_BLUE,
},
divider: {height: 1, borderWidth: 1, borderColor: '#e7e7e7'},
more: {
position: 'absolute',
right: '5%',
- marginTop: '4%',
zIndex: 1,
},
});
diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx
index b2c0a24d..38defb8d 100644
--- a/src/components/profile/ProfilePreview.tsx
+++ b/src/components/profile/ProfilePreview.tsx
@@ -1,32 +1,21 @@
-import React, {useEffect, useState, useContext} from 'react';
-import {ProfilePreviewType, ScreenType} from '../../types';
+import AsyncStorage from '@react-native-community/async-storage';
+import {useNavigation} from '@react-navigation/native';
+import React, {useEffect, useState} from 'react';
import {
- View,
- Text,
+ Alert,
Image,
StyleSheet,
- ViewProps,
+ Text,
TouchableOpacity,
- Alert,
+ View,
+ ViewProps,
} from 'react-native';
-import {useNavigation} from '@react-navigation/native';
-import RNFetchBlob from 'rn-fetch-blob';
-import AsyncStorage from '@react-native-community/async-storage';
-import {PROFILE_PHOTO_THUMBNAIL_ENDPOINT} from '../../constants';
-import {UserType, PreviewType} from '../../types';
-import {isUserBlocked, loadAvatar} from '../../services';
-import {useSelector, useDispatch, useStore} from 'react-redux';
+import {useDispatch, useSelector, useStore} from 'react-redux';
+import {ERROR_UNABLE_TO_VIEW_PROFILE} from '../../constants/strings';
+import {loadImageFromURL} from '../../services';
import {RootState} from '../../store/rootreducer';
-import {logout} from '../../store/actions';
+import {PreviewType, ProfilePreviewType, ScreenType} from '../../types';
import {checkIfUserIsBlocked, fetchUserX, userXInStore} from '../../utils';
-import {SearchResultsBackground} from '../search';
-import NavigationBar from 'src/routes/tabs';
-import {ERROR_UNABLE_TO_VIEW_PROFILE} from '../../constants/strings';
-
-const NO_USER: UserType = {
- userId: '',
- username: '',
-};
/**
* This component returns user's profile picture friended by username as a touchable component.
@@ -44,28 +33,23 @@ interface ProfilePreviewProps extends ViewProps {
}
const ProfilePreview: React.FC<ProfilePreviewProps> = ({
- profilePreview: {username, first_name, last_name, id},
+ profilePreview: {username, first_name, last_name, id, thumbnail_url},
previewType,
screenType,
}) => {
const navigation = useNavigation();
const {user: loggedInUser} = useSelector((state: RootState) => state.user);
- const [avatarURI, setAvatarURI] = useState<string | null>(null);
- const [user, setUser] = useState<UserType>(NO_USER);
+ const [avatar, setAvatar] = useState<string | null>(null);
const dispatch = useDispatch();
+
useEffect(() => {
- let mounted = true;
- const loadAvatarImage = async () => {
- const response = await loadAvatar(id, true);
- if (mounted) {
- setAvatarURI(response);
+ (async () => {
+ const response = await loadImageFromURL(thumbnail_url);
+ if (response) {
+ setAvatar(response);
}
- };
- loadAvatarImage();
- return () => {
- mounted = false;
- };
- }, [id]);
+ })();
+ }, []);
/**
* Adds a searched user to the recently searched cache if they're tapped on.
@@ -81,6 +65,7 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({
username,
first_name,
last_name,
+ thumbnail_url,
};
try {
@@ -212,8 +197,8 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({
<Image
style={avatarStyle}
source={
- avatarURI
- ? {uri: avatarURI}
+ avatar
+ ? {uri: avatar}
: require('../../assets/images/avatar-placeholder.png')
}
/>
diff --git a/src/components/profile/ToggleButton.tsx b/src/components/profile/ToggleButton.tsx
index 5d8f7874..236d811c 100644
--- a/src/components/profile/ToggleButton.tsx
+++ b/src/components/profile/ToggleButton.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import {StyleSheet, Text} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
-import {TAGG_TEXT_LIGHT_BLUE} from '../../constants';
+import {TAGG_LIGHT_BLUE} from '../../constants';
import {getToggleButtonText, SCREEN_WIDTH} from '../../utils';
type ToggleButtonProps = {
@@ -36,7 +36,7 @@ const styles = StyleSheet.create({
alignItems: 'center',
width: SCREEN_WIDTH * 0.4,
height: SCREEN_WIDTH * 0.08,
- borderColor: TAGG_TEXT_LIGHT_BLUE,
+ borderColor: TAGG_LIGHT_BLUE,
borderWidth: 3,
borderRadius: 5,
marginRight: '2%',
@@ -45,10 +45,10 @@ const styles = StyleSheet.create({
fontWeight: 'bold',
},
buttonColor: {
- backgroundColor: TAGG_TEXT_LIGHT_BLUE,
+ backgroundColor: TAGG_LIGHT_BLUE,
},
textColor: {color: 'white'},
buttonColorToggled: {backgroundColor: 'white'},
- textColorToggled: {color: TAGG_TEXT_LIGHT_BLUE},
+ textColorToggled: {color: TAGG_LIGHT_BLUE},
});
export default ToggleButton;
diff --git a/src/components/profile/UniversityIcon.tsx b/src/components/profile/UniversityIcon.tsx
index 13586359..95aef8b9 100644
--- a/src/components/profile/UniversityIcon.tsx
+++ b/src/components/profile/UniversityIcon.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import {StyleSheet, ViewProps} from 'react-native';
import {Image, Text, View} from 'react-native-animatable';
-import {getUniversityClass} from '../../utils';
+import {getUniversityClass, normalize} from '../../utils';
export interface UniversityIconProps extends ViewProps {
university: string;
@@ -38,19 +38,19 @@ const UniversityIcon: React.FC<UniversityIconProps> = ({
const styles = StyleSheet.create({
container: {
- flex: 1,
flexDirection: 'column',
flexWrap: 'wrap',
justifyContent: 'center',
+ alignItems: 'center',
+ height: '100%',
},
univClass: {
- fontSize: 13,
+ fontSize: normalize(14),
fontWeight: '500',
},
icon: {
- alignSelf: 'center',
- width: 17,
- height: 19,
+ width: normalize(17),
+ height: normalize(19),
},
});
diff --git a/src/components/search/Explore.tsx b/src/components/search/Explore.tsx
index c07c66b8..2a3bc749 100644
--- a/src/components/search/Explore.tsx
+++ b/src/components/search/Explore.tsx
@@ -4,6 +4,7 @@ import {useSelector} from 'react-redux';
import {EXPLORE_SECTION_TITLES} from '../../constants';
import {RootState} from '../../store/rootReducer';
import {ExploreSectionType} from '../../types';
+import {normalize} from '../../utils';
import ExploreSection from './ExploreSection';
const Explore: React.FC = () => {
@@ -11,9 +12,10 @@ const Explore: React.FC = () => {
return (
<View style={styles.container}>
<Text style={styles.header}>Search Profiles</Text>
- {EXPLORE_SECTION_TITLES.map((title: ExploreSectionType) => (
- <ExploreSection key={title} title={title} users={explores[title]} />
- ))}
+ {explores &&
+ EXPLORE_SECTION_TITLES.map((title: ExploreSectionType) => (
+ <ExploreSection key={title} title={title} users={explores[title]} />
+ ))}
</View>
);
};
@@ -21,11 +23,10 @@ const Explore: React.FC = () => {
const styles = StyleSheet.create({
container: {
zIndex: 0,
- // margin: '5%',
},
header: {
fontWeight: '700',
- fontSize: 22,
+ fontSize: normalize(22),
color: '#fff',
marginBottom: '2%',
margin: '5%',
diff --git a/src/components/search/ExploreSection.tsx b/src/components/search/ExploreSection.tsx
index 8e8b4988..025c8c3c 100644
--- a/src/components/search/ExploreSection.tsx
+++ b/src/components/search/ExploreSection.tsx
@@ -1,6 +1,7 @@
import React, {Fragment} from 'react';
-import {ScrollView, StyleSheet, Text, View} from 'react-native';
+import {FlatList, StyleSheet, Text, View} from 'react-native';
import {ProfilePreviewType} from '../../types';
+import {normalize} from '../../utils';
import ExploreSectionUser from './ExploreSectionUser';
/**
@@ -16,12 +17,15 @@ const ExploreSection: React.FC<ExploreSectionProps> = ({title, users}) => {
return users.length !== 0 ? (
<View style={styles.container}>
<Text style={styles.header}>{title}</Text>
- <ScrollView horizontal showsHorizontalScrollIndicator={false}>
- <View style={styles.padding} />
- {users.map((user) => (
+ <FlatList
+ data={users}
+ ListHeaderComponent={<View style={styles.padding} />}
+ renderItem={({item: user}: {item: ProfilePreviewType}) => (
<ExploreSectionUser key={user.id} user={user} style={styles.user} />
- ))}
- </ScrollView>
+ )}
+ showsHorizontalScrollIndicator={false}
+ horizontal
+ />
</View>
) : (
<Fragment />
@@ -34,7 +38,7 @@ const styles = StyleSheet.create({
},
header: {
fontWeight: '600',
- fontSize: 20,
+ fontSize: normalize(18),
color: '#fff',
marginLeft: '5%',
marginBottom: '5%',
diff --git a/src/components/search/ExploreSectionUser.tsx b/src/components/search/ExploreSectionUser.tsx
index 0bf68a20..b0cfe5c6 100644
--- a/src/components/search/ExploreSectionUser.tsx
+++ b/src/components/search/ExploreSectionUser.tsx
@@ -9,10 +9,10 @@ import {
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import {useDispatch, useSelector, useStore} from 'react-redux';
-import {loadAvatar} from '../../services';
+import {loadImageFromURL} from '../../services';
import {RootState} from '../../store/rootReducer';
import {ProfilePreviewType, ScreenType} from '../../types';
-import {fetchUserX, userXInStore} from '../../utils';
+import {fetchUserX, normalize, userXInStore} from '../../utils';
/**
* Search Screen for user recommendations and a search
@@ -36,18 +36,13 @@ const ExploreSectionUser: React.FC<ExploreSectionUserProps> = ({
const dispatch = useDispatch();
useEffect(() => {
- let mounted = true;
- const loadAvatarImage = async () => {
- const response = await loadAvatar(id, true);
- if (mounted) {
+ (async () => {
+ const response = await loadImageFromURL(user.thumbnail_url);
+ if (response) {
setAvatar(response);
}
- };
- loadAvatarImage();
- return () => {
- mounted = false;
- };
- }, [user]);
+ })();
+ }, []);
const handlePress = async () => {
if (!userXInStore(state, screenType, user.id)) {
@@ -63,7 +58,6 @@ const ExploreSectionUser: React.FC<ExploreSectionUserProps> = ({
screenType,
});
};
-
return (
<TouchableOpacity style={[styles.container, style]} onPress={handlePress}>
<LinearGradient
@@ -110,13 +104,13 @@ const styles = StyleSheet.create({
name: {
fontWeight: '600',
flexWrap: 'wrap',
- fontSize: 16,
+ fontSize: normalize(16),
color: '#fff',
textAlign: 'center',
},
username: {
fontWeight: '400',
- fontSize: 14,
+ fontSize: normalize(14),
color: '#fff',
},
});
diff --git a/src/components/search/RecentSearches.tsx b/src/components/search/RecentSearches.tsx
index 8a06017c..bdbd5773 100644
--- a/src/components/search/RecentSearches.tsx
+++ b/src/components/search/RecentSearches.tsx
@@ -7,7 +7,7 @@ import {
TouchableOpacityProps,
} from 'react-native';
import {PreviewType, ProfilePreviewType, ScreenType} from 'src/types';
-import {TAGG_TEXT_LIGHT_BLUE} from '../../constants';
+import {TAGG_LIGHT_BLUE} from '../../constants';
import SearchResults from './SearchResults';
interface RecentSearchesProps extends TouchableOpacityProps {
@@ -55,7 +55,7 @@ const styles = StyleSheet.create({
clear: {
fontSize: 18,
fontWeight: 'bold',
- color: TAGG_TEXT_LIGHT_BLUE,
+ color: TAGG_LIGHT_BLUE,
},
});
diff --git a/src/components/taggs/Tagg.tsx b/src/components/taggs/Tagg.tsx
index 82ac07df..5fa8b395 100644
--- a/src/components/taggs/Tagg.tsx
+++ b/src/components/taggs/Tagg.tsx
@@ -22,6 +22,7 @@ import {
ERROR_UNABLE_TO_FIND_PROFILE,
SUCCESS_LINK,
} from '../../constants/strings';
+import {normalize} from '../../utils';
interface TaggProps {
social: string;
@@ -165,7 +166,7 @@ const styles = StyleSheet.create({
justifyContent: 'space-between',
alignItems: 'center',
marginHorizontal: 15,
- height: 90,
+ height: normalize(90),
},
iconTap: {
justifyContent: 'center',
diff --git a/src/components/taggs/TwitterTaggPost.tsx b/src/components/taggs/TwitterTaggPost.tsx
index c971a82c..0cfde857 100644
--- a/src/components/taggs/TwitterTaggPost.tsx
+++ b/src/components/taggs/TwitterTaggPost.tsx
@@ -6,7 +6,7 @@ import LinearGradient from 'react-native-linear-gradient';
import {
AVATAR_DIM,
TAGGS_GRADIENT,
- TAGG_TEXT_LIGHT_BLUE,
+ TAGG_LIGHT_BLUE,
} from '../../constants';
import {TwitterPostType} from '../../types';
import {handleOpenSocialUrlOnBrowser, SCREEN_WIDTH} from '../../utils';
@@ -237,7 +237,7 @@ const styles = StyleSheet.create({
},
replyShowThisThread: {
fontSize: 15,
- color: TAGG_TEXT_LIGHT_BLUE,
+ color: TAGG_LIGHT_BLUE,
},
});