aboutsummaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
authorIvan Chen <ivan@tagg.id>2021-03-12 17:25:38 -0500
committerIvan Chen <ivan@tagg.id>2021-03-12 17:25:38 -0500
commitd58c534bff5fbe2941fe46f9ad4a3bfb8e4b3700 (patch)
treed3a573417c19a9e1199e40015819a524536e5df9 /src/components
parente645175094525c38beedb9227052582117e4661b (diff)
parent34d94e1dff831aafa984a2f6c9b9b01e2e349154 (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.tsx66
-rw-r--r--src/components/common/index.ts1
-rw-r--r--src/components/search/SearchBar.tsx78
-rw-r--r--src/components/search/SearchCategories.tsx97
-rw-r--r--src/components/search/SearchResultCell.tsx17
-rw-r--r--src/components/search/SearchResultList.tsx7
-rw-r--r--src/components/search/SearchResults.tsx34
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>
);
};