diff options
author | Ivan Chen <ivan@thetaggid.com> | 2021-02-20 12:27:45 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-20 12:27:45 -0500 |
commit | eae63e4a336785ae45cd01c4448a8444a7793613 (patch) | |
tree | 08aeb9ce90be7ca69b56923bab6c34871b3a793a /src | |
parent | 4b8130932b943afe9fdf63c611f1897622ab795e (diff) | |
parent | 82fc3c7ded1022a31cd532d469457d77f9214cc8 (diff) |
Merge pull request #248 from IvanIFChen/tma258-sp-pagination-2
[TMA-258] SP Pagination
Diffstat (limited to 'src')
-rw-r--r-- | src/components/suggestedPeople/MutualFriends.tsx | 27 | ||||
-rw-r--r-- | src/constants/api.ts | 4 | ||||
-rw-r--r-- | src/constants/constants.ts | 2 | ||||
-rw-r--r-- | src/screens/suggestedPeople/SuggestedPeopleScreen.tsx | 174 | ||||
-rw-r--r-- | src/services/SuggestedPeopleService.ts | 34 | ||||
-rw-r--r-- | src/store/initialStates.ts | 1 | ||||
-rw-r--r-- | src/types/types.ts | 1 |
7 files changed, 174 insertions, 69 deletions
diff --git a/src/components/suggestedPeople/MutualFriends.tsx b/src/components/suggestedPeople/MutualFriends.tsx index f99279c0..fdda104a 100644 --- a/src/components/suggestedPeople/MutualFriends.tsx +++ b/src/components/suggestedPeople/MutualFriends.tsx @@ -1,29 +1,24 @@ import React, {useState} from 'react'; import {SafeAreaView, StyleSheet, Text, View} from 'react-native'; +import {normalize} from 'react-native-elements'; import {ScrollView, TouchableOpacity} from 'react-native-gesture-handler'; -import {useSelector} from 'react-redux'; -import {ScreenType} from '../../types'; import {BottomDrawer, TabsGradient} from '../../components'; -import {RootState} from '../../store/rootReducer'; +import {ProfilePreviewType, ScreenType} from '../../types'; import {isIPhoneX, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; import {ProfilePreview} from '../profile'; -import {normalize} from 'react-native-elements'; -const MutualFriends: React.FC = () => { - // Requires user id of profile being viewed - const userXId = '53a7df9c-c3b2-4b1c-b197-7b1149ecfc8d'; - - // Fetch mutual friends of user X - let {friends} = userXId - ? useSelector((state: RootState) => state.userX[ScreenType.Search][userXId]) - : useSelector((state: RootState) => state.friends); +interface MutualFriendsProps { + user: ProfilePreviewType; + friends: ProfilePreviewType[]; +} +const MutualFriends: React.FC<MutualFriendsProps> = ({ + user, + friends, +}): JSX.Element => { // Getting list of first 4 friends to display on suggested people screen const friendsPreview = friends.slice(0, 4); - // Extract username of user whose profile is being viewed - const username = '@' + '12345678901234'; - // Count to be displayed after + symbol const count = friends.length - friendsPreview.length; @@ -61,7 +56,7 @@ const MutualFriends: React.FC = () => { <View style={styles.headerTextContainer}> <Text style={styles.headerTitle}>Mutual Friends</Text> <Text style={styles.headerDescription} numberOfLines={2}> - {username} and you are both friends with + @{user.username} and you are both friends with </Text> </View> </View> diff --git a/src/constants/api.ts b/src/constants/api.ts index 6e2b28ec..57c26824 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -34,8 +34,8 @@ export const WAITLIST_USER_ENDPOINT: string = API_URL + 'waitlist-user/'; export const COMMENT_THREAD_ENDPOINT: string = API_URL + 'reply/'; // Suggested People -export const SP_UPDATE_PICTURE: string = API_URL + 'suggested_people/update_picture/'; -export const SP_BASE_ENDPOINT: string = API_URL + 'suggested_people/'; +export const SP_USERS_ENDPOINT: string = API_URL + 'suggested_people/'; +export const SP_UPDATE_PICTURE_ENDPOINT: string = API_URL + 'suggested_people/update_picture/'; // Register as FCM device export const FCM_ENDPOINT: string = API_URL + 'fcm/'; diff --git a/src/constants/constants.ts b/src/constants/constants.ts index fbf03744..6e2c9e1c 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -184,3 +184,5 @@ export const EXPLORE_SECTION_TITLES: ExploreSectionType[] = [ export const SP_WIDTH = 375; export const SP_HEIGHT = 812; + +export const SP_PAGE_SIZE = 5; diff --git a/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx b/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx index ba9c36a7..b9dee55a 100644 --- a/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx +++ b/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx @@ -1,22 +1,31 @@ import {useFocusEffect, useNavigation} from '@react-navigation/native'; -import React, {useCallback} from 'react'; +import React, {useCallback, useEffect, useState} from 'react'; import { + FlatList, + ListRenderItemInfo, + RefreshControl, StatusBar, StyleSheet, Text, - TouchableOpacity, View, } from 'react-native'; import {Image} from 'react-native-animatable'; import Animated from 'react-native-reanimated'; -import {SafeAreaView} from 'react-native-safe-area-context'; -import {useSelector} from 'react-redux'; +import {useDispatch, useSelector, useStore} from 'react-redux'; import {TabsGradient, TaggsBar} from '../../components'; import {MutualFriends} from '../../components/suggestedPeople'; +import {SP_PAGE_SIZE} from '../../constants'; import SuggestedPeopleOnboardingStackScreen from '../../routes/suggestedPeopleOnboarding/SuggestedPeopleOnboardingStackScreen'; +import {getSuggestedPeople} from '../../services/SuggestedPeopleService'; +import {resetScreenType} from '../../store/actions'; import {RootState} from '../../store/rootReducer'; -import {ScreenType} from '../../types'; -import {isIPhoneX, normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import { + ProfilePreviewType, + ScreenType, + SuggestedPeopleDataType, +} from '../../types'; +import {fetchUserX, normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import {userXInStore} from './../../utils/'; /** * Bare bones for suggested people consisting of: @@ -24,17 +33,64 @@ import {isIPhoneX, normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; */ const SuggestedPeopleScreen: React.FC = () => { + const y = Animated.useValue(0); + const navigation = useNavigation(); + const state: RootState = useStore().getState(); + const dispatch = useDispatch(); + const screenType = ScreenType.SuggestedPeople; const {suggested_people_linked} = useSelector( (state: RootState) => state.user.profile, ) ?? {suggested_people_linked: -1}; - const y = Animated.useValue(0); + const [people, setPeople] = useState<SuggestedPeopleDataType[]>([]); + const [page, setPage] = useState(0); + const [refreshing, setRefreshing] = useState(false); + const [hideStatusBar, setHideStatusBar] = useState(false); - // Can be removed once firstname, username props are received - const firstName = 'Sarah'; + // loads data and append it to users based on current page + useEffect(() => { + // console.log('current page', page); + loadMore(false); + }, [page]); - // Adviced to maintain username as a variable here to append @ symbol for maintainability - const username = '@' + 'sarahmiller'; - const navigation = useNavigation(); + const loadMore = (resetData: boolean) => { + const loadNextPage = async () => + await getSuggestedPeople(SP_PAGE_SIZE, page * SP_PAGE_SIZE); + loadNextPage().then((newUsers) => { + if (resetData) { + setPeople([]); + setPage(0); + } + loadUserDataToStore(newUsers.map((ppl) => ppl.user)); + setPeople(people.concat(newUsers)); + }); + }; + + const loadUserDataToStore = async (users: ProfilePreviewType[]) => { + const loadUserData = async (user: ProfilePreviewType) => { + if (!userXInStore(state, screenType, user.id)) { + await fetchUserX( + dispatch, + {userId: user.id, username: user.username}, + screenType, + ); + } + }; + await Promise.all(users.map((user) => loadUserData(user))); + }; + const resetPage = () => { + const reset = async () => { + await dispatch(resetScreenType(screenType)); + loadMore(true); + }; + setRefreshing(true); + reset().then(() => { + setRefreshing(false); + }); + }; + + const onRefresh = useCallback(() => { + resetPage(); + }, []); useFocusEffect( useCallback(() => { @@ -48,60 +104,88 @@ const SuggestedPeopleScreen: React.FC = () => { }, [navigation, suggested_people_linked]), ); - const mainContent = () => ( - <SafeAreaView> - <StatusBar barStyle={'light-content'} /> - <Image - // !!! Displaying Sarah Miller's image - source={require('../../assets/images/sarah_miller_full.jpeg')} - style={styles.image} - /> - <View style={styles.mainContainer}> - <Text style={styles.title}>Suggested People</Text> - <View style={styles.body}> - <View style={styles.marginManager}> + // const onViewableItemsChanged = useCallback( + // ({viewableItems}: {viewableItems: ViewToken[]}) => { + // setHideStatusBar(viewableItems[0].index !== 0); + // }, + // [], + // ); + + const SPBody = ({ + item, + }: { + item: ListRenderItemInfo<SuggestedPeopleDataType>; + }) => { + const data = item.item; + const firstItem = item.index === 0; + return ( + <> + <StatusBar barStyle={'light-content'} hidden={hideStatusBar} /> + <Image + source={{ + uri: data.suggested_people_url, + }} + style={styles.image} + /> + <View style={styles.mainContainer}> + <Text style={styles.title}>{firstItem && 'Suggested People'}</Text> + <View style={styles.body}> <View style={styles.addUserContainer}> <View style={styles.nameInfoContainer}> - <Text style={styles.firstName}>{firstName}</Text> - <Text style={styles.username}>{username}</Text> + <Text style={styles.firstName}>{data.user.first_name}</Text> + <Text style={styles.username}>{'@' + data.user.username}</Text> </View> - <TouchableOpacity + {/* TODO: Finish me ?! */} + {/* <TouchableOpacity activeOpacity={0.5} + // TODO: Call function to Add Friend onPress={() => console.log('Call add friend function')}> <View style={styles.addButton}> <Text style={styles.addButtonTitle}>{'Add Friend'}</Text> </View> - </TouchableOpacity> + </TouchableOpacity> */} </View> - </View> - <TaggsBar - y={y} - userXId={undefined} - profileBodyHeight={0} - screenType={ScreenType.SuggestedPeople} - whiteRing={true} - /> - <View style={styles.marginManager}> - <MutualFriends /> + <TaggsBar + y={y} + userXId={data.user.id} + profileBodyHeight={0} + screenType={ScreenType.SuggestedPeople} + /> + <MutualFriends user={data.user} friends={data.mutual_friends} /> </View> </View> - </View> - <TabsGradient /> - </SafeAreaView> - ); + </> + ); + }; return suggested_people_linked === 0 ? ( <SuggestedPeopleOnboardingStackScreen /> ) : ( - mainContent() + <> + <FlatList + data={people} + renderItem={(item) => <SPBody item={item} />} + keyExtractor={(item, index) => index.toString()} + showsVerticalScrollIndicator={false} + onEndReached={() => setPage(page + 1)} + // onViewableItemsChanged={onViewableItemsChanged} + refreshControl={ + <RefreshControl refreshing={refreshing} onRefresh={onRefresh} /> + } + pagingEnabled + /> + <TabsGradient /> + </> ); }; const styles = StyleSheet.create({ mainContainer: { flexDirection: 'column', - width: SCREEN_WIDTH, - height: isIPhoneX() ? SCREEN_HEIGHT * 0.85 : SCREEN_HEIGHT * 0.88, + width: SCREEN_WIDTH * 0.9, + height: SCREEN_HEIGHT, + paddingVertical: '15%', + paddingBottom: '20%', justifyContent: 'space-between', alignSelf: 'center', }, diff --git a/src/services/SuggestedPeopleService.ts b/src/services/SuggestedPeopleService.ts index 525c68b1..34a31662 100644 --- a/src/services/SuggestedPeopleService.ts +++ b/src/services/SuggestedPeopleService.ts @@ -1,10 +1,10 @@ import AsyncStorage from '@react-native-community/async-storage'; import { EDIT_PROFILE_ENDPOINT, - SP_BASE_ENDPOINT, - SP_UPDATE_PICTURE, + SP_UPDATE_PICTURE_ENDPOINT, + SP_USERS_ENDPOINT } from '../constants'; -import {SuggestedPeopleDataType} from '../types'; +import { SuggestedPeopleDataType } from '../types'; export const sendSuggestedPeopleLinked = async ( userId: string, @@ -39,7 +39,7 @@ export const sendSuggestedPeoplePhoto = async (photoUri: string) => { name: 'sp_photo.jpg', type: 'image/jpg', }); - const response = await fetch(SP_UPDATE_PICTURE, { + const response = await fetch(SP_UPDATE_PICTURE_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'multipart/form-data', @@ -54,10 +54,33 @@ export const sendSuggestedPeoplePhoto = async (photoUri: string) => { } }; +export const getSuggestedPeople = async (limit: number, offset: number) => { + try { + const token = await AsyncStorage.getItem('token'); + const url = `${SP_USERS_ENDPOINT}?limit=${limit}&offset=${offset}`; + const response = await fetch(url, { + method: 'GET', + headers: { + Authorization: 'Token ' + token, + }, + }); + if (response.status !== 200) { + throw 'Non-200 response'; + } + const data = await response.json(); + const typedData: SuggestedPeopleDataType[] = data.results; + return typedData; + } catch (error) { + console.log('Error fetching SP user data'); + console.log(error); + return []; + } +}; + export const getSuggestedPeopleProfile = async (userId: string) => { try { const token = await AsyncStorage.getItem('token'); - const response = await fetch(SP_BASE_ENDPOINT + userId + '/', { + const response = await fetch(SP_USERS_ENDPOINT + userId + '/', { method: 'GET', headers: { Authorization: 'Token ' + token, @@ -73,4 +96,3 @@ export const getSuggestedPeopleProfile = async (userId: string) => { console.log('Error retrieving SP info'); return undefined; } -}; diff --git a/src/store/initialStates.ts b/src/store/initialStates.ts index 10fdad25..408de39e 100644 --- a/src/store/initialStates.ts +++ b/src/store/initialStates.ts @@ -128,6 +128,7 @@ export const EMPTY_SCREEN_TO_USERS_LIST: Record< [ScreenType.Profile]: EMPTY_USERX_LIST, [ScreenType.Search]: EMPTY_USERX_LIST, [ScreenType.Notifications]: EMPTY_USERX_LIST, + [ScreenType.SuggestedPeople]: EMPTY_USERX_LIST, }; export const INITIAL_CATEGORIES_STATE = { diff --git a/src/types/types.ts b/src/types/types.ts index 8e9e8a60..97471171 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -207,6 +207,7 @@ export type NotificationType = { }; export type TypeOfComment = 'Comment' | 'Thread'; + export type TypeOfNotification = // notification_object is undefined | 'DFT' |