diff options
-rw-r--r-- | src/components/common/TabsGradient.tsx | 23 | ||||
-rw-r--r-- | src/components/common/index.ts | 1 | ||||
-rw-r--r-- | src/components/profile/Content.tsx | 17 | ||||
-rw-r--r-- | src/components/search/Explore.tsx | 35 | ||||
-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.tsx | 105 | ||||
-rw-r--r-- | src/components/search/SearchHeader.tsx | 60 | ||||
-rw-r--r-- | src/components/search/SearchResult.tsx | 94 | ||||
-rw-r--r-- | src/components/search/SearchResults.tsx | 28 | ||||
-rw-r--r-- | src/components/search/SearchResultsBackground.tsx | 48 | ||||
-rw-r--r-- | src/components/search/index.ts | 7 | ||||
-rw-r--r-- | src/constants/api.ts | 1 | ||||
-rw-r--r-- | src/screens/profile/ProfileScreen.tsx | 3 | ||||
-rw-r--r-- | src/screens/search/SearchScreen.tsx | 143 | ||||
-rw-r--r-- | src/types/types.ts | 10 |
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; |