diff options
Diffstat (limited to 'src/components')
25 files changed, 369 insertions, 349 deletions
diff --git a/src/components/comments/AddComment.tsx b/src/components/comments/AddComment.tsx index ac1628da..f8c0b6bc 100644 --- a/src/components/comments/AddComment.tsx +++ b/src/components/comments/AddComment.tsx @@ -7,9 +7,11 @@ import { View, } from 'react-native'; import AsyncStorage from '@react-native-community/async-storage'; -import {AuthContext} from '../../routes'; import {TaggBigInput} from '../onboarding'; import {postMomentComment} from '../../services'; +import {logout} from '../../store/actions'; +import {useSelector, useDispatch} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; /** * This file provides the add comment view for a user. @@ -27,11 +29,12 @@ const AddComment: React.FC<AddCommentProps> = ({ moment_id, }) => { const [comment, setComment] = React.useState(''); + + const dispatch = useDispatch(); const { avatar, - user: {userId, username}, - logout, - } = React.useContext(AuthContext); + user: {userId}, + } = useSelector((state: RootState) => state.user); const handleCommentUpdate = (comment: string) => { setComment(comment); @@ -41,7 +44,7 @@ const AddComment: React.FC<AddCommentProps> = ({ try { const token = await AsyncStorage.getItem('token'); if (!token) { - logout(); + dispatch(logout()); return; } const postedComment = await postMomentComment( diff --git a/src/components/comments/CommentTile.tsx b/src/components/comments/CommentTile.tsx index bee590f5..da78a4dc 100644 --- a/src/components/comments/CommentTile.tsx +++ b/src/components/comments/CommentTile.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {Text, View} from 'react-native-animatable'; import {ProfilePreview} from '../profile'; -import {CommentType} from '../../types'; +import {CommentType, ScreenType} from '../../types'; import {StyleSheet} from 'react-native'; import {getTimePosted} from '../../utils'; import ClockIcon from '../../assets/icons/clock-icon-01.svg'; @@ -12,9 +12,13 @@ import ClockIcon from '../../assets/icons/clock-icon-01.svg'; interface CommentTileProps { comment_object: CommentType; + screenType: ScreenType; } -const CommentTile: React.FC<CommentTileProps> = ({comment_object}) => { +const CommentTile: React.FC<CommentTileProps> = ({ + comment_object, + screenType, +}) => { const timePosted = getTimePosted(comment_object.date_time); return ( <View style={styles.container}> @@ -26,6 +30,7 @@ const CommentTile: React.FC<CommentTileProps> = ({comment_object}) => { last_name: '', }} previewType={'Comment'} + screenType={screenType} /> <View style={styles.body}> <Text style={styles.comment}>{comment_object.comment}</Text> diff --git a/src/components/comments/CommentsCount.tsx b/src/components/comments/CommentsCount.tsx index a9d5b6d6..d210c39a 100644 --- a/src/components/comments/CommentsCount.tsx +++ b/src/components/comments/CommentsCount.tsx @@ -3,6 +3,7 @@ import {Text} from 'react-native-animatable'; import {StyleSheet, TouchableOpacity} from 'react-native'; import CommentIcon from '../../assets/icons/moment-comment-icon.svg'; import {useNavigation} from '@react-navigation/native'; +import {ScreenType} from '../../types'; /** * Provides a view for the comment icon and the comment count. @@ -11,20 +12,20 @@ import {useNavigation} from '@react-navigation/native'; type CommentsCountProps = { comments_count: string; - isProfileView: boolean; moment_id: string; + screenType: ScreenType; }; const CommentsCount: React.FC<CommentsCountProps> = ({ comments_count, - isProfileView, moment_id, + screenType, }) => { const navigation = useNavigation(); const navigateToCommentsScreen = async () => { navigation.push('MomentCommentsScreen', { - isProfileView: isProfileView, - moment_id: moment_id, + moment_id, + screenType, }); }; return ( diff --git a/src/components/common/AvatarTitle.tsx b/src/components/common/AvatarTitle.tsx index 65ae7486..a38f46fa 100644 --- a/src/components/common/AvatarTitle.tsx +++ b/src/components/common/AvatarTitle.tsx @@ -2,12 +2,9 @@ import React from 'react'; import {Image, StyleSheet, View} from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import {TAGGS_GRADIENT} from '../../constants'; -import {AuthContext, ProfileContext} from '../../routes/'; -import {loadAvatar} from '../../services'; -import AsyncStorage from '@react-native-community/async-storage'; type AvatarTitleProps = { - avatar: string; + avatar: string | null; }; const AvatarTitle: React.FC<AvatarTitleProps> = ({avatar}) => { return ( diff --git a/src/components/common/TaggLoadingIndicator.tsx b/src/components/common/TaggLoadingIndicator.tsx new file mode 100644 index 00000000..cfb99e80 --- /dev/null +++ b/src/components/common/TaggLoadingIndicator.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import {ActivityIndicator, StyleSheet, View} from 'react-native'; + +type TaggLoadingIndicatorProps = { + color: string; +}; +const TaggLoadingIndicator: React.FC<TaggLoadingIndicatorProps> = ({color}) => { + return ( + <View style={[styles.container, styles.horizontal]}> + <ActivityIndicator size="large" color={color} /> + </View> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + }, + horizontal: { + flexDirection: 'row', + justifyContent: 'space-around', + padding: 10, + }, +}); + +export default TaggLoadingIndicator; diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 0feeaab8..f6521497 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -15,4 +15,4 @@ export {default as ComingSoon} from './ComingSoon'; export {default as PostCarousel} from './PostCarousel'; export {default as TaggDatePicker} from './TaggDatePicker'; export {default as BottomDrawer} from './BottomDrawer'; -export * from './post'; +export {default as TaggLoadingTndicator} from './TaggLoadingIndicator'; diff --git a/src/components/common/post/Post.tsx b/src/components/common/post/Post.tsx deleted file mode 100644 index 9fa167f2..00000000 --- a/src/components/common/post/Post.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import {StyleSheet, View, Image} from 'react-native'; -import {PostType} from '../../../types'; -import PostHeader from './PostHeader'; -import {SCREEN_WIDTH} from '../../../utils'; - -interface PostProps { - post: PostType; -} -const Post: React.FC<PostProps> = ({post: {owner, social, data}}) => { - return ( - <> - <PostHeader post={data} owner={owner} social={social} /> - <View style={styles.image}> - {data && <Image style={styles.image} source={{uri: data.media_url}} />} - </View> - </> - ); -}; - -const styles = StyleSheet.create({ - image: { - width: SCREEN_WIDTH, - height: SCREEN_WIDTH, - backgroundColor: '#eee', - }, -}); -export default Post; diff --git a/src/components/common/post/PostHeader.tsx b/src/components/common/post/PostHeader.tsx deleted file mode 100644 index 0e9c708b..00000000 --- a/src/components/common/post/PostHeader.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react'; -import {UserType, InstagramPostType} from '../../../types'; -import {View, StyleSheet, Image, Text} from 'react-native'; -import {AuthContext} from '../../../routes/authentication'; -import SocialIcon from '../SocialIcon'; -import moment from 'moment'; - -const AVATAR_DIM = 35; -interface PostHeaderProps { - owner: UserType; - post: InstagramPostType | undefined; - social: string; -} -const PostHeader: React.FC<PostHeaderProps> = ({ - owner: {username}, - post, - social, -}) => { - const {avatar} = React.useContext(AuthContext); - - return ( - <View style={styles.container}> - <View style={styles.topRow}> - <Image - style={styles.avatar} - source={ - avatar - ? {uri: avatar} - : require('../../../assets/images/avatar-placeholder.png') - } - /> - <Text style={styles.username}>{username}</Text> - {post && <SocialIcon style={styles.icon} social={social} />} - </View> - {post && ( - <Text style={styles.timestamp}> - {moment(post.timestamp).format('LL')} at{' '} - {moment(post.timestamp).format('LT')} - </Text> - )} - </View> - ); -}; - -const styles = StyleSheet.create({ - container: { - flexDirection: 'column', - justifyContent: 'space-between', - padding: 10, - backgroundColor: 'white', - }, - topRow: { - flexDirection: 'row', - alignItems: 'center', - }, - avatar: { - width: AVATAR_DIM, - height: AVATAR_DIM, - borderRadius: AVATAR_DIM / 2, - marginRight: 10, - }, - icon: { - width: AVATAR_DIM, - height: AVATAR_DIM, - borderRadius: AVATAR_DIM / 2, - marginLeft: '55%', - }, - username: { - fontSize: 18, - }, - timestamp: { - color: '#6A757D', - fontSize: 11, - marginLeft: AVATAR_DIM + 10, - }, -}); - -export default PostHeader; diff --git a/src/components/common/post/index.ts b/src/components/common/post/index.ts deleted file mode 100644 index 358a59d5..00000000 --- a/src/components/common/post/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {default as Post} from './Post'; diff --git a/src/components/moments/Moment.tsx b/src/components/moments/Moment.tsx index 9e138ef3..0c8febcf 100644 --- a/src/components/moments/Moment.tsx +++ b/src/components/moments/Moment.tsx @@ -10,15 +10,21 @@ import {TAGG_TEXT_LIGHT_BLUE} from '../../constants'; import {SCREEN_WIDTH} from '../../utils'; import ImagePicker from 'react-native-image-crop-picker'; import MomentTile from './MomentTile'; -import {MomentType} from 'src/types'; +import {MomentType, ScreenType} from 'src/types'; interface MomentProps { title: string; images: MomentType[] | undefined; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } -const Moment: React.FC<MomentProps> = ({title, images, isProfileView}) => { +const Moment: React.FC<MomentProps> = ({ + title, + images, + userXId, + screenType, +}) => { const navigation = useNavigation(); const navigateToImagePicker = () => { @@ -32,6 +38,7 @@ const Moment: React.FC<MomentProps> = ({title, images, isProfileView}) => { .then((picture) => { if ('path' in picture) { navigation.navigate('CaptionScreen', { + screenType, title: title, image: picture, }); @@ -45,7 +52,7 @@ const Moment: React.FC<MomentProps> = ({title, images, isProfileView}) => { <View style={styles.container}> <View style={styles.header}> <Text style={styles.titleText}>{title}</Text> - {!isProfileView ? ( + {!userXId ? ( <PlusIcon width={21} height={21} @@ -64,10 +71,11 @@ const Moment: React.FC<MomentProps> = ({title, images, isProfileView}) => { <MomentTile key={imageObj.moment_id} moment={imageObj} - isProfileView={isProfileView} + userXId={userXId} + screenType={screenType} /> ))} - {(images === undefined || images.length === 0) && !isProfileView && ( + {(images === undefined || images.length === 0) && !userXId && ( <TouchableOpacity onPress={() => navigateToImagePicker()}> <LinearGradient colors={['rgba(105, 141, 211, 1)', 'rgba(105, 141, 211, 0.3)']}> diff --git a/src/components/moments/MomentTile.tsx b/src/components/moments/MomentTile.tsx index 787957e0..cc24c531 100644 --- a/src/components/moments/MomentTile.tsx +++ b/src/components/moments/MomentTile.tsx @@ -1,26 +1,29 @@ import {useNavigation} from '@react-navigation/native'; import React from 'react'; import {StyleSheet, View, Image, TouchableOpacity} from 'react-native'; -import {MomentType} from 'src/types'; -import {ProfileContext} from '../../routes'; +import {MomentType, ScreenType} from 'src/types'; interface MomentTileProps { moment: MomentType; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } -const MomentTile: React.FC<MomentTileProps> = ({moment, isProfileView}) => { +const MomentTile: React.FC<MomentTileProps> = ({ + moment, + userXId, + screenType, +}) => { const navigation = useNavigation(); - //Username is needed by the IndividualMoment screen - const { - user: {username}, - } = React.useContext(ProfileContext); - const {path_hash} = moment; return ( <TouchableOpacity onPress={() => { - navigation.push('IndividualMoment', {moment, isProfileView, username}); + navigation.push('IndividualMoment', { + moment, + screenType, + userXId, + }); }}> <View style={styles.image}> <Image style={styles.image} source={{uri: path_hash}} /> diff --git a/src/components/profile/Avatar.tsx b/src/components/profile/Avatar.tsx index aca3bf4d..903d0d18 100644 --- a/src/components/profile/Avatar.tsx +++ b/src/components/profile/Avatar.tsx @@ -1,16 +1,20 @@ -import React from 'react'; +import React, {useContext} from 'react'; import {Image, StyleSheet} from 'react-native'; -import {AuthContext, ProfileContext} from '../../routes/'; +import {useSelector} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; +import {ScreenType} from '../../types'; const PROFILE_DIM = 100; interface AvatarProps { style: object; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } -const Avatar: React.FC<AvatarProps> = ({style, isProfileView}) => { - const {avatar} = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); +const Avatar: React.FC<AvatarProps> = ({style, screenType, userXId}) => { + const {avatar} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); + return ( <Image style={[styles.image, style]} diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx index 13db60a5..73f6fad3 100644 --- a/src/components/profile/Content.tsx +++ b/src/components/profile/Content.tsx @@ -1,9 +1,13 @@ -import AsyncStorage from '@react-native-community/async-storage'; -import React, {useCallback, useEffect, useState} from 'react'; +import React, {useCallback, useEffect, useState, useContext} from 'react'; import {LayoutChangeEvent, StyleSheet, View} from 'react-native'; import Animated from 'react-native-reanimated'; -import {AuthContext, ProfileContext} from '../../routes/'; -import {MomentType} from 'src/types'; +import { + MomentType, + ProfilePreviewType, + ProfileType, + ScreenType, + UserType, +} from '../../types'; import {defaultMoments} from '../../constants'; import {SCREEN_HEIGHT} from '../../utils'; import TaggsBar from '../taggs/TaggsBar'; @@ -11,26 +15,49 @@ import {Moment} from '../moments'; import ProfileBody from './ProfileBody'; import ProfileCutout from './ProfileCutout'; import ProfileHeader from './ProfileHeader'; -import {followOrUnfollowUser, blockOrUnblockUser} from '../../services'; +import {useDispatch, useSelector, useStore} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; +import { + followUnfollowUser, + blockUnblockUser, + loadFollowData, + updateUserXFollowersAndFollowing, +} from '../../store/actions'; +import { + NO_USER, + NO_PROFILE, + EMPTY_PROFILE_PREVIEW_LIST, + EMPTY_MOMENTS_LIST, +} from '../../store/initialStates'; interface ContentProps { y: Animated.Value<number>; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } -const Content: React.FC<ContentProps> = ({y, isProfileView}) => { - const [profileBodyHeight, setProfileBodyHeight] = useState(0); - const {user, moments, followers, following, updateFollowers} = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); - - const { - logout, - user: loggedInUser, - updateFollowers: updateLoggedInUserFollowers, - blockedUsers, - updateBlockedUsers, - } = React.useContext(AuthContext); +const Content: React.FC<ContentProps> = ({y, 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 {followers = EMPTY_PROFILE_PREVIEW_LIST} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.follow); + + const {moments = EMPTY_MOMENTS_LIST} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.moments); + + const {blockedUsers = EMPTY_PROFILE_PREVIEW_LIST} = useSelector( + (state: RootState) => state.blocked, + ); + const {user: loggedInUser = NO_USER} = useSelector( + (state: RootState) => state.user, + ); + const state = useStore().getState(); /** * States @@ -38,8 +65,9 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { const [imagesMap, setImagesMap] = useState<Map<string, MomentType[]>>( new Map(), ); - const [isFollowed, setIsFollowed] = React.useState<boolean>(false); - const [isBlocked, setIsBlocked] = React.useState<boolean>(false); + const [isFollowed, setIsFollowed] = useState<boolean>(false); + const [isBlocked, setIsBlocked] = useState<boolean>(false); + const [profileBodyHeight, setProfileBodyHeight] = useState(0); /** * If own profile is being viewed then do not show the follow button. @@ -51,8 +79,6 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { setProfileBodyHeight(height); }; - const {userId} = user; - const createImagesMap = useCallback(() => { var map = new Map(); moments.forEach(function (imageObject) { @@ -68,9 +94,6 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { }, [moments]); useEffect(() => { - if (!userId) { - return; - } createImagesMap(); }, [createImagesMap]); @@ -78,9 +101,6 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { * This hook is called on load of profile and when you update the followers list. */ useEffect(() => { - if (!userId) { - return; - } const isActuallyFollowed = followers.some( (follower) => follower.username === loggedInUser.username, ); @@ -90,62 +110,63 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { }, [followers]); useEffect(() => { - if (!userId) { - return; - } - const isActuallyBlocked = blockedUsers.some( (cur_user) => user.username === cur_user.username, ); if (isBlocked != isActuallyBlocked) { setIsBlocked(isActuallyBlocked); } - }, [blockedUsers]); + }, [blockedUsers, user]); + + /** + * The object returned by this method is added to the list of blocked / followed users by the reducer. + * Which helps us prevent an extra api call to the backend just to fetch a user. + */ + const getUserAsProfilePreviewType = ( + passedInUser: UserType, + passedInProfile: ProfileType, + ): ProfilePreviewType => { + const fullName = passedInProfile.name.split(' '); + return { + id: passedInUser.userId, + username: passedInUser.username, + first_name: fullName[0], + last_name: fullName[1], + }; + }; /** * Handles a click on the follow / unfollow button. - * updateFollowers and updateLoggedInUerFollowers to make sure that we update followers list / count for both the users in context. + * followUnfollowUser takes care of updating the following list for loggedInUser + * updateUserXFollowersAndFollowing updates followers and following list for the followed user. */ + const handleFollowUnfollow = async () => { - const token = await AsyncStorage.getItem('token'); - if (!token) { - logout(); - return; - } - const isUpdatedSuccessful = await followOrUnfollowUser( - loggedInUser.userId, - userId, - token, - isFollowed, + await dispatch( + followUnfollowUser( + loggedInUser, + getUserAsProfilePreviewType(user, profile), + isFollowed, + ), ); - if (isUpdatedSuccessful) { - setIsFollowed(!isFollowed); - updateFollowers(true); - updateLoggedInUserFollowers(true); - } + await dispatch(updateUserXFollowersAndFollowing(user.userId, state)); }; /** * Handles a click on the block / unblock button. + * loadFollowData updates followers / following list for the logged in user + * updateUserXFollowersAndFollowing updates followers and following list for the followed user. */ const handleBlockUnblock = async () => { - const token = await AsyncStorage.getItem('token'); - if (!token) { - logout(); - return; - } - const isUpdatedSuccessful = await blockOrUnblockUser( - loggedInUser.userId, - userId, - token, - isBlocked, + await dispatch( + blockUnblockUser( + loggedInUser, + getUserAsProfilePreviewType(user, profile), + isBlocked, + ), ); - if (isUpdatedSuccessful) { - setIsBlocked(!isBlocked); - updateBlockedUsers(true); - updateFollowers(true); - updateLoggedInUserFollowers(true); - } + await dispatch(loadFollowData(loggedInUser.userId)); + await dispatch(updateUserXFollowersAndFollowing(user.userId, state)); }; return ( @@ -155,15 +176,12 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { showsVerticalScrollIndicator={false} scrollEventThrottle={1}> <ProfileCutout /> - <ProfileHeader - isProfileView={isProfileView} - numFollowing={following.length} - numFollowers={followers.length} - /> + <ProfileHeader {...{userXId, screenType}} /> <ProfileBody {...{ onLayout, - isProfileView, + userXId, + screenType, isOwnProfile, isFollowed, handleFollowUnfollow, @@ -171,14 +189,15 @@ const Content: React.FC<ContentProps> = ({y, isProfileView}) => { handleBlockUnblock, }} /> - <TaggsBar {...{y, profileBodyHeight, isProfileView}} /> + <TaggsBar {...{y, profileBodyHeight, userXId, screenType}} /> <View style={styles.momentsContainer}> {defaultMoments.map((title, index) => ( <Moment key={index} title={title} images={imagesMap.get(title)} - isProfileView={isProfileView} + userXId={userXId} + screenType={screenType} /> ))} </View> diff --git a/src/components/profile/Cover.tsx b/src/components/profile/Cover.tsx index 36e41776..3c0f7045 100644 --- a/src/components/profile/Cover.tsx +++ b/src/components/profile/Cover.tsx @@ -1,18 +1,23 @@ -import React from 'react'; +import React, {useContext} from 'react'; import {Image, StyleSheet} from 'react-native'; import Animated from 'react-native-reanimated'; import {IMAGE_WIDTH, COVER_HEIGHT, IMAGE_HEIGHT} from '../../constants'; -import {AuthContext, ProfileContext} from '../../routes/'; +import {useSelector, useStore} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; +import {ScreenType} from '../../types'; +import {DUMMY_USERID, NO_USER_DATA} from '../../store/initialStates'; const {interpolate, Extrapolate} = Animated; interface CoverProps { y: Animated.Value<number>; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } -const Cover: React.FC<CoverProps> = ({y, isProfileView}) => { - const {cover} = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); +const Cover: React.FC<CoverProps> = ({y, userXId, screenType}) => { + const {cover = ''} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); + const scale: Animated.Node<number> = interpolate(y, { inputRange: [-COVER_HEIGHT, 0], outputRange: [1.5, 1.25], diff --git a/src/components/profile/FollowCount.tsx b/src/components/profile/FollowCount.tsx index 3e270428..a23a3533 100644 --- a/src/components/profile/FollowCount.tsx +++ b/src/components/profile/FollowCount.tsx @@ -1,24 +1,33 @@ -import React from 'react'; +import React, {useContext} from 'react'; import {View, Text, StyleSheet, ViewProps} from 'react-native'; import {TouchableOpacity} from 'react-native-gesture-handler'; import {useNavigation} from '@react-navigation/native'; -import {AuthContext, ProfileContext} from '../../routes'; +import {RootState} from '../../store/rootReducer'; +import {useSelector} from 'react-redux'; +import {ScreenType} from '../../types'; +import {EMPTY_PROFILE_PREVIEW_LIST} from '../../store/initialStates'; interface FollowCountProps extends ViewProps { mode: 'followers' | 'following'; - count: number; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } const FollowCount: React.FC<FollowCountProps> = ({ style, mode, - count, - isProfileView, + userXId, + screenType, }) => { - const {followers, following} = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); + const { + followers = EMPTY_PROFILE_PREVIEW_LIST, + following = EMPTY_PROFILE_PREVIEW_LIST, + } = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.follow); + + const isFollowers = mode === 'followers'; + const count = isFollowers ? followers.length : following.length; const navigation = useNavigation(); const displayed: string = @@ -33,14 +42,15 @@ const FollowCount: React.FC<FollowCountProps> = ({ <TouchableOpacity onPress={() => navigation.push('FollowersListScreen', { - isFollowers: mode === 'followers', - list: mode === 'followers' ? followers : following, + isFollowers, + userXId, + screenType, }) }> <View style={[styles.container, style]}> <Text style={styles.count}>{displayed}</Text> <Text style={styles.label}> - {mode === 'followers' ? 'Followers' : 'Following'} + {isFollowers ? 'Followers' : 'Following'} </Text> </View> </TouchableOpacity> diff --git a/src/components/profile/Followers.tsx b/src/components/profile/Followers.tsx index e11041d0..c665603d 100644 --- a/src/components/profile/Followers.tsx +++ b/src/components/profile/Followers.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {View, StyleSheet, ViewProps, Text} from 'react-native'; -import {ProfilePreviewType} from '../../types'; +import {ProfilePreviewType, ScreenType} from '../../types'; import {ProfilePreview} from '..'; import {useNavigation} from '@react-navigation/native'; import {Button} from 'react-native-elements'; @@ -8,9 +8,14 @@ import {Button} from 'react-native-elements'; interface FollowersListProps { result: Array<ProfilePreviewType>; sectionTitle: string; + screenType: ScreenType; } -const Followers: React.FC<FollowersListProps> = ({result, sectionTitle}) => { +const Followers: React.FC<FollowersListProps> = ({ + result, + sectionTitle, + screenType, +}) => { const navigation = useNavigation(); return ( <> @@ -31,6 +36,7 @@ const Followers: React.FC<FollowersListProps> = ({result, sectionTitle}) => { key={profilePreview.id} {...{profilePreview}} previewType={'Comment'} + screenType={screenType} /> ))} </> diff --git a/src/components/profile/MoreInfoDrawer.tsx b/src/components/profile/MoreInfoDrawer.tsx index 719c1894..a8908b4d 100644 --- a/src/components/profile/MoreInfoDrawer.tsx +++ b/src/components/profile/MoreInfoDrawer.tsx @@ -4,14 +4,14 @@ import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import {TAGG_TEXT_LIGHT_BLUE} from '../../constants'; import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; -import {AuthContext} from '../../routes'; import {BottomDrawer} from '../common'; import PersonOutline from '../../assets/ionicons/person-outline.svg'; +import {useSelector} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; interface MoreInfoDrawerProps { isOpen: boolean; setIsOpen: (visible: boolean) => void; - isProfileView: boolean; } const MoreInfoDrawer: React.FC<MoreInfoDrawerProps> = (props) => { @@ -20,7 +20,7 @@ const MoreInfoDrawer: React.FC<MoreInfoDrawerProps> = (props) => { const navigation = useNavigation(); const { user: {userId, username}, - } = useContext(AuthContext); + } = useSelector((state: RootState) => state.user); const goToEditProfile = () => { navigation.push('EditProfile', { diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx index c0253533..3c05fc26 100644 --- a/src/components/profile/ProfileBody.tsx +++ b/src/components/profile/ProfileBody.tsx @@ -1,33 +1,38 @@ -import React from 'react'; +import React, {useContext} from 'react'; import {StyleSheet, View, Text, LayoutChangeEvent} from 'react-native'; import {TAGG_DARK_BLUE, TOGGLE_BUTTON_TYPE} from '../../constants'; -import {AuthContext, ProfileContext} from '../../routes/'; import ToggleButton from './ToggleButton'; +import {RootState} from '../../store/rootReducer'; +import {useSelector} from 'react-redux'; +import {ScreenType} from '../../types'; +import {NO_PROFILE} from '../../store/initialStates'; interface ProfileBodyProps { onLayout: (event: LayoutChangeEvent) => void; - isProfileView: boolean; isFollowed: boolean; isBlocked: boolean; isOwnProfile: boolean; handleFollowUnfollow: Function; handleBlockUnblock: Function; + userXId: string; + screenType: ScreenType; } const ProfileBody: React.FC<ProfileBodyProps> = ({ onLayout, - isProfileView, isFollowed, isBlocked, isOwnProfile, handleFollowUnfollow, handleBlockUnblock, + userXId, + screenType, }) => { const { - profile, + profile = NO_PROFILE, user: {username}, - } = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); + } = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); const {biography, website} = profile; @@ -36,7 +41,7 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({ <Text style={styles.username}>{`@${username}`}</Text> <Text style={styles.biography}>{`${biography}`}</Text> <Text style={styles.website}>{`${website}`}</Text> - {isProfileView && !isOwnProfile ? ( + {userXId && !isOwnProfile ? ( <View style={styles.toggleButtonContainer}> {!isBlocked && ( <ToggleButton diff --git a/src/components/profile/ProfileHeader.tsx b/src/components/profile/ProfileHeader.tsx index 62949746..621aae9a 100644 --- a/src/components/profile/ProfileHeader.tsx +++ b/src/components/profile/ProfileHeader.tsx @@ -1,34 +1,30 @@ -import React, {useState} from 'react'; +import React, {useState, useContext} from 'react'; import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'; import MoreIcon from '../../assets/icons/more_horiz-24px.svg'; import {TAGG_DARK_BLUE} from '../../constants'; -import {AuthContext, ProfileContext} from '../../routes/'; import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; import Avatar from './Avatar'; import MoreInfoDrawer from './MoreInfoDrawer'; import FollowCount from './FollowCount'; +import {useSelector} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; +import {ScreenType} from '../../types'; type ProfileHeaderProps = { - isProfileView: boolean; - numFollowing: number; - numFollowers: number; + userXId: string; + screenType: ScreenType; }; -const ProfileHeader: React.FC<ProfileHeaderProps> = ({ - isProfileView, - numFollowing, - numFollowers, -}) => { - const { - profile: {name}, - } = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); +const ProfileHeader: React.FC<ProfileHeaderProps> = ({userXId, screenType}) => { + const {profile: {name = ''} = {}} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); + const [drawerVisible, setDrawerVisible] = useState(false); return ( <View style={styles.container}> - {!isProfileView && ( + {!userXId && ( <> <TouchableOpacity style={styles.more} @@ -37,29 +33,29 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = ({ }}> <MoreIcon height={30} width={30} color={TAGG_DARK_BLUE} /> </TouchableOpacity> - <MoreInfoDrawer - isOpen={drawerVisible} - setIsOpen={setDrawerVisible} - isProfileView={isProfileView} - /> + <MoreInfoDrawer isOpen={drawerVisible} setIsOpen={setDrawerVisible} /> </> )} <View style={styles.row}> - <Avatar style={styles.avatar} isProfileView={isProfileView} /> + <Avatar + style={styles.avatar} + userXId={userXId} + screenType={screenType} + /> <View style={styles.header}> <Text style={styles.name}>{name}</Text> <View style={styles.row}> <FollowCount style={styles.follows} mode="followers" - count={numFollowers} - isProfileView={isProfileView} + screenType={screenType} + userXId={userXId} /> <FollowCount style={styles.follows} mode="following" - count={numFollowing} - isProfileView={isProfileView} + screenType={screenType} + userXId={userXId} /> </View> </View> diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx index af116dd3..5567fa5a 100644 --- a/src/components/profile/ProfilePreview.tsx +++ b/src/components/profile/ProfilePreview.tsx @@ -1,5 +1,5 @@ import React, {useEffect, useState, useContext} from 'react'; -import {ProfilePreviewType} from '../../types'; +import {ProfilePreviewType, ScreenType} from '../../types'; import { View, Text, @@ -14,8 +14,11 @@ import RNFetchBlob from 'rn-fetch-blob'; import AsyncStorage from '@react-native-community/async-storage'; import {AVATAR_PHOTO_ENDPOINT} from '../../constants'; import {UserType, PreviewType} from '../../types'; -import {AuthContext} from '../../routes/'; import {isUserBlocked} from '../../services'; +import {useSelector, useDispatch, useStore} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; +import {loadUserX, logout} from '../../store/actions'; +import {userXInStore} from '../../utils'; const NO_USER: UserType = { userId: '', username: '', @@ -27,22 +30,24 @@ const NO_USER: UserType = { * If isComment is true then it means that we are not displaying this tile as a part of search results. * And hence we do not cache the search results. * On the other hand, if isComment is false, then we should update the search cache. (This cache needs to be revamped to clear outdated results.) - * In either case, we update the userBeingVisited in our AuthContext (Which can be used to make api calls later on to fetch user specific data). * Finally, We navigate to Profile. */ interface ProfilePreviewProps extends ViewProps { profilePreview: ProfilePreviewType; previewType: PreviewType; + screenType: ScreenType; } const ProfilePreview: React.FC<ProfilePreviewProps> = ({ profilePreview: {username, first_name, last_name, id}, previewType, + screenType, }) => { const navigation = useNavigation(); - const {user: loggedInUser, logout} = React.useContext(AuthContext); + const {user: loggedInUser} = useSelector((state: RootState) => state.user); const [avatarURI, setAvatarURI] = useState<string | null>(null); const [user, setUser] = useState<UserType>(NO_USER); + const dispatch = useDispatch(); useEffect(() => { let mounted = true; const loadAvatar = async () => { @@ -87,12 +92,14 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({ const checkIfUserIsBlocked = async (userId: string) => { const token = await AsyncStorage.getItem('token'); if (!token) { - logout(); + dispatch(logout()); return false; } return await isUserBlocked(userId, loggedInUser.userId, token); }; + const state: RootState = useStore().getState(); + const addToRecentlyStoredAndNavigateToProfile = async () => { let user: ProfilePreviewType = { id, @@ -145,12 +152,19 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({ } /** - * Navigate to profile of the user selected + * Dispatch an event to Fetch the user details + * If the user is already present in store, do not fetch again + * Finally, Navigate to profile of the user selected */ + if (!userXInStore(state, screenType, user.id)) { + dispatch( + loadUserX({userId: user.id, username: user.username}, screenType), + ); + } navigation.push('Profile', { - isProfileView: true, username: user.username, - userId: user.id, + userXId: user.id, + screenType: screenType, }); } catch (e) { console.log(e); diff --git a/src/components/search/DiscoverUsers.tsx b/src/components/search/DiscoverUsers.tsx index 885c712b..ec0a8daa 100644 --- a/src/components/search/DiscoverUsers.tsx +++ b/src/components/search/DiscoverUsers.tsx @@ -6,23 +6,32 @@ import { StyleSheet, TouchableOpacityProps, } from 'react-native'; -import {ProfilePreviewType} from '../../types'; +import {PreviewType, ProfilePreviewType, ScreenType} from '../../types'; import SearchResults from './SearchResults'; interface DiscoverUsersProps extends TouchableOpacityProps { - sectionTitle: string; + sectionTitle: PreviewType; users: Array<ProfilePreviewType>; + screenType: ScreenType; } /** * An image component that returns the <Image> of the icon for a specific social media platform. */ -const DiscoverUsers: React.FC<DiscoverUsersProps> = (props) => { +const DiscoverUsers: React.FC<DiscoverUsersProps> = ({ + sectionTitle, + screenType, + users, +}) => { return ( <View style={styles.container}> <View style={styles.headerContainer}> - <Text style={styles.title}>{props.sectionTitle}</Text> + <Text style={styles.title}>{sectionTitle}</Text> </View> - <SearchResults results={props.users} previewType={props.sectionTitle} /> + <SearchResults + results={users} + previewType={sectionTitle} + screenType={screenType} + /> </View> ); }; diff --git a/src/components/search/RecentSearches.tsx b/src/components/search/RecentSearches.tsx index 6a98e49a..22a36a6b 100644 --- a/src/components/search/RecentSearches.tsx +++ b/src/components/search/RecentSearches.tsx @@ -6,14 +6,15 @@ import { StyleSheet, TouchableOpacityProps, } from 'react-native'; -import {ProfilePreviewType} from 'src/types'; -import { TAGG_TEXT_LIGHT_BLUE } from '../../constants'; +import {PreviewType, ProfilePreviewType, ScreenType} from 'src/types'; +import {TAGG_TEXT_LIGHT_BLUE} from '../../constants'; import SearchResults from './SearchResults'; interface RecentSearchesProps extends TouchableOpacityProps { - sectionTitle: string; + sectionTitle: PreviewType; sectionButtonTitle: string; recents: Array<ProfilePreviewType>; + screenType: ScreenType; } /** * An image component that returns the <Image> of the icon for a specific social media platform. @@ -29,7 +30,11 @@ const RecentSearches: React.FC<RecentSearchesProps> = (props) => { </TouchableOpacity> )} </View> - <SearchResults results={props.recents} previewType={props.sectionTitle} /> + <SearchResults + results={props.recents} + previewType={props.sectionTitle} + screenType={props.screenType} + /> </> ); }; diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx index 2d5c9db8..001c7968 100644 --- a/src/components/search/SearchResults.tsx +++ b/src/components/search/SearchResults.tsx @@ -1,20 +1,26 @@ import React from 'react'; -import {ProfilePreviewType, PreviewType} from '../../types'; +import {ProfilePreviewType, PreviewType, ScreenType} from '../../types'; import ProfilePreview from '../profile/ProfilePreview'; import {StyleSheet, View} from 'react-native'; interface SearchResultsProps { results: Array<ProfilePreviewType>; previewType: PreviewType; + screenType: ScreenType; } -const SearchResults: React.FC<SearchResultsProps> = (props) => { +const SearchResults: React.FC<SearchResultsProps> = ({ + results, + previewType, + screenType, +}) => { return ( <View style={styles.container}> - {props.results.map((profilePreview) => ( + {results.map((profilePreview) => ( <ProfilePreview style={styles.result} key={profilePreview.id} {...{profilePreview}} - previewType={props.previewType} + previewType={previewType} + screenType={screenType} /> ))} </View> diff --git a/src/components/taggs/Tagg.tsx b/src/components/taggs/Tagg.tsx index 9f8fafd1..086b3c87 100644 --- a/src/components/taggs/Tagg.tsx +++ b/src/components/taggs/Tagg.tsx @@ -1,5 +1,5 @@ import {useNavigation} from '@react-navigation/native'; -import React, {Fragment, useContext, useState} from 'react'; +import React, {Fragment, useContext, useEffect, useState} from 'react'; import {Alert, Linking, StyleSheet, TouchableOpacity, View} from 'react-native'; import PurpleRingPlus from '../../assets/icons/purple_ring+.svg'; import PurpleRing from '../../assets/icons/purple_ring.svg'; @@ -17,36 +17,35 @@ import { registerNonIntegratedSocialLink, } from '../../services'; import {SmallSocialIcon, SocialIcon, SocialLinkModal} from '../common'; -import {AuthContext, ProfileContext} from '../../routes'; +import {useSelector} from 'react-redux'; +import {RootState} from '../../store/rootreducer'; +import {ScreenType} from '../../types'; interface TaggProps { social: string; - isProfileView: boolean; isLinked: boolean; isIntegrated: boolean; setTaggsNeedUpdate: (_: boolean) => void; - setSocialDataNeedUpdate: (_: string[]) => void; - userId: string; + setSocialDataNeedUpdate: (_: string) => void; + userXId: string; + screenType: ScreenType; } const Tagg: React.FC<TaggProps> = ({ social, - isProfileView, isLinked, isIntegrated, setTaggsNeedUpdate, setSocialDataNeedUpdate, - userId, + userXId, + screenType, }) => { const navigation = useNavigation(); const [modalVisible, setModalVisible] = useState(false); - const youMayPass = isLinked || isProfileView; - const { - profile: {name}, - socialAccounts, - avatar, - } = isProfileView ? useContext(ProfileContext) : useContext(AuthContext); - + const youMayPass = isLinked || userXId; + const {user} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); /* case isProfileView: case linked: @@ -62,7 +61,7 @@ const Tagg: React.FC<TaggProps> = ({ show auth browser case !integrated_social: show modal - Tagg's "Tagg" will use the Ring instead of PurpleRing + Tagg's "Tagg" will use the Ring instead of PurpleRing */ const modalOrAuthBrowserOrPass = async () => { @@ -70,14 +69,10 @@ const Tagg: React.FC<TaggProps> = ({ if (INTEGRATED_SOCIAL_LIST.indexOf(social) !== -1) { navigation.push('SocialMediaTaggs', { socialMediaType: social, - isProfileView: isProfileView, - userId: userId, - name: name, - accountData: socialAccounts[social], - avatar: avatar, + userXId, }); } else { - getNonIntegratedURL(social, userId).then((socialURL) => { + getNonIntegratedURL(social, user.userId).then((socialURL) => { if (socialURL) { Linking.openURL(socialURL); } else { @@ -89,7 +84,7 @@ const Tagg: React.FC<TaggProps> = ({ if (isIntegrated) { handlePressForAuthBrowser(social).then((success) => { setTaggsNeedUpdate(success); - setSocialDataNeedUpdate(success ? [social] : []); + if (success) setSocialDataNeedUpdate(social); }); } else { setModalVisible(true); @@ -127,7 +122,7 @@ const Tagg: React.FC<TaggProps> = ({ return ( <> - {isProfileView && !isLinked ? ( + {userXId && !isLinked ? ( <Fragment /> ) : ( <> diff --git a/src/components/taggs/TaggsBar.tsx b/src/components/taggs/TaggsBar.tsx index aac68e99..12e4b93a 100644 --- a/src/components/taggs/TaggsBar.tsx +++ b/src/components/taggs/TaggsBar.tsx @@ -1,35 +1,53 @@ -// @refresh react -import React, {useEffect, useState} from 'react'; +import React, {useEffect, useState, useContext} from 'react'; import {StyleSheet} from 'react-native'; import Animated from 'react-native-reanimated'; +import {useDispatch, useSelector} from 'react-redux'; import { INTEGRATED_SOCIAL_LIST, PROFILE_CUTOUT_BOTTOM_Y, SOCIAL_LIST, } from '../../constants'; -import {AuthContext, ProfileContext} from '../../routes'; import {getLinkedSocials} from '../../services'; import {StatusBarHeight} from '../../utils'; import Tagg from './Tagg'; +import {RootState} from '../../store/rootReducer'; +import {ScreenType} from '../../types'; +import {loadIndividualSocial} from '../../store/actions'; const {View, ScrollView, interpolate, Extrapolate} = Animated; interface TaggsBarProps { y: Animated.Value<number>; profileBodyHeight: number; - isProfileView: boolean; + userXId: string; + screenType: ScreenType; } const TaggsBar: React.FC<TaggsBarProps> = ({ y, profileBodyHeight, - isProfileView, + userXId, + screenType, }) => { let [taggs, setTaggs] = useState<Object[]>([]); let [taggsNeedUpdate, setTaggsNeedUpdate] = useState(true); - const context = isProfileView - ? React.useContext(ProfileContext) - : React.useContext(AuthContext); - const {user, socialsNeedUpdate} = context; + const {user} = userXId + ? useSelector((state: RootState) => state.userX[screenType][userXId]) + : useSelector((state: RootState) => state.user); + + const dispatch = useDispatch(); + + /** + * Updates the individual social that needs update + * @param socialType Type of the social that needs update + */ + const handleSocialUpdate = (socialType: string) => { + dispatch(loadIndividualSocial(user.userId, socialType)); + }; + + /** + * This useEffect should be called evey time the user being viewed is changed OR + * And update is triggered manually + */ useEffect(() => { const loadData = async () => { getLinkedSocials(user.userId).then((linkedSocials) => { @@ -43,12 +61,12 @@ const TaggsBar: React.FC<TaggsBarProps> = ({ <Tagg key={i} social={social} - isProfileView={isProfileView} + userXId={userXId} + screenType={screenType} isLinked={true} isIntegrated={INTEGRATED_SOCIAL_LIST.indexOf(social) !== -1} setTaggsNeedUpdate={setTaggsNeedUpdate} - setSocialDataNeedUpdate={socialsNeedUpdate} - userId={user.userId} + setSocialDataNeedUpdate={handleSocialUpdate} />, ); i++; @@ -58,12 +76,12 @@ const TaggsBar: React.FC<TaggsBarProps> = ({ <Tagg key={i} social={social} - isProfileView={isProfileView} + userXId={userXId} + screenType={screenType} isLinked={false} isIntegrated={INTEGRATED_SOCIAL_LIST.indexOf(social) !== -1} setTaggsNeedUpdate={setTaggsNeedUpdate} - setSocialDataNeedUpdate={socialsNeedUpdate} - userId={user.userId} + setSocialDataNeedUpdate={handleSocialUpdate} />, ); i++; @@ -73,17 +91,8 @@ const TaggsBar: React.FC<TaggsBarProps> = ({ }); }; - if (taggsNeedUpdate) { - /** - * Triggering redundant call to the backend for now to make the app work. - * TODO : Figure out a better way to get the updates social posts for the profile being visited. - * Have an event triggered from ProfileProvider based on which we could make a call to backedn to get updated posts. - */ - //We may need the line below in future ? - // socialsNeedUpdate(INTEGRATED_SOCIAL_LIST); - loadData(); - } - }, [isProfileView, taggsNeedUpdate, user.userId]); + loadData(); + }, [taggsNeedUpdate, user]); const shadowOpacity: Animated.Node<number> = interpolate(y, { inputRange: [ |