import React, {useCallback, useEffect, useState} from 'react'; import { Alert, LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, RefreshControl, StyleSheet, Text, View, } from 'react-native'; import Animated from 'react-native-reanimated'; import { CategorySelectionScreenType, FriendshipStatusType, MomentCategoryType, MomentType, ProfilePreviewType, ProfileType, ScreenType, UserType, } from '../../types'; import {COVER_HEIGHT, TAGG_TEXT_LIGHT_BLUE} from '../../constants'; import { fetchUserX, getUserAsProfilePreviewType, moveCategory, SCREEN_HEIGHT, userLogin, } from '../../utils'; import TaggsBar from '../taggs/TaggsBar'; import {Moment} from '../moments'; 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; userXId: string | undefined; screenType: ScreenType; } const Content: React.FC = ({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 {friends = EMPTY_PROFILE_PREVIEW_LIST} = userXId ? useSelector((state: RootState) => state.userX[screenType][userXId]) : useSelector((state: RootState) => state.friends); const { friends: friendsLoggedInUser = EMPTY_PROFILE_PREVIEW_LIST, } = useSelector((state: RootState) => state.friends); const {moments = EMPTY_MOMENTS_LIST} = userXId ? useSelector((state: RootState) => state.userX[screenType][userXId]) : useSelector((state: RootState) => state.moments); const {momentCategories = []} = userXId ? useSelector((state: RootState) => state.userX[screenType][userXId]) : useSelector((state: RootState) => state.momentCategories); 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(); const navigation = useNavigation(); /** * States */ const [imagesMap, setImagesMap] = useState>( new Map(), ); const [isFriend, setIsFriend] = useState(false); const [isBlocked, setIsBlocked] = useState(false); const [profileBodyHeight, setProfileBodyHeight] = useState(0); const [shouldBounce, setShouldBounce] = useState(true); const [refreshing, setRefreshing] = useState(false); //These two booleans are used to see if user closed the pormpt displayed to them const [isStageTwoPromptClosed, setIsStageTwoPromptClosed] = useState( false, ); const [isStageThreePromptClosed, setIsStageThreePromptClosed] = useState< boolean >(false); const onRefresh = useCallback(() => { const refrestState = async () => { if (!userXId) { await userLogin(dispatch, loggedInUser); } else { await fetchUserX(dispatch, user, screenType); } }; setRefreshing(true); refrestState().then(() => { setRefreshing(false); }); }, []); const onLayout = (e: LayoutChangeEvent) => { const {height} = e.nativeEvent.layout; setProfileBodyHeight(height); }; const createImagesMap = useCallback(() => { var map = new Map(); moments.forEach(function (imageObject) { var moment_category = imageObject.moment_category; if (map.has(moment_category)) { map.get(moment_category).push(imageObject); } else { map.set(moment_category, [imageObject]); } }); setImagesMap(map); }, [moments]); useEffect(() => { createImagesMap(); }, [createImagesMap]); const move = (direction: 'up' | 'down', title: string) => { let categories = [...momentCategories]; categories = moveCategory(categories, title, direction === 'up'); dispatch(updateMomentCategories(categories)); }; /** * Prompt user to perform an activity based on their profile completion stage * To fire 2 seconds after the screen comes in focus * 1 means STAGE_1: * The user must upload a moment, so take them to a screen guiding them to post a moment * 2 means STAGE_2: * The user must create another category so show a prompt on top of the screen * 3 means STAGE_3: * The user must upload a moment to the second category, so show a prompt on top of the screen * Else, profile is complete and no prompt needs to be shown */ useFocusEffect( useCallback(() => { const navigateToMomentUploadPrompt = () => { switch (profile.profile_completion_stage) { case 1: if (momentCategories && momentCategories[0]) { navigation.navigate('MomentUploadPrompt', { screenType, momentCategory: momentCategories[0], }); } break; case 2: setIsStageTwoPromptClosed(false); break; case 3: setIsStageThreePromptClosed(false); break; default: break; } }; setTimeout(navigateToMomentUploadPrompt, 2000); }, [profile.profile_completion_stage, momentCategories]), ); useEffect(() => { const isActuallyBlocked = blockedUsers.some( (cur_user) => user.username === cur_user.username, ); if (isBlocked != isActuallyBlocked) { setIsBlocked(isActuallyBlocked); } }, [blockedUsers, user]); // Handles click on friend/requested/unfriend button /* * When user logged in clicks on the friend button: A request is sent. Which means you have to update the status of their friendshpi to requested When the status is changed to requested the button should change to requested. When the button is changed to requested and thr user clicks on it, a request much go to the backend to delete that request When that succeeds, their friendship must be updated to no-record again; When the button is changed to no_record, the add friends button should be displayed again */ const handleFriendUnfriend = async () => { const {friendship_status} = profile; await dispatch( friendUnfriendUser( loggedInUser, getUserAsProfilePreviewType(user, profile), friendship_status, screenType, ), ); await dispatch(updateUserXFriends(user.userId, state)); dispatch(updateUserXProfileAllScreens(user.userId, state)); }; /** * Handles a click on the block / unblock button. * loadFriends updates friends list for the logged in user * updateUserXFriends updates friends list for the user. */ const handleBlockUnblock = async (callback?: () => void) => { await dispatch( blockUnblockUser( loggedInUser, getUserAsProfilePreviewType(user, profile), isBlocked, ), ); await dispatch(loadFriendsData(loggedInUser.userId)); await dispatch(updateUserXFriends(user.userId, state)); if (callback) { callback(); } }; /** * Handle deletion of a category * Confirm with user before deleting the category * @param category category to be deleted */ const handleCategoryDeletion = (category: string) => { Alert.alert( 'Category Deletion', `Are you sure that you want to delete the category ${category} ?`, [ { text: 'Cancel', style: 'cancel', }, { text: 'Yes', onPress: () => { dispatch( updateMomentCategories( momentCategories.filter((mc) => mc !== category), false, ), ); dispatch(deleteUserMomentsForCategory(category)); }, }, ], {cancelable: true}, ); }; const handleScroll = (e: NativeSyntheticEvent) => { /** * Set the new y position */ const newY = e.nativeEvent.contentOffset.y; y.setValue(newY); /** * Do not allow overflow of scroll on bottom of the screen * SCREEN_HEIGHT - COVER_HEIGHT = Height of the scroll view */ if (newY >= SCREEN_HEIGHT - COVER_HEIGHT) { setShouldBounce(false); } else if (newY === 0) { setShouldBounce(true); } }; return ( handleScroll(e)} bounces={shouldBounce} showsVerticalScrollIndicator={false} scrollEventThrottle={1} stickyHeaderIndices={[4]} refreshControl={ }> {userXId && moments.length === 0 && ( {`Looks like ${ profile.name.split(' ')[0] } has not posted any moments yet`} )} {!userXId && profile.profile_completion_stage === 2 && !isStageTwoPromptClosed && ( { setIsStageTwoPromptClosed(true); }} /> )} {!userXId && profile.profile_completion_stage === 3 && !isStageThreePromptClosed && ( { setIsStageThreePromptClosed(true); }} /> )} {momentCategories.map( (title, index) => (!userXId || imagesMap.get(title)) && ( 1} showUpButton={index !== 0} showDownButton={index !== momentCategories.length - 1} move={move} /> ), )} {!userXId && profile.profile_completion_stage !== 1 && ( navigation.push('CategorySelection', { screenType: CategorySelectionScreenType.Profile, user: loggedInUser, }) } style={styles.createCategoryButton}> Create a new category )} ); }; const styles = StyleSheet.create({ container: { flex: 1, }, momentsContainer: { backgroundColor: '#f2f2f2', paddingBottom: SCREEN_HEIGHT / 10, flex: 1, flexDirection: 'column', }, createCategoryButton: { backgroundColor: TAGG_TEXT_LIGHT_BLUE, justifyContent: 'center', alignItems: 'center', width: '70%', height: 30, marginTop: '15%', alignSelf: 'center', }, createCategoryButtonLabel: { fontSize: 16, fontWeight: '500', color: 'white', }, plusIconContainer: { flexDirection: 'column', justifyContent: 'center', alignItems: 'center', marginVertical: '10%', }, noMomentsText: { fontSize: 14, fontWeight: 'bold', color: 'gray', marginVertical: '8%', }, }); export default Content;