aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/components/common/TabsGradient.tsx23
-rw-r--r--src/components/common/index.ts1
-rw-r--r--src/components/profile/Content.tsx17
-rw-r--r--src/components/search/Explore.tsx35
-rw-r--r--src/components/search/ExploreSection.tsx (renamed from src/components/search/SuggestedSection.tsx)10
-rw-r--r--src/components/search/ExploreSectionUser.tsx (renamed from src/components/search/SuggestedUser.tsx)21
-rw-r--r--src/components/search/SearchBar.tsx105
-rw-r--r--src/components/search/SearchHeader.tsx60
-rw-r--r--src/components/search/SearchResult.tsx94
-rw-r--r--src/components/search/SearchResults.tsx28
-rw-r--r--src/components/search/SearchResultsBackground.tsx48
-rw-r--r--src/components/search/index.ts7
-rw-r--r--src/constants/api.ts1
-rw-r--r--src/screens/profile/ProfileScreen.tsx3
-rw-r--r--src/screens/search/SearchScreen.tsx143
-rw-r--r--src/types/types.ts10
16 files changed, 482 insertions, 124 deletions
diff --git a/src/components/common/TabsGradient.tsx b/src/components/common/TabsGradient.tsx
new file mode 100644
index 00000000..a95e8bc3
--- /dev/null
+++ b/src/components/common/TabsGradient.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import LinearGradient from 'react-native-linear-gradient';
+import {StyleSheet} from 'react-native';
+import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+
+const TabsGradient: React.FC = () => {
+ return (
+ <LinearGradient
+ locations={[0, 1]}
+ colors={['transparent', 'rgba(0, 0, 0, 0.7)']}
+ style={styles.gradient}
+ />
+ );
+};
+const styles = StyleSheet.create({
+ gradient: {
+ position: 'absolute',
+ top: (SCREEN_HEIGHT / 10) * 9,
+ height: SCREEN_HEIGHT / 10,
+ width: SCREEN_WIDTH,
+ },
+});
+export default TabsGradient;
diff --git a/src/components/common/index.ts b/src/components/common/index.ts
index 8d0ef778..63a7b9c2 100644
--- a/src/components/common/index.ts
+++ b/src/components/common/index.ts
@@ -5,3 +5,4 @@ export {default as NavigationIcon} from './NavigationIcon';
export {default as GradientBackground} from './GradientBackground';
export {default as Post} from './post';
export {default as SocialIcon} from './SocialIcon';
+export {default as TabsGradient} from './TabsGradient';
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx
index 82b5fdc0..49cc2c35 100644
--- a/src/components/profile/Content.tsx
+++ b/src/components/profile/Content.tsx
@@ -1,7 +1,6 @@
import React, {useState} from 'react';
import {StyleSheet, LayoutChangeEvent} from 'react-native';
import Animated from 'react-native-reanimated';
-const {ScrollView} = Animated;
import {UserType} from '../../types';
import ProfileCutout from './ProfileCutout';
@@ -9,8 +8,6 @@ import ProfileHeader from './ProfileHeader';
import ProfileBody from './ProfileBody';
import MomentsBar from './MomentsBar';
import Feed from './Feed';
-import LinearGradient from 'react-native-linear-gradient';
-import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
interface ContentProps {
y: Animated.Value<number>;
@@ -23,7 +20,7 @@ const Content: React.FC<ContentProps> = ({y, user}) => {
setProfileBodyHeight(height);
};
return (
- <ScrollView
+ <Animated.ScrollView
style={styles.container}
onScroll={(e) => y.setValue(e.nativeEvent.contentOffset.y)}
showsVerticalScrollIndicator={false}
@@ -35,12 +32,7 @@ const Content: React.FC<ContentProps> = ({y, user}) => {
<ProfileBody {...{onLayout}} />
<MomentsBar {...{y, profileBodyHeight}} />
<Feed {...{user}} />
- <LinearGradient
- locations={[0.89, 1]}
- colors={['transparent', 'rgba(0, 0, 0, 0.6)']}
- style={styles.gradient}
- />
- </ScrollView>
+ </Animated.ScrollView>
);
};
@@ -48,11 +40,6 @@ const styles = StyleSheet.create({
container: {
flex: 1,
},
- gradient: {
- height: SCREEN_HEIGHT,
- width: SCREEN_WIDTH,
- position: 'absolute',
- },
});
export default Content;
diff --git a/src/components/search/Explore.tsx b/src/components/search/Explore.tsx
new file mode 100644
index 00000000..a02205a4
--- /dev/null
+++ b/src/components/search/Explore.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import {View, StyleSheet} from 'react-native';
+import ExploreSection from './ExploreSection';
+
+const Explore: React.FC = () => {
+ const sections: Array<string> = [
+ 'People you follow',
+ 'People you may know',
+ 'Trending in sports',
+ 'Trending on Tagg',
+ 'Trending in music',
+ ];
+ const users: Array<string> = [
+ 'Sam Davis',
+ 'Becca Smith',
+ 'Ann Taylor',
+ 'Clara Johnson',
+ 'Sarah Jung',
+ 'Lila Hernandez',
+ ];
+ return (
+ <View style={styles.container}>
+ {sections.map((title) => (
+ <ExploreSection key={title} title={title} users={users} />
+ ))}
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ zIndex: 0,
+ },
+});
+export default Explore;
diff --git a/src/components/search/SuggestedSection.tsx b/src/components/search/ExploreSection.tsx
index af0323a5..8e826bd9 100644
--- a/src/components/search/SuggestedSection.tsx
+++ b/src/components/search/ExploreSection.tsx
@@ -1,23 +1,23 @@
import React from 'react';
import {View, Text, ScrollView, StyleSheet} from 'react-native';
-import SuggestedUser from './SuggestedUser';
+import ExploreSectionUser from './ExploreSectionUser';
/**
* Search Screen for user recommendations and a search
* tool to allow user to find other users
*/
-interface SuggestedSectionProps {
+interface ExploreSectionProps {
title: string;
users: Array<string>;
}
-const SuggestedSection: React.FC<SuggestedSectionProps> = ({title, users}) => {
+const ExploreSection: React.FC<ExploreSectionProps> = ({title, users}) => {
return (
<View style={styles.container}>
<Text style={styles.header}>{title}</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
{users.map((name, key) => (
- <SuggestedUser {...{name, key}} style={styles.user} />
+ <ExploreSectionUser {...{name, key}} style={styles.user} />
))}
</ScrollView>
</View>
@@ -39,4 +39,4 @@ const styles = StyleSheet.create({
},
});
-export default SuggestedSection;
+export default ExploreSection;
diff --git a/src/components/search/SuggestedUser.tsx b/src/components/search/ExploreSectionUser.tsx
index 467e5e6c..a9fce063 100644
--- a/src/components/search/SuggestedUser.tsx
+++ b/src/components/search/ExploreSectionUser.tsx
@@ -1,5 +1,11 @@
import React from 'react';
-import {View, StyleSheet, Text, ViewProps, Image} from 'react-native';
+import {
+ StyleSheet,
+ Text,
+ ViewProps,
+ Image,
+ TouchableOpacity,
+} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
/**
@@ -7,12 +13,15 @@ import LinearGradient from 'react-native-linear-gradient';
* tool to allow user to find other users
*/
-interface SuggestedUserProps extends ViewProps {
+interface ExploreSectionUserProps extends ViewProps {
name: string;
}
-const SuggestedUser: React.FC<SuggestedUserProps> = ({name, style}) => {
+const ExploreSectionUser: React.FC<ExploreSectionUserProps> = ({
+ name,
+ style,
+}) => {
return (
- <View style={[styles.container, style]}>
+ <TouchableOpacity style={[styles.container, style]}>
<LinearGradient
colors={['#9F00FF', '#27EAE9']}
useAngle
@@ -26,7 +35,7 @@ const SuggestedUser: React.FC<SuggestedUserProps> = ({name, style}) => {
</LinearGradient>
<Text style={styles.name}>{name}</Text>
<Text style={styles.username}>{`@${name.split(' ').join('')}`}</Text>
- </View>
+ </TouchableOpacity>
);
};
@@ -58,4 +67,4 @@ const styles = StyleSheet.create({
color: '#fff',
},
});
-export default SuggestedUser;
+export default ExploreSectionUser;
diff --git a/src/components/search/SearchBar.tsx b/src/components/search/SearchBar.tsx
index 283b6d59..8bb93d54 100644
--- a/src/components/search/SearchBar.tsx
+++ b/src/components/search/SearchBar.tsx
@@ -2,71 +2,97 @@ import React from 'react';
import {
StyleSheet,
TextInput,
- TextInputProps,
- NativeSyntheticEvent,
- TextInputFocusEventData,
TouchableOpacity,
Text,
View,
+ TextInputProps,
+ Keyboard,
+ NativeSyntheticEvent,
+ TextInputSubmitEditingEventData,
} from 'react-native';
+import Animated, {
+ interpolate,
+ interpolateColors,
+} from 'react-native-reanimated';
+import {SCREEN_HEIGHT} from '../../utils';
import Icon from 'react-native-vector-icons/Feather';
-import Animated from 'react-native-reanimated';
interface SearchBarProps extends TextInputProps {
- active: boolean;
+ onCancel: () => void;
+ top: Animated.Value<number>;
}
-const SearchBar: React.FC<SearchBarProps> = ({onFocus, onBlur, active}) => {
- const handleFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
- // TODO: animate Icon & View.inputContainer.borderColor color to '#000'
- // TODO: animate background color (& page color in results ScrollView) to '#ffff' (last f for opacity)
- // TODO: animate TextInput width and mount "Cancel" button (& animate opacity)
- // OR
- // TODO: just animate "Cancel" button width and opacity (this might be easier)
- onFocus && onFocus(e);
- };
- const handleBlur = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
- // TODO: animate Icon color & View.inputContainer borderColor back
- // TODO: animate background color (and page color in ScrollView) back to '#fff3'
- // TODO: unmount Cancel button (and animate width change)
- onBlur && onBlur(e);
+const SearchBar: React.FC<SearchBarProps> = ({
+ onFocus,
+ onBlur,
+ onChangeText,
+ value,
+ onCancel,
+ top,
+ style,
+}) => {
+ const opacity: Animated.Node<number> = interpolate(top, {
+ inputRange: [-SCREEN_HEIGHT, 0],
+ outputRange: [0, 1],
+ });
+ const marginRight: Animated.Node<number> = interpolate(top, {
+ inputRange: [-SCREEN_HEIGHT, 0],
+ outputRange: [0, 57],
+ });
+ const color: Animated.Node<number> = interpolateColors(top, {
+ inputRange: [-SCREEN_HEIGHT, 0],
+ outputColorRange: ['#fff', '#000'],
+ });
+ const handleSubmit = (
+ e: NativeSyntheticEvent<TextInputSubmitEditingEventData>,
+ ) => {
+ e.preventDefault();
+ Keyboard.dismiss();
};
+ const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
+ const AnimatedIcon = Animated.createAnimatedComponent(Icon);
return (
- <View style={styles.container}>
- <Animated.View style={styles.inputContainer}>
- <Icon name="search" size={25} color="#fff" style={styles.searchIcon} />
- <TextInput
- onFocus={handleFocus}
- onBlur={handleBlur}
- style={styles.input}
+ <View style={[styles.container, style]}>
+ <Animated.View style={[styles.inputContainer, {borderColor: color}]}>
+ <AnimatedIcon
+ name="search"
+ color={color}
+ size={25}
+ style={styles.searchIcon}
+ />
+ <AnimatedTextInput
+ style={[styles.input, {color}]}
placeholder={'Search...'}
+ placeholderTextColor={'#fff'}
+ onSubmitEditing={handleSubmit}
+ clearButtonMode="while-editing"
+ {...{value, onChangeText, onFocus, onBlur}}
/>
</Animated.View>
- {active && (
- <TouchableOpacity style={styles.cancelButton}>
- <Text style={styles.cancel}>Cancel</Text>
+ <Animated.View style={{opacity, marginRight}}>
+ <TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
+ <Text style={styles.cancelText}>Cancel</Text>
</TouchableOpacity>
- )}
+ </Animated.View>
</View>
);
};
const styles = StyleSheet.create({
container: {
- flexDirection: 'row',
- alignItems: 'center',
height: 40,
+ flexDirection: 'row',
+ alignItems: 'stretch',
+ zIndex: 2,
},
inputContainer: {
+ flexGrow: 1,
flexDirection: 'row',
alignItems: 'center',
- flex: 1,
- height: '100%',
paddingHorizontal: 8,
- backgroundColor: '#fff3',
- borderColor: '#fff',
borderWidth: 1.5,
borderRadius: 20,
+ backgroundColor: '#fff3',
},
searchIcon: {
marginRight: 8,
@@ -76,9 +102,12 @@ const styles = StyleSheet.create({
fontSize: 16,
},
cancelButton: {
- marginHorizontal: 5,
+ position: 'absolute',
+ height: '100%',
+ justifyContent: 'center',
+ paddingHorizontal: 5,
},
- cancel: {
+ cancelText: {
color: '#818181',
fontWeight: '600',
},
diff --git a/src/components/search/SearchHeader.tsx b/src/components/search/SearchHeader.tsx
new file mode 100644
index 00000000..2a022f50
--- /dev/null
+++ b/src/components/search/SearchHeader.tsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import {SCREEN_HEIGHT} from '../../utils';
+import {View, StyleSheet, ViewProps} from 'react-native';
+import Animated, {
+ Value,
+ interpolateColors,
+ interpolate,
+} from 'react-native-reanimated';
+
+interface SearchHeaderProps extends ViewProps {
+ top: Value<number>;
+}
+const SearchHeader: React.FC<SearchHeaderProps> = ({top, style}) => {
+ const color: Animated.Node<number> = interpolateColors(top, {
+ inputRange: [-SCREEN_HEIGHT, 0],
+ outputColorRange: ['#fff', '#000'],
+ });
+ const searchOpacity: Animated.Node<number> = interpolate(top, {
+ inputRange: [-SCREEN_HEIGHT, 0],
+ outputRange: [0, 1],
+ });
+ const exploreOpacity: Animated.Node<number> = interpolate(top, {
+ inputRange: [-SCREEN_HEIGHT, 0],
+ outputRange: [1, 0],
+ });
+ return (
+ <View style={[styles.container, style]}>
+ <View style={styles.headerContainer}>
+ <Animated.Text
+ style={[styles.header, {opacity: exploreOpacity, color}]}>
+ Explore
+ </Animated.Text>
+ </View>
+ <View style={styles.headerContainer}>
+ <Animated.Text style={[styles.header, {opacity: searchOpacity, color}]}>
+ Search
+ </Animated.Text>
+ </View>
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ height: 30,
+ },
+ headerContainer: {
+ position: 'absolute',
+ left: '50%',
+ },
+ header: {
+ position: 'relative',
+ right: '50%%',
+ fontSize: 24,
+ fontWeight: 'bold',
+ },
+});
+export default SearchHeader;
diff --git a/src/components/search/SearchResult.tsx b/src/components/search/SearchResult.tsx
new file mode 100644
index 00000000..60c22d41
--- /dev/null
+++ b/src/components/search/SearchResult.tsx
@@ -0,0 +1,94 @@
+import React, {useEffect, useState} from 'react';
+import {ProfilePreviewType} from '../../types';
+import {
+ View,
+ Text,
+ Image,
+ StyleSheet,
+ ViewProps,
+ TouchableOpacity,
+} from 'react-native';
+import RNFetchBlob from 'rn-fetch-blob';
+import {AVATAR_PHOTO_ENDPOINT} from '../../constants';
+
+interface SearchResultProps extends ViewProps {
+ profilePreview: ProfilePreviewType;
+}
+const SearchResult: React.FC<SearchResultProps> = ({
+ profilePreview: {username, first_name, last_name, id},
+ style,
+}) => {
+ const [avatarURI, setAvatarURI] = useState<string | null>(null);
+
+ useEffect(() => {
+ let mounted = true;
+ const loadAvatar = async () => {
+ try {
+ const response = await RNFetchBlob.config({
+ fileCache: true,
+ appendExt: 'jpg',
+ }).fetch('GET', AVATAR_PHOTO_ENDPOINT + `${id}`);
+ const status = response.info().status;
+ if (status === 200) {
+ if (mounted) {
+ setAvatarURI(response.path());
+ }
+ return;
+ }
+ if (mounted) {
+ setAvatarURI('');
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ };
+ loadAvatar();
+ return () => {
+ mounted = false;
+ };
+ }, [id]);
+
+ return (
+ <TouchableOpacity style={[styles.container, style]}>
+ <Image
+ style={styles.avatar}
+ source={
+ avatarURI
+ ? {uri: avatarURI}
+ : require('../../assets/images/avatar-placeholder.png')
+ }
+ />
+ <View style={styles.nameContainer}>
+ <Text style={styles.username}>@{username}</Text>
+ <Text style={styles.name}>{first_name.concat(' ', last_name)}</Text>
+ </View>
+ </TouchableOpacity>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ avatar: {
+ height: 60,
+ width: 60,
+ borderRadius: 30,
+ marginRight: 15,
+ },
+ nameContainer: {
+ justifyContent: 'space-evenly',
+ alignSelf: 'stretch',
+ },
+ username: {
+ fontSize: 18,
+ fontWeight: '500',
+ },
+ name: {
+ fontSize: 16,
+ color: '#333',
+ },
+});
+
+export default SearchResult;
diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx
new file mode 100644
index 00000000..16bff818
--- /dev/null
+++ b/src/components/search/SearchResults.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import {ProfilePreviewType} from '../../types';
+import SearchResult from './SearchResult';
+import {StyleSheet, View} from 'react-native';
+interface SearchResultsProps {
+ results: Array<ProfilePreviewType>;
+}
+const SearchResults: React.FC<SearchResultsProps> = ({results}) => {
+ return (
+ <View>
+ {results.map((profilePreview) => (
+ <SearchResult
+ style={styles.result}
+ key={profilePreview.id}
+ {...{profilePreview}}
+ />
+ ))}
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ result: {
+ marginVertical: 10,
+ },
+});
+
+export default SearchResults;
diff --git a/src/components/search/SearchResultsBackground.tsx b/src/components/search/SearchResultsBackground.tsx
new file mode 100644
index 00000000..3e1e4fdc
--- /dev/null
+++ b/src/components/search/SearchResultsBackground.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import Animated, {interpolate} from 'react-native-reanimated';
+import {StyleSheet} from 'react-native';
+import {SCREEN_HEIGHT, SCREEN_WIDTH, StatusBarHeight} from '../../utils';
+
+interface SearchResultsBackgroundProps {
+ top: Animated.Value<number>;
+}
+const SearchResultsBackground: React.FC<SearchResultsBackgroundProps> = ({
+ top,
+ children,
+}) => {
+ const opacityBackground: Animated.Node<number> = interpolate(top, {
+ inputRange: [-SCREEN_HEIGHT, 0],
+ outputRange: [0, 1],
+ });
+ const opacityContent: Animated.Node<number> = interpolate(top, {
+ inputRange: [-SCREEN_HEIGHT / 40, 0],
+ outputRange: [0, 1],
+ });
+ return (
+ <Animated.View
+ style={[styles.container, {opacity: opacityBackground, top}]}>
+ <Animated.ScrollView
+ contentContainerStyle={styles.contentContainer}
+ style={[styles.results, {opacity: opacityContent}]}>
+ {children}
+ </Animated.ScrollView>
+ </Animated.View>
+ );
+};
+const styles = StyleSheet.create({
+ container: {
+ height: SCREEN_HEIGHT,
+ width: SCREEN_WIDTH,
+ padding: 20,
+ position: 'absolute',
+ backgroundColor: '#fff',
+ zIndex: 0,
+ },
+ contentContainer: {
+ flex: 1,
+ },
+ results: {
+ marginTop: StatusBarHeight + 110,
+ },
+});
+export default SearchResultsBackground;
diff --git a/src/components/search/index.ts b/src/components/search/index.ts
index 4e1d2b14..47dccd8b 100644
--- a/src/components/search/index.ts
+++ b/src/components/search/index.ts
@@ -1,3 +1,6 @@
-export {default as SearchBar} from './SearchBar';
-export {default as SuggestedSection} from './SuggestedSection';
export {default as SearchBackground} from './SearchBackground';
+export {default as SearchHeader} from './SearchHeader';
+export {default as SearchBar} from './SearchBar';
+export {default as Explore} from './Explore';
+export {default as SearchResultsBackground} from './SearchResultsBackground';
+export {default as SearchResults} from './SearchResults';
diff --git a/src/constants/api.ts b/src/constants/api.ts
index 057b5da7..5eb07980 100644
--- a/src/constants/api.ts
+++ b/src/constants/api.ts
@@ -9,3 +9,4 @@ export const PROFILE_INFO_ENDPOINT: string = API_URL + 'user-profile-info/';
export const COVER_PHOTO_ENDPOINT: string = API_URL + 'large-profile-pic/';
export const AVATAR_PHOTO_ENDPOINT: string = API_URL + 'small-profile-pic/';
export const GET_IG_POSTS_ENDPOINT: string = API_URL + 'posts-ig/';
+export const SEARCH_ENDPOINT: string = API_URL + 'search/';
diff --git a/src/screens/profile/ProfileScreen.tsx b/src/screens/profile/ProfileScreen.tsx
index 3d1ef2a8..9da9a3d8 100644
--- a/src/screens/profile/ProfileScreen.tsx
+++ b/src/screens/profile/ProfileScreen.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import {Cover, Content} from '../../components';
+import {Cover, Content, TabsGradient} from '../../components';
import Animated from 'react-native-reanimated';
import {AuthContext} from '../../routes/authentication';
import {StatusBar} from 'react-native';
@@ -19,6 +19,7 @@ const ProfileScreen: React.FC = () => {
<StatusBar />
<Cover {...{y, user}} />
<Content {...{y, user}} />
+ <TabsGradient />
</>
);
};
diff --git a/src/screens/search/SearchScreen.tsx b/src/screens/search/SearchScreen.tsx
index 8ef56b73..94b9ab41 100644
--- a/src/screens/search/SearchScreen.tsx
+++ b/src/screens/search/SearchScreen.tsx
@@ -1,83 +1,112 @@
-import React, {useState} from 'react';
+import React, {useEffect, useState} from 'react';
+import {StatusBar, StyleSheet, ScrollView, Keyboard} from 'react-native';
import {
- StatusBar,
- SafeAreaView,
- StyleSheet,
- Text,
- View,
- ScrollView,
-} from 'react-native';
-import {SearchBar, SuggestedSection, SearchBackground} from '../../components';
-import {SCREEN_HEIGHT} from '../../utils';
+ SearchBackground,
+ SearchHeader,
+ SearchBar,
+ Explore,
+ SearchResultsBackground,
+ SearchResults,
+ TabsGradient,
+} from '../../components';
+import {SCREEN_HEIGHT, StatusBarHeight} from '../../utils';
+import Animated, {Easing, timing} from 'react-native-reanimated';
+import {ProfilePreviewType} from '../../types';
+import {SEARCH_ENDPOINT} from '../../constants';
+const {Value} = Animated;
/**
* Search Screen for user recommendations and a search
* tool to allow user to find other users
*/
+const top: Animated.Value<number> = new Value(-SCREEN_HEIGHT);
const SearchScreen: React.FC = () => {
- const sections: Array<string> = [
- 'People you follow',
- 'People you may know',
- 'Trending in sports',
- 'Trending on Tagg',
- 'Trending in music',
- ];
- // dummy user data
- const users: Array<string> = [
- 'Sam Davis',
- 'Becca Smith',
- 'Ann Taylor',
- 'Clara Johnson',
- 'Sarah Jung',
- 'Lila Hernandez',
- ];
- const [isSearching, setIsSearching] = useState<boolean>(false);
+ const [query, setQuery] = useState<string>('');
+ const [results, setResults] = useState<Array<ProfilePreviewType>>([]);
+ useEffect(() => {
+ if (query.length < 3) {
+ setResults([]);
+ return;
+ }
+ const loadResults = async (q: string) => {
+ try {
+ const response = await fetch(`${SEARCH_ENDPOINT}?query=${q}`, {
+ method: 'GET',
+ });
+ const status = response.status;
+ if (status === 200) {
+ let searchResults = await response.json();
+ setResults(searchResults);
+ return;
+ }
+ setResults([]);
+ } catch (error) {
+ console.log(error);
+ setResults([]);
+ }
+ };
+ loadResults(query);
+ }, [query]);
+
const handleFocus = () => {
- setIsSearching(true);
+ const topInConfig = {
+ duration: 180,
+ toValue: 0,
+ easing: Easing.bezier(0.31, 0.14, 0.66, 0.82),
+ };
+ timing(top, topInConfig).start();
};
const handleBlur = () => {
- setIsSearching(false);
+ Keyboard.dismiss();
+ const topOutConfig = {
+ duration: 180,
+ toValue: -SCREEN_HEIGHT,
+ easing: Easing.inOut(Easing.ease),
+ };
+ timing(top, topOutConfig).start();
};
+
return (
- <SearchBackground style={styles.screen}>
+ <SearchBackground>
<StatusBar />
- <SafeAreaView>
- <ScrollView showsVerticalScrollIndicator={false}>
- <Text style={styles.header}>Explore</Text>
- <SearchBar
- active={isSearching}
- onFocus={handleFocus}
- onBlur={handleBlur}
- />
- {!isSearching && (
- <View style={styles.content}>
- {sections.map((title) => (
- <SuggestedSection key={title} title={title} users={users} />
- ))}
- </View>
- )}
- </ScrollView>
- </SafeAreaView>
+ <ScrollView
+ keyboardShouldPersistTaps={'always'}
+ stickyHeaderIndices={[4]}
+ contentContainerStyle={styles.contentContainer}
+ showsVerticalScrollIndicator={false}>
+ <SearchHeader style={styles.header} {...{top}} />
+ <SearchBar
+ style={styles.searchBar}
+ onCancel={handleBlur}
+ onChangeText={setQuery}
+ onBlur={Keyboard.dismiss}
+ onFocus={handleFocus}
+ value={query}
+ {...{top}}
+ />
+ <Explore />
+ <SearchResultsBackground {...{top}}>
+ <SearchResults {...{results}} />
+ </SearchResultsBackground>
+ </ScrollView>
+ <TabsGradient />
</SearchBackground>
);
};
const styles = StyleSheet.create({
- screen: {
- paddingTop: 50,
- paddingBottom: SCREEN_HEIGHT / 10,
+ contentContainer: {
+ paddingTop: StatusBarHeight,
+ paddingBottom: SCREEN_HEIGHT / 15,
paddingHorizontal: 15,
},
- content: {
- paddingVertical: 20,
+ searchBar: {
+ marginBottom: 20,
},
header: {
- fontWeight: 'bold',
- fontSize: 24,
- color: '#fff',
- marginBottom: 20,
- textAlign: 'center',
+ marginVertical: 20,
+ zIndex: 1,
},
});
export default SearchScreen;
diff --git a/src/types/types.ts b/src/types/types.ts
index 6dabc0bc..c01ee045 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -3,6 +3,16 @@ export interface UserType {
username: string;
}
+/**
+ * User profile information that only conntains a few key fields.
+ */
+export interface ProfilePreviewType {
+ id: string;
+ username: string;
+ first_name: string;
+ last_name: string;
+}
+
export interface ProfileType {
name: string;
biography: string;