aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ios/Frontend.xcodeproj/project.pbxproj2
-rw-r--r--src/assets/images/shuffle-1.pngbin0 -> 157612 bytes
-rw-r--r--src/assets/images/shuffle-2.pngbin0 -> 149495 bytes
-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.tsx96
-rw-r--r--src/components/search/SearchResultCell.tsx17
-rw-r--r--src/components/search/SearchResultList.tsx7
-rw-r--r--src/components/search/SearchResults.tsx34
-rw-r--r--src/routes/main/MainStackNavigator.tsx1
-rw-r--r--src/routes/main/MainStackScreen.tsx3
-rw-r--r--src/screens/search/DiscoverUsers.tsx124
-rw-r--r--src/screens/search/SearchScreen.tsx72
-rw-r--r--src/services/ExploreService.ts46
-rw-r--r--src/store/actions/taggUsers.ts4
-rw-r--r--src/store/initialStates.ts14
-rw-r--r--src/store/reducers/taggUsersReducer.ts1
-rw-r--r--src/utils/common.ts21
-rw-r--r--src/utils/index.ts1
-rw-r--r--src/utils/search.ts132
-rw-r--r--src/utils/users.ts66
22 files changed, 556 insertions, 230 deletions
diff --git a/ios/Frontend.xcodeproj/project.pbxproj b/ios/Frontend.xcodeproj/project.pbxproj
index f763dd62..4cd4db33 100644
--- a/ios/Frontend.xcodeproj/project.pbxproj
+++ b/ios/Frontend.xcodeproj/project.pbxproj
@@ -442,7 +442,7 @@
name = "[CP-User] [RNFB] Core Configuration";
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n _JSON_OUTPUT_BASE64=$(python -c 'import json,sys,base64;print(base64.b64encode(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"').read())['${_JSON_ROOT}'])))' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\n\n # config.admob_delay_app_measurement_init\n _ADMOB_DELAY_APP_MEASUREMENT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"admob_delay_app_measurement_init\")\n if [[ $_ADMOB_DELAY_APP_MEASUREMENT == \"true\" ]]; then\n _PLIST_ENTRY_KEYS+=(\"GADDelayAppMeasurementInit\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"YES\")\n fi\n\n # config.admob_ios_app_id\n _ADMOB_IOS_APP_ID=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"admob_ios_app_id\")\n if [[ $_ADMOB_IOS_APP_ID ]]; then\n _PLIST_ENTRY_KEYS+=(\"GADApplicationIdentifier\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_ADMOB_IOS_APP_ID\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n";
+ shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n _JSON_OUTPUT_BASE64=$(python -c 'import json,sys,base64;print(base64.b64encode(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"').read())['${_JSON_ROOT}'])))' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\n\n # config.admob_delay_app_measurement_init\n _ADMOB_DELAY_APP_MEASUREMENT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"admob_delay_app_measurement_init\")\n if [[ $_ADMOB_DELAY_APP_MEASUREMENT == \"true\" ]]; then\n _PLIST_ENTRY_KEYS+=(\"GADDelayAppMeasurementInit\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"YES\")\n fi\n\n # config.admob_ios_app_id\n _ADMOB_IOS_APP_ID=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"admob_ios_app_id\")\n if [[ $_ADMOB_IOS_APP_ID ]]; then\n _PLIST_ENTRY_KEYS+=(\"GADApplicationIdentifier\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_ADMOB_IOS_APP_ID\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n";
};
5369A53AD507805BDB117490 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
diff --git a/src/assets/images/shuffle-1.png b/src/assets/images/shuffle-1.png
new file mode 100644
index 00000000..d28ddc21
--- /dev/null
+++ b/src/assets/images/shuffle-1.png
Binary files differ
diff --git a/src/assets/images/shuffle-2.png b/src/assets/images/shuffle-2.png
new file mode 100644
index 00000000..a6a701b0
--- /dev/null
+++ b/src/assets/images/shuffle-2.png
Binary files differ
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 f016a806..4bae27c2 100644
--- a/src/components/search/SearchCategories.tsx
+++ b/src/components/search/SearchCategories.tsx
@@ -1,46 +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 {getSuggestedSearchBubbleSuggestions} from '../../services/ExploreService';
import {SearchCategoryType} from '../../types';
-import {TAGG_LIGHT_BLUE_2, TAGG_PURPLE} from '../../constants';
import {SCREEN_WIDTH} from '../../utils';
+import GradientBorderButton from '../common/GradientBorderButton';
+
+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),
+ ]);
-const SearchCategories: React.FC = () => {
- const [buttons, setButtons] = useState<SearchCategoryType[]>([]);
useEffect(() => {
const loadButtons = async () => {
- const localButtons = await getButtons();
- 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>
);
};
@@ -55,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,
@@ -83,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>
);
};
diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx
index c60c8602..252ece3b 100644
--- a/src/routes/main/MainStackNavigator.tsx
+++ b/src/routes/main/MainStackNavigator.tsx
@@ -23,7 +23,6 @@ export type MainStackParams = {
screenType: ScreenType;
};
DiscoverUsers: {
- type: 'badges' | 'categories' | 'users';
searchCategory: SearchCategoryType;
};
Profile: {
diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx
index 068748b1..0a75dd22 100644
--- a/src/routes/main/MainStackScreen.tsx
+++ b/src/routes/main/MainStackScreen.tsx
@@ -4,11 +4,9 @@ import {StackNavigationOptions} from '@react-navigation/stack';
import React, {useState} from 'react';
import {StyleSheet, Text} from 'react-native';
import {normalize} from 'react-native-elements';
-import MutualBadgeHolders from '../../screens/suggestedPeople/MutualBadgeHolders';
import BackIcon from '../../assets/icons/back-arrow.svg';
import {
AnimatedTutorial,
- BadgeSelection,
CaptionScreen,
CategorySelection,
CreateCustomCategory,
@@ -26,6 +24,7 @@ import {
SuggestedPeopleScreen,
SuggestedPeopleUploadPictureScreen,
} from '../../screens';
+import MutualBadgeHolders from '../../screens/suggestedPeople/MutualBadgeHolders';
import {ScreenType} from '../../types';
import {AvatarHeaderHeight, SCREEN_WIDTH} from '../../utils';
import {MainStack, MainStackParams} from './MainStackNavigator';
diff --git a/src/screens/search/DiscoverUsers.tsx b/src/screens/search/DiscoverUsers.tsx
index ce7507fc..b87bfc37 100644
--- a/src/screens/search/DiscoverUsers.tsx
+++ b/src/screens/search/DiscoverUsers.tsx
@@ -1,15 +1,26 @@
+import {RouteProp, useNavigation} from '@react-navigation/native';
import React, {useEffect, useState} from 'react';
import {FlatList, StatusBar, StyleSheet} from 'react-native';
-import {Text} from 'react-native-animatable';
+import {Image, Text} from 'react-native-animatable';
+import {TouchableOpacity} from 'react-native-gesture-handler';
import {SafeAreaView} from 'react-native-safe-area-context';
-import {HeaderHeight, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
-import {SearchBackground, TabsGradient} from '../../components';
-import {RouteProp} from '@react-navigation/native';
-import {MainStackParams} from '../../routes';
-import {normalize} from '../../utils';
-import {ProfilePreviewType} from '../../types';
+import {
+ SearchBackground,
+ SearchCategories,
+ TabsGradient,
+ TaggLoadingIndicator,
+} from '../../components';
import ExploreSectionUser from '../../components/search/ExploreSectionUser';
+import {headerBarOptions, MainStackParams} from '../../routes';
import {getDiscoverUsers} from '../../services/ExploreService';
+import {ProfilePreviewType} from '../../types';
+import {
+ HeaderHeight,
+ normalize,
+ SCREEN_HEIGHT,
+ SCREEN_WIDTH,
+ shuffle,
+} from '../../utils';
type DiscoverUsersRouteProps = RouteProp<MainStackParams, 'DiscoverUsers'>;
@@ -18,16 +29,81 @@ interface DiscoverUsersProps {
}
const DiscoverUsers: React.FC<DiscoverUsersProps> = ({route}) => {
- const {type: category_type} = route.params;
- const {id, name} = route.params.searchCategory;
- const [users, setUsers] = useState<ProfilePreviewType[]>();
+ const {name} = route.params.searchCategory;
+ const [categoryName, setCategoryName] = useState<string | undefined>();
+ const [users, setUsers] = useState<ProfilePreviewType[]>([]);
+ const [shouldRefresh, setShouldRefresh] = useState(false);
+ const [showIcon1, setShowIcon1] = useState(true);
+ const mtUser = (key: number) => ({
+ id: key,
+ username: '...',
+ first_name: '',
+ last_name: '',
+ thumbnail_url: '',
+ });
+ const dummyUsers = [
+ mtUser(-1),
+ mtUser(-2),
+ mtUser(-3),
+ mtUser(-4),
+ mtUser(-5),
+ mtUser(-6),
+ mtUser(-7),
+ mtUser(-8),
+ mtUser(-9),
+ ];
+ const [loading, setLoading] = useState(true);
+ const navigation = useNavigation();
+
+ navigation.setOptions({
+ ...headerBarOptions('white', name),
+ headerRight: () => (
+ <TouchableOpacity
+ onPress={() => {
+ setShowIcon1(!showIcon1);
+ setShouldRefresh(true);
+ }}>
+ <Image
+ source={
+ showIcon1
+ ? require('../../assets/images/shuffle-1.png')
+ : require('../../assets/images/shuffle-2.png')
+ }
+ style={styles.shuffleIcon}
+ />
+ </TouchableOpacity>
+ ),
+ });
+
+ useEffect(() => {
+ setCategoryName(name);
+ }, []);
+
+ useEffect(() => {
+ if (shouldRefresh) {
+ setLoading(true);
+ setTimeout(() => {
+ setUsers(shuffle(users));
+ setShouldRefresh(false);
+ setLoading(false);
+ }, 500);
+ }
+ }, [shouldRefresh, users]);
useEffect(() => {
const loadData = async () => {
- setUsers(await getDiscoverUsers(id, category_type));
+ setLoading(true);
+ if (!categoryName) {
+ return;
+ }
+ const fetched_users = await getDiscoverUsers(categoryName);
+ if (fetched_users) {
+ setUsers(fetched_users);
+ }
+ setLoading(false);
};
loadData();
- }, []);
+ }, [categoryName]);
const _renderItem = ({item: user}: {item: ProfilePreviewType}) => (
<ExploreSectionUser key={user.id} user={user} style={styles.user} />
@@ -37,9 +113,9 @@ const DiscoverUsers: React.FC<DiscoverUsersProps> = ({route}) => {
<SearchBackground>
<StatusBar barStyle="light-content" />
<SafeAreaView>
- <Text style={styles.headerText}>{name}</Text>
+ {loading && <TaggLoadingIndicator fullscreen={true} />}
<FlatList
- data={users}
+ data={loading ? dummyUsers : users.slice(0, 9)}
style={styles.scrollView}
contentContainerStyle={styles.contentContainerStyle}
columnWrapperStyle={styles.columnWrapperStyle}
@@ -47,6 +123,12 @@ const DiscoverUsers: React.FC<DiscoverUsersProps> = ({route}) => {
keyExtractor={(item) => item.id}
renderItem={_renderItem}
showsVerticalScrollIndicator={false}
+ ListFooterComponent={() => (
+ <>
+ <Text style={styles.otherGroups}>Other Groups</Text>
+ <SearchCategories darkStyle={true} />
+ </>
+ )}
/>
<TabsGradient />
</SafeAreaView>
@@ -67,6 +149,7 @@ const styles = StyleSheet.create({
},
scrollView: {
top: HeaderHeight,
+ marginTop: '10%',
width: SCREEN_WIDTH * 0.95,
height: SCREEN_HEIGHT - HeaderHeight,
alignSelf: 'center',
@@ -83,6 +166,19 @@ const styles = StyleSheet.create({
width: SCREEN_WIDTH * 0.95,
paddingBottom: SCREEN_HEIGHT * 0.2,
},
+ otherGroups: {
+ color: 'white',
+ fontSize: normalize(18),
+ fontWeight: '600',
+ lineHeight: normalize(35),
+ alignSelf: 'center',
+ marginTop: 20,
+ },
+ shuffleIcon: {
+ width: 40,
+ height: 40,
+ marginRight: 20,
+ },
});
export default DiscoverUsers;
diff --git a/src/screens/search/SearchScreen.tsx b/src/screens/search/SearchScreen.tsx
index 728510c5..65ec3934 100644
--- a/src/screens/search/SearchScreen.tsx
+++ b/src/screens/search/SearchScreen.tsx
@@ -17,8 +17,19 @@ import {SEARCH_ENDPOINT, TAGG_LIGHT_BLUE} from '../../constants';
import {loadSearchResults} from '../../services';
import {resetScreenType} from '../../store/actions';
import {RootState} from '../../store/rootReducer';
-import {ProfilePreviewType, ScreenType, CategoryPreviewType} from '../../types';
-import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import {
+ CategoryPreviewType,
+ ProfilePreviewType,
+ ScreenType,
+ SearchCategoryType,
+} from '../../types';
+import {
+ getRecentlySearchedCategories,
+ getRecentlySearchedUsers,
+ normalize,
+ SCREEN_HEIGHT,
+ SCREEN_WIDTH,
+} from '../../utils';
/**
* Search Screen for user recommendations and a search
@@ -37,6 +48,11 @@ const SearchScreen: React.FC = () => {
>([]);
const [searching, setSearching] = useState(false);
const top = Animated.useValue(-SCREEN_HEIGHT);
+ const defaultButtons: SearchCategoryType[] = [21, 22, 23, 24].map((year) => ({
+ id: -1,
+ name: `Brown '${year}`,
+ category: 'Brown',
+ }));
const [keyboardVisible, setKeyboardVisible] = React.useState(
'keyboardVisible',
);
@@ -57,10 +73,11 @@ const SearchScreen: React.FC = () => {
* Main handler for changes in query.
*/
useEffect(() => {
- if (!searching) return;
- if (!query.length) loadRecentSearches();
+ if (!searching) {
+ return;
+ }
if (query.length < 3) {
- setResults(undefined);
+ loadRecentlySearched().then(() => setResults(undefined));
return;
}
(async () => {
@@ -101,7 +118,9 @@ const SearchScreen: React.FC = () => {
// when searching state changes, run animation and reset query accordingly
useEffect(() => {
if (searching) {
- timing(top, topInConfig).start();
+ loadRecentlySearched().then(() => {
+ timing(top, topInConfig).start();
+ });
} else {
setQuery('');
handleBlur();
@@ -109,36 +128,23 @@ const SearchScreen: React.FC = () => {
}
}, [searching]);
- const loadRecentlySearchedUsers = async () => {
- try {
- const asyncCache = await AsyncStorage.getItem('@recently_searched_users');
- asyncCache != null ? setRecents(JSON.parse(asyncCache)) : setRecents([]);
- } catch (e) {
- console.log(e);
- }
- };
- const loadRecentlySearchedCategories = async () => {
- try {
- const recentCategoriesJSON = await AsyncStorage.getItem(
- '@recently_searched_categories',
- );
- setRecentCategories(
- recentCategoriesJSON ? JSON.parse(recentCategoriesJSON) : [],
- );
- } catch (e) {
- console.log(e);
- }
- };
- const loadRecentSearches = () => {
- loadRecentlySearchedUsers();
- loadRecentlySearchedCategories();
+ // loads recent searches (users & categories) from AsyncStorage
+ const loadRecentlySearched = async () => {
+ return Promise.all([
+ getRecentlySearchedUsers(),
+ getRecentlySearchedCategories(),
+ ]).then(
+ ([users, categories]: [ProfilePreviewType[], CategoryPreviewType[]]) => {
+ setRecents(users);
+ setRecentCategories(categories);
+ },
+ );
};
const clearRecentlySearched = async () => {
try {
AsyncStorage.removeItem('@recently_searched_users');
AsyncStorage.removeItem('@recently_searched_categories');
- loadRecentlySearchedUsers();
- loadRecentlySearchedCategories();
+ loadRecentlySearched();
} catch (e) {
console.log(e);
}
@@ -173,7 +179,7 @@ const SearchScreen: React.FC = () => {
onBlur={handleBlur}
onFocus={handleFocus}
value={query}
- {...{top}}
+ {...{top, searching}}
/>
<ScrollView
scrollEnabled={!searching}
@@ -181,7 +187,7 @@ const SearchScreen: React.FC = () => {
stickyHeaderIndices={[4]}
contentContainerStyle={styles.contentContainer}
showsVerticalScrollIndicator={false}>
- <SearchCategories />
+ <SearchCategories defaultButtons={defaultButtons} />
<SearchResultsBackground {...{top}}>
{results === undefined &&
recents.length + recentCategories.length !== 0 ? (
diff --git a/src/services/ExploreService.ts b/src/services/ExploreService.ts
index 9b0b4f71..07af91ad 100644
--- a/src/services/ExploreService.ts
+++ b/src/services/ExploreService.ts
@@ -68,13 +68,10 @@ export const getAllExploreSections = async () => {
}
};
-export const getDiscoverUsers = async (id: number, category_type: string) => {
+export const getDiscoverUsers = async (categoryName: string) => {
try {
const token = await AsyncStorage.getItem('token');
- let url = DISCOVER_ENDPOINT + `${id}/`;
- if (category_type === 'badges') {
- url += '?type=badge';
- }
+ const url = `${DISCOVER_ENDPOINT}get_users/?category=${categoryName}`;
const response = await fetch(url, {
method: 'GET',
headers: {
@@ -82,31 +79,36 @@ export const getDiscoverUsers = async (id: number, category_type: string) => {
},
});
if (response.status !== 200) {
- return EMPTY_PROFILE_PREVIEW_LIST;
+ return undefined;
}
- const data = await response.json();
- const users: ProfilePreviewType[] = data.users;
+ const users: ProfilePreviewType[] = await response.json();
return users;
} catch (error) {
console.log('Error fetching SP user data');
console.log(error);
- return [];
+ return undefined;
}
};
-export const getButtons = async () => {
- const token = await AsyncStorage.getItem('token');
- const response = await fetch(SEARCH_BUTTONS_ENDPOPINT, {
- method: 'GET',
- headers: {
- Authorization: 'Token ' + token,
- },
- });
+export const getSuggestedSearchBubbleSuggestions = async () => {
+ try {
+ const token = await AsyncStorage.getItem('token');
+ const response = await fetch(SEARCH_BUTTONS_ENDPOPINT, {
+ method: 'GET',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
+ });
+
+ if (response.status !== 200) {
+ return undefined;
+ }
- if (response.status !== 200) {
- return [];
+ const data: SearchCategoryType[] = await response.json();
+ return data;
+ } catch (error) {
+ console.log('Error fetching suggested search bubble suggestions');
+ console.log(error);
+ return undefined;
}
-
- const data: SearchCategoryType[] = await response.json();
- return data;
};
diff --git a/src/store/actions/taggUsers.ts b/src/store/actions/taggUsers.ts
index 7b6d4d5e..72ce848b 100644
--- a/src/store/actions/taggUsers.ts
+++ b/src/store/actions/taggUsers.ts
@@ -11,10 +11,10 @@ export const loadRecentlySearched = (): ThunkAction<
> => async (dispatch) => {
try {
const recentSearches = await loadRecentlySearchedUsers();
- const exploreSections = await getAllExploreSections();
+ getAllExploreSections();
dispatch({
type: taggUsersFetched.type,
- payload: {recentSearches, explores: exploreSections},
+ payload: {recentSearches},
});
} catch (error) {
console.log(error);
diff --git a/src/store/initialStates.ts b/src/store/initialStates.ts
index 1a3db433..b43e4a1d 100644
--- a/src/store/initialStates.ts
+++ b/src/store/initialStates.ts
@@ -69,22 +69,8 @@ export const NO_SOCIAL_ACCOUNTS: Record<string, SocialAccountType> = {
Twitter: {posts: []},
};
-export const EMPTY_EXPLORE_SECTIONS: Record<
- ExploreSectionType,
- ProfilePreviewType[]
-> = {
- 'People You May Know': EMPTY_PROFILE_PREVIEW_LIST,
- 'New to Tagg': EMPTY_PROFILE_PREVIEW_LIST,
- 'Trending on Tagg': EMPTY_PROFILE_PREVIEW_LIST,
- "Brown '21": EMPTY_PROFILE_PREVIEW_LIST,
- "Brown '22": EMPTY_PROFILE_PREVIEW_LIST,
- "Brown '23": EMPTY_PROFILE_PREVIEW_LIST,
- "Brown '24": EMPTY_PROFILE_PREVIEW_LIST,
-};
-
export const NO_TAGG_USERS = {
recentSearches: EMPTY_PROFILE_PREVIEW_LIST,
- explores: EMPTY_EXPLORE_SECTIONS,
};
export const NO_SOCIALS = {
diff --git a/src/store/reducers/taggUsersReducer.ts b/src/store/reducers/taggUsersReducer.ts
index 33e2e18d..92973101 100644
--- a/src/store/reducers/taggUsersReducer.ts
+++ b/src/store/reducers/taggUsersReducer.ts
@@ -7,7 +7,6 @@ const taggUsersSlice = createSlice({
reducers: {
taggUsersFetched: (state, action) => {
state.recentSearches = action.payload.recentSearches;
- state.explores = action.payload.explores;
},
},
});
diff --git a/src/utils/common.ts b/src/utils/common.ts
index 30122e79..c1049c42 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -94,3 +94,24 @@ export const haveUnreadNotifications = async (
}
return false;
};
+
+// https://stackoverflow.com/a/2450976
+export const shuffle = (array: any[]) => {
+ var currentIndex = array.length,
+ temporaryValue,
+ randomIndex;
+
+ // While there remain elements to shuffle...
+ while (currentIndex !== 0) {
+ // Pick a remaining element...
+ randomIndex = Math.floor(Math.random() * currentIndex);
+ currentIndex -= 1;
+
+ // And swap it with the current element.
+ temporaryValue = array[currentIndex];
+ array[currentIndex] = array[randomIndex];
+ array[randomIndex] = temporaryValue;
+ }
+
+ return array;
+};
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 82c94100..739e6fb8 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -3,3 +3,4 @@ export * from './moments';
export * from './common';
export * from './users';
export * from './friends';
+export * from './search';
diff --git a/src/utils/search.ts b/src/utils/search.ts
new file mode 100644
index 00000000..4293ff25
--- /dev/null
+++ b/src/utils/search.ts
@@ -0,0 +1,132 @@
+import AsyncStorage from '@react-native-community/async-storage';
+
+import {BADGE_DATA} from '../constants/badges';
+import {ProfilePreviewType, CategoryPreviewType} from '../types';
+
+/*
+ * Gets all possible search suggestions.
+ */
+export const getSearchSuggestions = (): string[] => {
+ const suggestions: string[] = [];
+ for (const category of BADGE_DATA) {
+ for (const interest of category.data) {
+ suggestions.push(interest.badgeName);
+ }
+ }
+ return suggestions;
+};
+
+/*
+ * AsyncStorage key for list of recently-searched users.
+ */
+const recentlySearchedUsersKey = '@recently_searched_users';
+
+/*
+ * Stores `user` in AsyncStorage as a recently-searched user.
+ */
+export const addUserToRecentlySearched = async (user: ProfilePreviewType) => {
+ let users: ProfilePreviewType[];
+ // retrieve and update recently-searched categories list
+ try {
+ const usersJSON = await AsyncStorage.getItem(recentlySearchedUsersKey);
+ if (usersJSON) {
+ users = JSON.parse(usersJSON);
+ // if category already exists, move it to the end
+ for (let i = 0; i < users.length; i++) {
+ // TODO: speed up comparison by adding some id field to category
+ if (users[i].id === user.id) {
+ users.splice(i, 1);
+ break;
+ }
+ }
+ users.push(user);
+ } else {
+ users = [user];
+ }
+ // store updated list of recently-searched categories
+ try {
+ AsyncStorage.setItem(recentlySearchedUsersKey, JSON.stringify(users));
+ } catch (e) {
+ console.log(e);
+ }
+ } catch (e) {
+ console.log(e);
+ }
+};
+
+/*
+ * Retrieves and returns user's recently-searched categories from AsyncStorage.
+ */
+export const getRecentlySearchedUsers = async (): Promise<
+ ProfilePreviewType[]
+> => {
+ try {
+ const usersJSON = await AsyncStorage.getItem(recentlySearchedUsersKey);
+ if (usersJSON) return JSON.parse(usersJSON);
+ } catch (e) {
+ console.log(e);
+ }
+ return [];
+};
+
+/*
+ * AsyncStorage key for list of recently-searched categories.
+ */
+const recentlySearchedCategoriesKey = '@recently_searched_categories';
+
+/*
+ * Stores `category` in AsyncStorage as a recently-searched category.
+ */
+export const addCategoryToRecentlySearched = async (
+ category: CategoryPreviewType,
+) => {
+ let categories: CategoryPreviewType[];
+ // retrieve and update recently-searched categories list
+ try {
+ const categoriesJSON = await AsyncStorage.getItem(
+ recentlySearchedCategoriesKey,
+ );
+ if (categoriesJSON) {
+ categories = JSON.parse(categoriesJSON);
+ // if category already exists, move it to the end
+ for (let i = 0; i < categories.length; i++) {
+ // TODO: speed up comparison by adding some id field to category
+ if (categories[i].name === category.name) {
+ categories.splice(i, 1);
+ break;
+ }
+ }
+ categories.push(category);
+ } else {
+ categories = [category];
+ }
+ // store updated list of recently-searched categories
+ try {
+ AsyncStorage.setItem(
+ recentlySearchedCategoriesKey,
+ JSON.stringify(categories),
+ );
+ } catch (e) {
+ console.log(e);
+ }
+ } catch (e) {
+ console.log(e);
+ }
+};
+
+/*
+ * Retrieves and returns user's recently-searched categories from AsyncStorage.
+ */
+export const getRecentlySearchedCategories = async (): Promise<
+ CategoryPreviewType[]
+> => {
+ try {
+ const categoriesJSON = await AsyncStorage.getItem(
+ '@recently_searched_categories',
+ );
+ if (categoriesJSON) return JSON.parse(categoriesJSON);
+ } catch (e) {
+ console.log(e);
+ }
+ return [];
+};
diff --git a/src/utils/users.ts b/src/utils/users.ts
index 15107c99..af4f3813 100644
--- a/src/utils/users.ts
+++ b/src/utils/users.ts
@@ -166,69 +166,3 @@ export const defaultUserProfile = () => {
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);
- }
-};
-
-/*
- * Stores `category` in AsyncStorage as a recently searched category.
- */
-export const addCategoryToRecentlySearched = async (
- category: CategoryPreviewType,
-) => {
- const recentlySearchedCategoriesKey = '@recently_searched_categories';
- let categories: CategoryPreviewType[];
- // retrieve recently-searched categories and set new list
- try {
- const categoriesJSON = await AsyncStorage.getItem(
- recentlySearchedCategoriesKey,
- );
- if (categoriesJSON) {
- categories = JSON.parse(categoriesJSON);
- // TODO: make this more efficient by comparing shorter key
- if (categories.find((c) => c.name === category.name)) return;
- categories.push(category);
- } else {
- categories = [category];
- }
- // store updated list of recently-searched categories
- try {
- AsyncStorage.setItem(
- recentlySearchedCategoriesKey,
- JSON.stringify(categories),
- );
- } catch (e) {
- console.log(e);
- }
- } catch (e) {
- console.log(e);
- }
-};