diff options
-rw-r--r-- | src/components/profile/ProfilePreview.tsx | 35 | ||||
-rw-r--r-- | src/components/search/RecentSearches.tsx | 1 | ||||
-rw-r--r-- | src/components/search/SearchResultCell.tsx | 87 | ||||
-rw-r--r-- | src/components/search/SearchResultList.tsx | 82 | ||||
-rw-r--r-- | src/constants/api.ts | 3 | ||||
-rw-r--r-- | src/constants/strings.ts | 1 | ||||
-rw-r--r-- | src/screens/search/SearchScreen.tsx | 114 | ||||
-rw-r--r-- | src/services/SearchService.ts | 22 | ||||
-rw-r--r-- | src/services/index.ts | 1 | ||||
-rw-r--r-- | src/utils/users.ts | 32 |
10 files changed, 249 insertions, 129 deletions
diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx index 0021b1c6..f08335a1 100644 --- a/src/components/profile/ProfilePreview.tsx +++ b/src/components/profile/ProfilePreview.tsx @@ -16,6 +16,7 @@ import {loadImageFromURL} from '../../services'; import {RootState} from '../../store/rootreducer'; import {PreviewType, ProfilePreviewType, ScreenType} from '../../types'; import { + addUserToRecentlyViewed, checkIfUserIsBlocked, fetchUserX, isIPhoneX, @@ -89,39 +90,7 @@ const ProfilePreview: React.FC<ProfilePreviewProps> = ({ return; } if (previewType !== 'Comment') { - const jsonValue = await AsyncStorage.getItem( - '@recently_searched_users', - ); - let recentlySearchedList = - jsonValue != null ? JSON.parse(jsonValue) : null; - if (recentlySearchedList) { - if (recentlySearchedList.length > 0) { - if ( - recentlySearchedList.some( - (saved_user: ProfilePreviewType) => saved_user.id === id, - ) - ) { - console.log('User already in recently searched.'); - } else { - if (recentlySearchedList.length >= 10) { - recentlySearchedList.pop(); - } - recentlySearchedList.unshift(user); - } - } - } else { - recentlySearchedList = [user]; - } - - try { - let recentlySearchedListString = JSON.stringify(recentlySearchedList); - await AsyncStorage.setItem( - '@recently_searched_users', - recentlySearchedListString, - ); - } catch (e) { - console.log(e); - } + await addUserToRecentlyViewed(user) } const userXId = diff --git a/src/components/search/RecentSearches.tsx b/src/components/search/RecentSearches.tsx index 43a26514..3925d084 100644 --- a/src/components/search/RecentSearches.tsx +++ b/src/components/search/RecentSearches.tsx @@ -54,7 +54,6 @@ const styles = StyleSheet.create({ marginBottom: '5%', }, clear: { - fontSize: 18, fontWeight: 'bold', color: TAGG_LIGHT_BLUE, diff --git a/src/components/search/SearchResultCell.tsx b/src/components/search/SearchResultCell.tsx index 084c6afe..705fb5c9 100644 --- a/src/components/search/SearchResultCell.tsx +++ b/src/components/search/SearchResultCell.tsx @@ -1,12 +1,24 @@ +import {useNavigation} from '@react-navigation/native'; import React, {useEffect, useState} from 'react'; -import {Image, StyleSheet, Text, View} from 'react-native'; +import {Alert, Image, StyleSheet, Text, View} from 'react-native'; +import {TouchableOpacity} from 'react-native-gesture-handler'; +import {useDispatch, useStore} from 'react-redux'; +import {ERROR_UNABLE_TO_VIEW_PROFILE} from '../../constants/strings'; import {loadImageFromURL} from '../../services'; -import {ProfilePreviewType} from '../../types'; +import {RootState} from '../../store/rootReducer'; +import {ProfilePreviewType, ScreenType, UserType} from '../../types'; import {normalize, SCREEN_WIDTH} from '../../utils'; -import {defaultUserProfile} from '../../utils/users'; +import { + addUserToRecentlyViewed, + checkIfUserIsBlocked, + defaultUserProfile, + fetchUserX, + userXInStore, +} from '../../utils/users'; interface SearchResults { profileData: ProfilePreviewType; + loggedInUser: UserType; } const SearchResultsCell: React.FC<SearchResults> = ({ @@ -19,8 +31,9 @@ const SearchResultsCell: React.FC<SearchResults> = ({ thumbnail_url, category, }, + loggedInUser, }) => { - const [avatar, setAvatar] = useState(''); + const [avatar, setAvatar] = useState<string | undefined>(undefined); useEffect(() => { (async () => { if (thumbnail_url !== undefined) { @@ -37,9 +50,60 @@ const SearchResultsCell: React.FC<SearchResults> = ({ })(); }, [thumbnail_url]); + const dispatch = useDispatch(); + const state: RootState = useStore().getState(); + const navigation = useNavigation(); + const addToRecentlyStoredAndNavigateToProfile = async () => { + try { + //If the logged in user is blocked by the user being viewed, do not proceed. + const isUserBlocked = await checkIfUserIsBlocked( + id, + dispatch, + loggedInUser, + ); + if (isUserBlocked) { + Alert.alert(ERROR_UNABLE_TO_VIEW_PROFILE); + return; + } + + await addUserToRecentlyViewed({ + id, + first_name, + last_name, + thumbnail_url, + username, + }); + + const userXId = loggedInUser.username === username ? undefined : id; + + /** + * Dispatch an event to Fetch the user details only if we're navigating to + * a userX's profile. + * If the user is already present in store, do not fetch again. + * Finally, Navigate to profile of the user selected. + */ + if (userXId && !userXInStore(state, ScreenType.Search, id)) { + await fetchUserX( + dispatch, + {userId: id, username: username}, + ScreenType.Search, + ); + } + + navigation.navigate('Profile', { + userXId, + screenType: ScreenType.Search, + }); + } catch (e) { + console.log(e); + } + }; + const userCell = () => { return ( - <View style={styles.cellContainer}> + <TouchableOpacity + onPress={addToRecentlyStoredAndNavigateToProfile} + style={styles.cellContainer}> <Image defaultSource={defaultUserProfile()} source={{uri: avatar}} @@ -51,7 +115,7 @@ const SearchResultsCell: React.FC<SearchResults> = ({ {first_name + ' ' + last_name} </Text> </View> - </View> + </TouchableOpacity> ); }; @@ -65,7 +129,7 @@ const SearchResultsCell: React.FC<SearchResults> = ({ const categoryCell = () => { return ( - <View style={styles.cellContainer}> + <TouchableOpacity style={styles.cellContainer}> <View style={[styles.imageContainer, styles.categoryBackground]}> <Image resizeMode="contain" @@ -76,16 +140,11 @@ const SearchResultsCell: React.FC<SearchResults> = ({ <View style={styles.initialTextContainer}> <Text style={styles.initialTextStyle}>{name}</Text> </View> - </View> + </TouchableOpacity> ); }; - return ( - <> - {name !== undefined && categoryCell()} - {name === undefined && userCell()} - </> - ); + return name === undefined ? userCell() : categoryCell(); }; const styles = StyleSheet.create({ diff --git a/src/components/search/SearchResultList.tsx b/src/components/search/SearchResultList.tsx index bf08e205..7f8073c4 100644 --- a/src/components/search/SearchResultList.tsx +++ b/src/components/search/SearchResultList.tsx @@ -1,16 +1,20 @@ -import React, { useState } from 'react'; +import React, {useEffect, useState} from 'react'; import { + Keyboard, KeyboardAvoidingView, SectionList, StyleSheet, - View + Text, + View, } from 'react-native'; -import { PreviewType, ScreenType } from '../../types'; -import { normalize, SCREEN_HEIGHT } from '../../utils'; +import {useSelector} from 'react-redux'; +import {RootState} from 'src/store/rootreducer'; +import {PreviewType, ScreenType} from '../../types'; +import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; import SearchResultsCell from './SearchResultCell'; - +import {NO_RESULTS_FOUND} from '../../constants/strings'; interface SearchResultsProps { - results: []; + results: Array<any> | undefined; previewType: PreviewType; screenType: ScreenType; } @@ -24,34 +28,66 @@ const sectionHeader: React.FC<Boolean> = (showBorder: Boolean) => { const SearchResultList: React.FC<SearchResultsProps> = ({results}) => { const [showSection, setShowSection] = useState(true); + const [showEmptyView, setshowEmptyView] = useState(false); + const {user: loggedInUser} = useSelector((state: RootState) => state.user); + + useEffect(() => { + if (results && results.length > 0) { + setshowEmptyView( + results[0].data.length === 0 && results[1].data.length === 0, + ); + } + }, [results]); + return ( - <KeyboardAvoidingView style={styles.container} behavior="padding"> - <SectionList - style={styles.container} - showsVerticalScrollIndicator={false} - sections={results} - keyExtractor={(item, index) => item + index} - renderItem={({item}) => <SearchResultsCell profileData={item} />} - renderSectionHeader={({section: {title, data}}) => { - if (title === 'categories' && data.length === 0) { - setShowSection(false); - } - return sectionHeader(title !== 'categories' && showSection); - }} - /> - <View style={{height: SCREEN_HEIGHT * 0.35}} /> - </KeyboardAvoidingView> + <View style={styles.container}> + {showEmptyView && ( + <View style={styles.noResultsTextContainer}> + <Text style={styles.noResultsTextStyle}>{NO_RESULTS_FOUND}</Text> + </View> + )} + {!showEmptyView && ( + <SectionList + style={{height: SCREEN_HEIGHT, width: SCREEN_WIDTH}} + showsVerticalScrollIndicator={false} + sections={results} + keyExtractor={(item, index) => item.id + index} + renderItem={({item}) => ( + <SearchResultsCell profileData={item} loggedInUser={loggedInUser} /> + )} + renderSectionHeader={({section: {title, data}}) => { + if (title === 'categories' && data.length === 0) { + setShowSection(false); + } + return sectionHeader(title !== 'categories' && showSection); + }} + /> + )} + </View> ); }; const styles = StyleSheet.create({ - container: {marginTop: SCREEN_HEIGHT * 0.02}, + container: { + marginTop: SCREEN_HEIGHT * 0.04, + }, sectionHeaderStyle: { width: '100%', height: 0.5, marginBottom: normalize(24), backgroundColor: '#C4C4C4', }, + keyboardOpen: {marginBottom: SCREEN_HEIGHT * 0.3}, + keyboardClose: {marginBottom: 20}, + noResultsTextContainer: { + justifyContent: 'center', + flexDirection: 'row', + width: SCREEN_WIDTH, + }, + noResultsTextStyle: { + fontWeight: '500', + fontSize: normalize(14), + }, }); export default SearchResultList; diff --git a/src/constants/api.ts b/src/constants/api.ts index 5e23ac7e..1463683f 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -17,8 +17,7 @@ export const PROFILE_PHOTO_THUMBNAIL_ENDPOINT: string = export const GET_IG_POSTS_ENDPOINT: string = API_URL + 'posts-ig/'; export const GET_FB_POSTS_ENDPOINT: string = API_URL + 'posts-fb/'; export const GET_TWITTER_POSTS_ENDPOINT: string = API_URL + 'posts-twitter/'; -export const SEARCH_ENDPOINT: string = API_URL + 'search/'; -export const SEARCH_V2_ENDPOINT: string = API_URL + 'search/v2/'; +export const SEARCH_ENDPOINT: string = API_URL + 'search/v2/'; export const MOMENTS_ENDPOINT: string = API_URL + 'moments/'; export const MOMENT_THUMBNAIL_ENDPOINT: string = API_URL + 'moment-thumbnail/'; export const VERIFY_INVITATION_CODE_ENDPOUNT: string = API_URL + 'verify-code/'; diff --git a/src/constants/strings.ts b/src/constants/strings.ts index 104cc198..7652fccd 100644 --- a/src/constants/strings.ts +++ b/src/constants/strings.ts @@ -49,6 +49,7 @@ export const ERROR_VERIFICATION_FAILED_SHORT = 'Verification failed 😓'; export const MARKED_AS_MSG = (str: string) => `Marked as ${str}`; export const MOMENT_DELETED_MSG = 'Moment deleted....Some moments have to go, to create space for greater ones'; export const NO_NEW_NOTIFICATIONS = 'You have no new notifications'; +export const NO_RESULTS_FOUND = 'No Results Found!'; export const SUCCESS_CATEGORY_DELETE = 'Category successfully deleted, but its memory will live on'; export const SUCCESS_LINK = (str: string) => `Successfully linked ${str} 🎉`; export const SUCCESS_PIC_UPLOAD = 'Beautiful, the picture was uploaded successfully!'; diff --git a/src/screens/search/SearchScreen.tsx b/src/screens/search/SearchScreen.tsx index a66a2cbc..b6841480 100644 --- a/src/screens/search/SearchScreen.tsx +++ b/src/screens/search/SearchScreen.tsx @@ -3,10 +3,12 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useEffect, useState} from 'react'; import { Keyboard, + KeyboardAvoidingView, RefreshControl, ScrollView, StatusBar, StyleSheet, + SafeAreaView, } from 'react-native'; import Animated, {Easing, timing} from 'react-native-reanimated'; import {useDispatch, useSelector} from 'react-redux'; @@ -20,11 +22,12 @@ import { SearchResultsBackground, TabsGradient, } from '../../components'; -import {SEARCH_V2_ENDPOINT, TAGG_LIGHT_BLUE} from '../../constants'; +import {SEARCH_ENDPOINT, TAGG_LIGHT_BLUE} from '../../constants'; import {loadRecentlySearched, resetScreenType} from '../../store/actions'; import {RootState} from '../../store/rootReducer'; import {ProfilePreviewType, ScreenType, UserType} from '../../types'; import {SCREEN_HEIGHT, SCREEN_WIDTH, StatusBarHeight} from '../../utils'; +import {loadSearchResults} from '../../services'; const NO_USER: UserType = { userId: '', username: '', @@ -38,14 +41,27 @@ const NO_USER: UserType = { const SearchScreen: React.FC = () => { const {recentSearches} = useSelector((state: RootState) => state.taggUsers); const [query, setQuery] = useState<string>(''); - const [results, setResults] = useState(Array<any>()); + const [results, setResults] = useState<Array<any> | undefined>(undefined); const [recents, setRecents] = useState<Array<ProfilePreviewType>>( recentSearches ?? [], ); const [searching, setSearching] = useState(false); const top = Animated.useValue(-SCREEN_HEIGHT); const [refreshing, setRefreshing] = useState<boolean>(false); + const [keyboardVisible, setKeyboardVisible] = React.useState( + 'keyboardVisible', + ); + useEffect(() => { + const showKeyboard = () => setKeyboardVisible('keyboardVisibleTrue'); + Keyboard.addListener('keyboardWillShow', showKeyboard); + return () => Keyboard.removeListener('keyboardWillShow', showKeyboard); + }, []); + useEffect(() => { + const hideKeyboard = () => setKeyboardVisible('keyboardVisibleFalse'); + Keyboard.addListener('keyboardWillHide', hideKeyboard); + return () => Keyboard.removeListener('keyboardWillHide', hideKeyboard); + }, []); const dispatch = useDispatch(); const onRefresh = useCallback(() => { @@ -60,48 +76,31 @@ const SearchScreen: React.FC = () => { useEffect(() => { if (query.length < 3) { - setResults([]); + setResults(undefined); return; } - const loadResults = async (q: string) => { - try { - const token = await AsyncStorage.getItem('token'); - const response = await fetch(`${SEARCH_V2_ENDPOINT}?query=${q}`, { - method: 'GET', - headers: { - Authorization: 'Token ' + token, + (async () => { + const searchResults = await loadSearchResults( + `${SEARCH_ENDPOINT}?query=${query}`, + ); + if (query.length > 2) { + const categories = searchResults?.categories; + const users = searchResults?.users; + const sanitizedResult = [ + { + title: 'categories', + data: categories, }, - }); - const {status} = response; - if (status === 200) { - const searchResults = await response.json(); - const sanitizedResult = [ - { - title: 'categories', - data: searchResults.categories, - }, - { - title: 'users', - data: searchResults.users, - }, - ]; - - if ( - searchResults.categories.length !== 0 || - searchResults.users.length !== 0 - ) { - if (query.length > 3) { - setResults(sanitizedResult); - return; - } - } - } - } catch (error) { - console.log(error); + { + title: 'users', + data: users, + }, + ]; + setResults(sanitizedResult); + } else { + setResults(undefined); } - setResults([]); - }; - loadResults(query); + })(); }, [query]); /** @@ -179,24 +178,27 @@ const SearchScreen: React.FC = () => { <Explore /> <SearchResultsBackground {...{top}}> - {results && results.length === 0 ? ( - <RecentSearches - sectionTitle="Recent" - sectionButtonTitle="Clear all" - onPress={clearRecentlySearched} - recents={recents} - screenType={ScreenType.Search} - /> - ) : ( - <SearchResultList - {...{results}} - previewType={'Search'} - screenType={ScreenType.Search} - /> - )} + <KeyboardAvoidingView + behavior={'padding'} + keyboardVerticalOffset={SCREEN_HEIGHT * 0.1}> + {results === undefined && recents.length !== 0 ? ( + <RecentSearches + sectionTitle="Recent" + sectionButtonTitle="Clear all" + onPress={clearRecentlySearched} + recents={recents} + screenType={ScreenType.Search} + /> + ) : ( + <SearchResultList + {...{results}} + previewType={'Search'} + screenType={ScreenType.Search} + /> + )} + </KeyboardAvoidingView> </SearchResultsBackground> </ScrollView> - <TabsGradient /> </SearchBackground> ); diff --git a/src/services/SearchService.ts b/src/services/SearchService.ts new file mode 100644 index 00000000..7b97f9a7 --- /dev/null +++ b/src/services/SearchService.ts @@ -0,0 +1,22 @@ +import AsyncStorage from '@react-native-community/async-storage'; + +export const loadSearchResults = async (url: string) => { + try { + const token = await AsyncStorage.getItem('token'); + const response = await fetch(url, { + method: 'GET', + headers: { + Authorization: 'Token ' + token, + }, + }); + const {status} = response; + if (status === 200) { + const searchResults = await response.json(); + return searchResults; + } + } catch (error) { + console.log(error); + throw error; + } + return {}; +}; diff --git a/src/services/index.ts b/src/services/index.ts index ef71233a..28e03e0e 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -12,3 +12,4 @@ export * from './WaitlistUserService'; export * from './CommonService'; export * from './CommentService'; export * from './SuggestedPeopleService'; +export * from './SearchService'; diff --git a/src/utils/users.ts b/src/utils/users.ts index b8faf206..653c941e 100644 --- a/src/utils/users.ts +++ b/src/utils/users.ts @@ -164,3 +164,35 @@ export const defaultUserProfile = () => { const defaultImage = require('../assets/images/avatar-placeholder.png'); return defaultImage; }; + +export const addUserToRecentlyViewed = async (user: ProfilePreviewType) => { + const jsonValue = await AsyncStorage.getItem('@recently_searched_users'); + let recentlySearchedList = jsonValue != null ? JSON.parse(jsonValue) : null; + if (recentlySearchedList) { + if (recentlySearchedList.length > 0) { + if ( + recentlySearchedList.some( + (saved_user: ProfilePreviewType) => saved_user.id === user.id, + ) + ) { + console.log('User already in recently searched.'); + } else { + if (recentlySearchedList.length >= 10) { + recentlySearchedList.pop(); + } + recentlySearchedList.unshift(user); + } + } + } else { + recentlySearchedList = [user]; + } + try { + let recentlySearchedListString = JSON.stringify(recentlySearchedList); + await AsyncStorage.setItem( + '@recently_searched_users', + recentlySearchedListString, + ); + } catch (e) { + console.log(e); + } +}; |