diff options
| author | Ivan Chen <ivan@tagg.id> | 2021-03-12 17:25:38 -0500 |
|---|---|---|
| committer | Ivan Chen <ivan@tagg.id> | 2021-03-12 17:25:38 -0500 |
| commit | d58c534bff5fbe2941fe46f9ad4a3bfb8e4b3700 (patch) | |
| tree | d3a573417c19a9e1199e40015819a524536e5df9 /src/components | |
| parent | e645175094525c38beedb9227052582117e4661b (diff) | |
| parent | 34d94e1dff831aafa984a2f6c9b9b01e2e349154 (diff) | |
Merge branch 'master' into hotfix-badge-name-fix
# Conflicts:
# src/screens/suggestedPeople/SPBody.tsx
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/common/GradientBorderButton.tsx | 66 | ||||
| -rw-r--r-- | src/components/common/index.ts | 1 | ||||
| -rw-r--r-- | src/components/search/SearchBar.tsx | 78 | ||||
| -rw-r--r-- | src/components/search/SearchCategories.tsx | 97 | ||||
| -rw-r--r-- | src/components/search/SearchResultCell.tsx | 17 | ||||
| -rw-r--r-- | src/components/search/SearchResultList.tsx | 7 | ||||
| -rw-r--r-- | src/components/search/SearchResults.tsx | 34 |
7 files changed, 225 insertions, 75 deletions
diff --git a/src/components/common/GradientBorderButton.tsx b/src/components/common/GradientBorderButton.tsx new file mode 100644 index 00000000..00f46a96 --- /dev/null +++ b/src/components/common/GradientBorderButton.tsx @@ -0,0 +1,66 @@ +import MaskedView from '@react-native-community/masked-view'; +import React from 'react'; +import {StyleSheet, Text, View} from 'react-native'; +import {TouchableOpacity} from 'react-native-gesture-handler'; +import LinearGradient from 'react-native-linear-gradient'; +import {TAGG_LIGHT_BLUE_2, TAGG_PURPLE} from '../../constants'; +import {normalize, SCREEN_WIDTH} from '../../utils'; + +interface GradientBorderButtonProps { + text: string; + darkStyle: boolean; + onPress: () => void; +} + +const GradientBorderButton: React.FC<GradientBorderButtonProps> = ({ + text, + darkStyle, + onPress, +}) => { + const labelColor = darkStyle ? 'white' : '#828282'; + const borderWidth = darkStyle ? 2 : 1; + return ( + <TouchableOpacity style={styles.container} onPress={onPress}> + <MaskedView + maskElement={ + <View + style={[styles.gradientContainer, styles.maskBorder, {borderWidth}]} + /> + }> + <LinearGradient + colors={[TAGG_PURPLE, TAGG_LIGHT_BLUE_2]} + start={{x: 0.0, y: 1.0}} + end={{x: 1.0, y: 1.0}} + style={styles.gradientContainer} + /> + </MaskedView> + <View style={styles.textContainer}> + <Text style={[styles.label, {color: labelColor}]}>{text}</Text> + </View> + </TouchableOpacity> + ); +}; +const styles = StyleSheet.create({ + container: { + marginVertical: 15, + }, + gradientContainer: { + width: SCREEN_WIDTH / 2 - 40, + height: 40, + }, + label: { + fontWeight: '500', + fontSize: normalize(14), + }, + maskBorder: { + borderRadius: 20, + }, + textContainer: { + position: 'absolute', + width: SCREEN_WIDTH / 2 - 40, + height: 40, + justifyContent: 'center', + alignItems: 'center', + }, +}); +export default GradientBorderButton; diff --git a/src/components/common/index.ts b/src/components/common/index.ts index e1543cd8..8499dbfa 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -22,3 +22,4 @@ export {default as TaggPrompt} from './TaggPrompt'; export {default as AcceptDeclineButtons} from './AcceptDeclineButtons'; export {default as FriendsButton} from './FriendsButton'; export {default as TaggSquareButton} from './TaggSquareButton'; +export {default as GradientBorderButton} from './GradientBorderButton'; diff --git a/src/components/search/SearchBar.tsx b/src/components/search/SearchBar.tsx index 5e3a1e64..1a855f20 100644 --- a/src/components/search/SearchBar.tsx +++ b/src/components/search/SearchBar.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react'; +import React, {useState, useEffect} from 'react'; import { StyleSheet, TextInput, @@ -13,13 +13,14 @@ import { import Animated, {interpolate} from 'react-native-reanimated'; import Icon from 'react-native-vector-icons/Feather'; import {normalize} from 'react-native-elements'; -import {SCREEN_HEIGHT} from '../../utils'; +import {SCREEN_HEIGHT, getSearchSuggestions} from '../../utils'; const AnimatedIcon = Animated.createAnimatedComponent(Icon); interface SearchBarProps extends TextInputProps { onCancel: () => void; top: Animated.Value<number>; + searching: boolean; } const SearchBar: React.FC<SearchBarProps> = ({ onFocus, @@ -27,6 +28,7 @@ const SearchBar: React.FC<SearchBarProps> = ({ onChangeText, value, onCancel, + searching, top, }) => { const handleSubmit = ( @@ -35,9 +37,76 @@ const SearchBar: React.FC<SearchBarProps> = ({ e.preventDefault(); Keyboard.dismiss(); }; + const DEFAULT_PLACEHOLDER: string = 'Search'; + // the list of suggestions to cycle through. TODO: get this from the backend + const SEARCH_SUGGESTIONS: string[] = getSearchSuggestions(); + /* + * index & id of current placeholder, used in selecting next placeholder. -1 + * indicates DEFAULT_PLACEHOLDER. TODO: make it appear more random by tracking + * last 3-5 ids & use longer list of placeholders + */ + const [placeholderId, setPlaceholderId] = useState<number>(-1); + // the current placeholder + const [placeholder, setPlaceholder] = useState<string>(DEFAULT_PLACEHOLDER); + + /* + * Utility function that generates a random integer in [0, xCeil). + * + * @param xCeil - the exclusive ceiling (getRandomInt(2) => 0 or 1, not 2) + * @returns a random integer in the range [0, xCeil) + */ + const getRandomInt = (xCeil: number): number => { + return Math.floor(Math.random() * Math.floor(xCeil)); + }; + + /* + * Handler for `placeholderChangeInterval` that sets the next placeholderId. + */ + const updatePlaceholder = () => { + let nextId: number = getRandomInt(SEARCH_SUGGESTIONS.length); + while (nextId === placeholderId) { + nextId = getRandomInt(SEARCH_SUGGESTIONS.length); + } + // TODO: FIGURE OUT WHY CHANGES IN placeholderId ARE NOT REFLECTED HERE + // my thought: the value is set when the function is defined, so it keeps + // its inital value of -1 forever. + console.log(`Previous ID: ${placeholderId}`); + console.log(`Next ID: ${nextId}`); + setPlaceholderId(nextId); + }; + + /* + * Update `placeholder` when `placeholderId` is updated by the interval handler. + */ + useEffect(() => { + if (placeholderId === -1) { + setPlaceholder(DEFAULT_PLACEHOLDER); + return; + } + setPlaceholder( + DEFAULT_PLACEHOLDER.concat(` '${SEARCH_SUGGESTIONS[placeholderId]}'`), + ); + }, [placeholderId]); + + /* + * Sets the interval when the user begins searching and clears it when the user is done. + */ + useEffect(() => { + if (!searching) { + return; + } + updatePlaceholder(); + const updateInterval = setInterval(() => { + updatePlaceholder(); + }, 4000); + return () => { + clearInterval(updateInterval); + setPlaceholderId(-1); + }; + }, [searching]); /* - * CSS properties for width change animation. + * Animated nodes used in search bar activation animation. */ const marginRight: Animated.Node<number> = interpolate(top, { inputRange: [-SCREEN_HEIGHT, 0], @@ -59,13 +128,12 @@ const SearchBar: React.FC<SearchBarProps> = ({ /> <TextInput style={[styles.input]} - placeholder={'Search'} placeholderTextColor={'#828282'} onSubmitEditing={handleSubmit} clearButtonMode="while-editing" autoCapitalize="none" autoCorrect={false} - {...{value, onChangeText, onFocus, onBlur}} + {...{placeholder, value, onChangeText, onFocus, onBlur}} /> </Animated.View> <Animated.View style={{marginRight, opacity}}> diff --git a/src/components/search/SearchCategories.tsx b/src/components/search/SearchCategories.tsx index c3c4c518..a71befe6 100644 --- a/src/components/search/SearchCategories.tsx +++ b/src/components/search/SearchCategories.tsx @@ -1,47 +1,64 @@ import {useNavigation} from '@react-navigation/native'; import React, {useEffect, useState} from 'react'; -import {StyleSheet, Text, View} from 'react-native'; -import {TouchableOpacity} from 'react-native-gesture-handler'; -import LinearGradient from 'react-native-linear-gradient'; -import {getButtons} from '../../services/ExploreService'; +import {StyleSheet, View} from 'react-native'; +import {GradientBorderButton} from '..'; +import {getSuggestedSearchBubbleSuggestions} from '../../services/ExploreService'; import {SearchCategoryType} from '../../types'; -import {TAGG_LIGHT_BLUE_2, TAGG_PURPLE} from '../../constants'; import {SCREEN_WIDTH} from '../../utils'; -const SearchCategories: React.FC = () => { - const [buttons, setButtons] = useState<SearchCategoryType[]>([]); +interface SearchCategoriesProps { + darkStyle?: boolean; + defaultButtons?: SearchCategoryType[]; +} + +const SearchCategories: React.FC<SearchCategoriesProps> = ({ + darkStyle = false, + defaultButtons, +}) => { + const navigation = useNavigation(); + const mtSearchCategory: (key: number) => SearchCategoryType = (key) => ({ + id: key, + name: '...', + category: '...', + }); + const [buttons, setButtons] = useState<SearchCategoryType[]>([ + mtSearchCategory(-1), + mtSearchCategory(-2), + mtSearchCategory(-3), + mtSearchCategory(-4), + ]); + useEffect(() => { const loadButtons = async () => { - const localButtons = await getButtons(); - console.log('localButtons: ', localButtons); - await setButtons(localButtons); + const localButtons = await getSuggestedSearchBubbleSuggestions(); + setButtons([]); + if (localButtons) { + setButtons(localButtons); + } }; - loadButtons(); + if (!defaultButtons) { + loadButtons(); + } else { + setButtons(defaultButtons); + } }, []); - const navigation = useNavigation(); return ( <View style={styles.container}> - {buttons && - buttons.map((searchCategory) => ( - <LinearGradient - key={searchCategory.id} - colors={[TAGG_PURPLE, TAGG_LIGHT_BLUE_2]} - start={{x: 0.0, y: 1.0}} - end={{x: 1.0, y: 1.0}} - style={styles.gradientContainer}> - <TouchableOpacity - style={styles.buttonContainer} - key={searchCategory.id} - onPress={() => { - navigation.navigate('DiscoverUsers', { - searchCategory, - }); - }}> - <Text style={styles.buttonText}>{searchCategory.name}</Text> - </TouchableOpacity> - </LinearGradient> - ))} + {buttons.map((searchCategory) => ( + <GradientBorderButton + key={searchCategory.id} + text={searchCategory.name} + darkStyle={darkStyle} + onPress={() => { + if (searchCategory.name !== '...') { + navigation.push('DiscoverUsers', { + searchCategory, + }); + } + }} + /> + ))} </View> ); }; @@ -56,20 +73,8 @@ const styles = StyleSheet.create({ flexWrap: 'wrap', justifyContent: 'space-evenly', }, - gradientContainer: { - width: 159, - height: 38, - alignItems: 'center', - justifyContent: 'center', - marginVertical: '2.5%', - flexDirection: 'row', - alignContent: 'center', - borderRadius: 20, - borderColor: 'transparent', - borderWidth: 1, - }, buttonContainer: { - backgroundColor: 'white', + backgroundColor: 'transparent', width: 158, height: 37, borderRadius: 20, @@ -84,7 +89,7 @@ const styles = StyleSheet.create({ fontSize: 15, lineHeight: 17.9, alignSelf: 'center', - color: '#828282', + color: 'white', }, }); export default SearchCategories; diff --git a/src/components/search/SearchResultCell.tsx b/src/components/search/SearchResultCell.tsx index 5cba6d2f..9a8216e5 100644 --- a/src/components/search/SearchResultCell.tsx +++ b/src/components/search/SearchResultCell.tsx @@ -7,29 +7,30 @@ import {ERROR_UNABLE_TO_VIEW_PROFILE} from '../../constants/strings'; import {loadImageFromURL} from '../../services'; import {RootState} from '../../store/rootReducer'; import { + CategoryPreviewType, ProfilePreviewType, ScreenType, UserType, - CategoryPreviewType, } from '../../types'; -import {normalize, SCREEN_WIDTH} from '../../utils'; import { - addUserToRecentlyViewed, + addCategoryToRecentlySearched, + addUserToRecentlySearched, + normalize, + SCREEN_WIDTH, +} from '../../utils'; +import { checkIfUserIsBlocked, defaultUserProfile, fetchUserX, userXInStore, - addCategoryToRecentlySearched, } from '../../utils/users'; interface SearchResults { - type: 'badges' | 'categories' | 'users'; profileData: ProfilePreviewType; loggedInUser: UserType; } const SearchResultsCell: React.FC<SearchResults> = ({ - type, profileData: { id, name, @@ -74,7 +75,7 @@ const SearchResultsCell: React.FC<SearchResults> = ({ return; } - addUserToRecentlyViewed({ + addUserToRecentlySearched({ id, first_name, last_name, @@ -114,7 +115,6 @@ const SearchResultsCell: React.FC<SearchResults> = ({ const categoryObj: CategoryPreviewType = {name, category}; addCategoryToRecentlySearched(categoryObj); navigation.navigate('DiscoverUsers', { - type, searchCategory: {id, name}, }); }; @@ -172,6 +172,7 @@ const styles = StyleSheet.create({ flexDirection: 'row', paddingHorizontal: 25, paddingVertical: 15, + width: SCREEN_WIDTH, }, imageContainer: { width: SCREEN_WIDTH * 0.112, diff --git a/src/components/search/SearchResultList.tsx b/src/components/search/SearchResultList.tsx index d8cf02d9..687b2285 100644 --- a/src/components/search/SearchResultList.tsx +++ b/src/components/search/SearchResultList.tsx @@ -51,13 +51,12 @@ const SearchResultList: React.FC<SearchResultsProps> = ({ {width: SCREEN_WIDTH}, keyboardVisible ? styles.keyboardOpen : {}, ]} - contentContainerStyle={{paddingBottom: SCREEN_HEIGHT * 0.1}} + contentContainerStyle={styles.sectionListContentContainer} sections={results} keyExtractor={(item, index) => item.id + index} renderItem={({section, item}) => { return ( <SearchResultsCell - type={section.title} profileData={item} loggedInUser={loggedInUser} /> @@ -77,6 +76,10 @@ const styles = StyleSheet.create({ height: SCREEN_HEIGHT, paddingBottom: SCREEN_HEIGHT * 0.1, }, + sectionListContentContainer: { + paddingBottom: SCREEN_HEIGHT * 0.15, + width: SCREEN_WIDTH, + }, sectionHeaderStyle: { width: '100%', height: 0.5, diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx index 277b3454..ef518d8b 100644 --- a/src/components/search/SearchResults.tsx +++ b/src/components/search/SearchResults.tsx @@ -23,20 +23,26 @@ const SearchResults: React.FC<SearchResultsProps> = ({results, categories}) => { const {user: loggedInUser} = useSelector((state: RootState) => state.user); return ( <View> - {categories.map((category: CategoryPreviewType) => ( - <SearchResultsCell - key={category.name} - profileData={category} - {...{loggedInUser}} - /> - ))} - {results.map((profile: ProfilePreviewType) => ( - <SearchResultsCell - key={profile.id} - profileData={profile} - {...{loggedInUser}} - /> - ))} + {categories + .slice(0) + .reverse() + .map((category: CategoryPreviewType) => ( + <SearchResultsCell + key={category.name} + profileData={category} + {...{loggedInUser}} + /> + ))} + {results + .slice(0) + .reverse() + .map((profile: ProfilePreviewType) => ( + <SearchResultsCell + key={profile.id} + profileData={profile} + {...{loggedInUser}} + /> + ))} </View> ); }; |
