aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Shillingford <jgs272@cornell.edu>2020-08-19 13:27:22 -0400
committerGitHub <noreply@github.com>2020-08-19 13:27:22 -0400
commit7596b69482914569cbb4bb5f287bbc0a72d74133 (patch)
tree198b99e60066ba316fb2a13b9f9b7858cd86cd50
parent611dac558d37ce8153dfbef00964833fd976cc31 (diff)
[TMA-161] Recently Searched Users (#34)
* Basic AsyncStorage code * Basic implementation complete Need to fix re-rendering bug * Removed errant comment * Fixed bug for rerendering upon addition to recents Need to fix bug for rerendering upon clearing * Fixed rerendering bug for clear * Only present recents header if users in recents * Lint cleaning * Basic AsyncStorage code * Basic implementation complete Need to fix re-rendering bug * Removed errant comment * Fixed bug for rerendering upon addition to recents Need to fix bug for rerendering upon clearing * Fixed rerendering bug for clear * Only present recents header if users in recents * Lint cleaning * Added comments for a function * Updated conditional presentation to use ternary * Created component for List Section Headers * Lint cleaning * Converted component to be for Recent Searches As opposed to just the list header
-rw-r--r--ios/Podfile.lock6
-rw-r--r--package.json1
-rw-r--r--src/components/common/index.ts1
-rw-r--r--src/components/search/RecentSearches.tsx52
-rw-r--r--src/components/search/SearchResult.tsx53
-rw-r--r--src/routes/authentication/AuthProvider.tsx25
-rw-r--r--src/screens/search/SearchScreen.tsx53
-rw-r--r--yarn.lock19
8 files changed, 206 insertions, 4 deletions
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 87cc0258..54909b3d 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -298,6 +298,8 @@ PODS:
- ReactCommon/callinvoker (= 0.62.2)
- rn-fetch-blob (0.12.0):
- React-Core
+ - RNCAsyncStorage (1.12.0):
+ - React
- RNCMaskedView (0.1.10):
- React
- RNGestureHandler (1.7.0):
@@ -374,6 +376,7 @@ DEPENDENCIES:
- ReactCommon/callinvoker (from `../node_modules/react-native/ReactCommon`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- rn-fetch-blob (from `../node_modules/rn-fetch-blob`)
+ - "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)"
- "RNCMaskedView (from `../node_modules/@react-native-community/masked-view`)"
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
@@ -454,6 +457,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon"
rn-fetch-blob:
:path: "../node_modules/rn-fetch-blob"
+ RNCAsyncStorage:
+ :path: "../node_modules/@react-native-community/async-storage"
RNCMaskedView:
:path: "../node_modules/@react-native-community/masked-view"
RNGestureHandler:
@@ -510,6 +515,7 @@ SPEC CHECKSUMS:
React-RCTVibration: 4356114dbcba4ce66991096e51a66e61eda51256
ReactCommon: ed4e11d27609d571e7eee8b65548efc191116eb3
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
+ RNCAsyncStorage: 2a692bcb9b69b76a2f1a95f33db057129700af64
RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f
RNGestureHandler: b6b359bb800ae399a9c8b27032bdbf7c18f08a08
RNImageCropPicker: f0557a908758c4a3f83978894ec7227651529b45
diff --git a/package.json b/package.json
index 7f47e1af..96c7d401 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --fix"
},
"dependencies": {
+ "@react-native-community/async-storage": "^1.12.0",
"@react-native-community/masked-view": "^0.1.10",
"@react-navigation/bottom-tabs": "^5.7.2",
"@react-navigation/native": "^5.6.1",
diff --git a/src/components/common/index.ts b/src/components/common/index.ts
index 63a7b9c2..c9c4f27a 100644
--- a/src/components/common/index.ts
+++ b/src/components/common/index.ts
@@ -6,3 +6,4 @@ export {default as GradientBackground} from './GradientBackground';
export {default as Post} from './post';
export {default as SocialIcon} from './SocialIcon';
export {default as TabsGradient} from './TabsGradient';
+export {default as RecentSearches} from '../search/RecentSearches';
diff --git a/src/components/search/RecentSearches.tsx b/src/components/search/RecentSearches.tsx
new file mode 100644
index 00000000..a5c08984
--- /dev/null
+++ b/src/components/search/RecentSearches.tsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import {
+ View,
+ Text,
+ TouchableOpacity,
+ StyleSheet,
+ TouchableOpacityProps,
+} from 'react-native';
+import {ProfilePreviewType} from 'src/types';
+import SearchResults from './SearchResults';
+
+interface RecentSearchesProps extends TouchableOpacityProps {
+ sectionTitle: string;
+ sectionButtonTitle: string;
+ recents: Array<ProfilePreviewType>;
+}
+/**
+ * An image component that returns the <Image> of the icon for a specific social media platform.
+ */
+const RecentSearches: React.FC<RecentSearchesProps> = (props) => {
+ return (
+ <>
+ <View style={styles.container}>
+ <Text style={styles.title}>{props.sectionTitle}</Text>
+ {props.sectionButtonTitle && (
+ <TouchableOpacity {...props}>
+ <Text style={styles.clear}>Clear all</Text>
+ </TouchableOpacity>
+ )}
+ </View>
+ <SearchResults results={props.recents} />
+ </>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ },
+ title: {
+ fontSize: 17,
+ fontWeight: 'bold',
+ flexGrow: 1,
+ },
+ clear: {
+ fontSize: 17,
+ fontWeight: 'bold',
+ color: '#698DD3',
+ },
+});
+
+export default RecentSearches;
diff --git a/src/components/search/SearchResult.tsx b/src/components/search/SearchResult.tsx
index 60c22d41..e65be1f4 100644
--- a/src/components/search/SearchResult.tsx
+++ b/src/components/search/SearchResult.tsx
@@ -9,6 +9,7 @@ import {
TouchableOpacity,
} from 'react-native';
import RNFetchBlob from 'rn-fetch-blob';
+import AsyncStorage from '@react-native-community/async-storage';
import {AVATAR_PHOTO_ENDPOINT} from '../../constants';
interface SearchResultProps extends ViewProps {
@@ -48,8 +49,58 @@ const SearchResult: React.FC<SearchResultProps> = ({
};
}, [id]);
+ /**
+ * Adds a searched user to the recently searched cache if they're tapped on.
+ * Cache maintains 10 recently searched users, popping off the oldest one if
+ * needed to make space.
+ */
+ const addToRecentlyStored = async () => {
+ let user: ProfilePreviewType = {
+ id,
+ username,
+ first_name,
+ last_name,
+ };
+ try {
+ 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 === 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);
+ }
+ } catch (e) {
+ console.log(e);
+ }
+ };
+
return (
- <TouchableOpacity style={[styles.container, style]}>
+ <TouchableOpacity
+ onPress={addToRecentlyStored}
+ style={[styles.container, style]}>
<Image
style={styles.avatar}
source={
diff --git a/src/routes/authentication/AuthProvider.tsx b/src/routes/authentication/AuthProvider.tsx
index e52d56bc..589cb051 100644
--- a/src/routes/authentication/AuthProvider.tsx
+++ b/src/routes/authentication/AuthProvider.tsx
@@ -1,7 +1,13 @@
import React, {useEffect} from 'react';
import {createContext, useState} from 'react';
import RNFetchBlob from 'rn-fetch-blob';
-import {UserType, ProfileType, InstagramPostType} from '../../types';
+import AsyncStorage from '@react-native-community/async-storage';
+import {
+ UserType,
+ ProfileType,
+ InstagramPostType,
+ ProfilePreviewType,
+} from '../../types';
import {
PROFILE_INFO_ENDPOINT,
AVATAR_PHOTO_ENDPOINT,
@@ -17,6 +23,7 @@ interface AuthContextProps {
avatar: string | null;
cover: string | null;
instaPosts: Array<InstagramPostType>;
+ recentSearches: Array<ProfilePreviewType>;
}
const NO_USER: UserType = {
userId: '',
@@ -35,6 +42,7 @@ export const AuthContext = createContext<AuthContextProps>({
avatar: null,
cover: null,
instaPosts: [],
+ recentSearches: [],
});
/**
@@ -46,6 +54,9 @@ const AuthProvider: React.FC = ({children}) => {
const [avatar, setAvatar] = useState<string | null>(null);
const [cover, setCover] = useState<string | null>(null);
const [instaPosts, setInstaPosts] = useState<Array<InstagramPostType>>([]);
+ const [recentSearches, setRecentSearches] = useState<
+ Array<ProfilePreviewType>
+ >([]);
const {userId} = user;
useEffect(() => {
@@ -115,10 +126,21 @@ const AuthProvider: React.FC = ({children}) => {
console.log(error);
}
};
+ const loadRecentlySearchedUsers = async () => {
+ try {
+ const asyncCache = await AsyncStorage.getItem(
+ '@recently_searched_users',
+ );
+ asyncCache != null ? setRecentSearches(JSON.parse(asyncCache)) : null;
+ } catch (e) {
+ console.log(e);
+ }
+ };
loadProfileInfo();
loadAvatar();
loadCover();
loadInstaPosts();
+ loadRecentlySearchedUsers();
}, [userId]);
return (
@@ -135,6 +157,7 @@ const AuthProvider: React.FC = ({children}) => {
logout: () => {
setUser(NO_USER);
},
+ recentSearches,
}}>
{children}
</AuthContext.Provider>
diff --git a/src/screens/search/SearchScreen.tsx b/src/screens/search/SearchScreen.tsx
index 94b9ab41..d85c0a90 100644
--- a/src/screens/search/SearchScreen.tsx
+++ b/src/screens/search/SearchScreen.tsx
@@ -8,11 +8,14 @@ import {
SearchResultsBackground,
SearchResults,
TabsGradient,
+ RecentSearches,
} from '../../components';
import {SCREEN_HEIGHT, StatusBarHeight} from '../../utils';
import Animated, {Easing, timing} from 'react-native-reanimated';
+import AsyncStorage from '@react-native-community/async-storage';
import {ProfilePreviewType} from '../../types';
import {SEARCH_ENDPOINT} from '../../constants';
+import {AuthContext} from '../../routes/authentication';
const {Value} = Animated;
/**
@@ -22,8 +25,12 @@ const {Value} = Animated;
const top: Animated.Value<number> = new Value(-SCREEN_HEIGHT);
const SearchScreen: React.FC = () => {
+ const {recentSearches} = React.useContext(AuthContext);
const [query, setQuery] = useState<string>('');
const [results, setResults] = useState<Array<ProfilePreviewType>>([]);
+ const [recents, setRecents] = useState<Array<ProfilePreviewType>>(
+ recentSearches,
+ );
useEffect(() => {
if (query.length < 3) {
setResults([]);
@@ -66,6 +73,26 @@ const SearchScreen: React.FC = () => {
};
timing(top, topOutConfig).start();
};
+ 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 clearRecentlySearched = async () => {
+ try {
+ await AsyncStorage.removeItem('@recently_searched_users');
+ loadRecentlySearchedUsers();
+ } catch (e) {
+ console.log(e);
+ }
+ };
+ const handleUpdate = async (val: string) => {
+ setQuery(val);
+ loadRecentlySearchedUsers();
+ };
return (
<SearchBackground>
@@ -79,7 +106,7 @@ const SearchScreen: React.FC = () => {
<SearchBar
style={styles.searchBar}
onCancel={handleBlur}
- onChangeText={setQuery}
+ onChangeText={handleUpdate}
onBlur={Keyboard.dismiss}
onFocus={handleFocus}
value={query}
@@ -87,7 +114,16 @@ const SearchScreen: React.FC = () => {
/>
<Explore />
<SearchResultsBackground {...{top}}>
- <SearchResults {...{results}} />
+ {results.length === 0 && recents.length !== 0 ? (
+ <RecentSearches
+ sectionTitle="Recent"
+ sectionButtonTitle="Clear all"
+ onPress={clearRecentlySearched}
+ recents={recents}
+ />
+ ) : (
+ <SearchResults {...{results}} />
+ )}
</SearchResultsBackground>
</ScrollView>
<TabsGradient />
@@ -108,5 +144,18 @@ const styles = StyleSheet.create({
marginVertical: 20,
zIndex: 1,
},
+ recentsHeaderContainer: {
+ flexDirection: 'row',
+ },
+ recentsHeader: {
+ fontSize: 17,
+ fontWeight: 'bold',
+ flexGrow: 1,
+ },
+ clear: {
+ fontSize: 17,
+ fontWeight: 'bold',
+ color: '#698DD3',
+ },
});
export default SearchScreen;
diff --git a/yarn.lock b/yarn.lock
index f08ea3ec..372f0ccd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -877,6 +877,13 @@
"@types/yargs" "^15.0.0"
chalk "^3.0.0"
+"@react-native-community/async-storage@^1.12.0":
+ version "1.12.0"
+ resolved "https://registry.yarnpkg.com/@react-native-community/async-storage/-/async-storage-1.12.0.tgz#d2fc65bc08aa1c3e9514bbe9fe7095eab8e8aca3"
+ integrity sha512-y3zVxuVyiOxI8TXrvajmYfDbIt2vFNxzV5MiA28v15DQTxDk6uJH3rpc9my+la7u2Tiwt3PpdU2+59ZgZ4h7wA==
+ dependencies:
+ deep-assign "^3.0.0"
+
"@react-native-community/cli-debugger-ui@^4.9.0":
version "4.9.0"
resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-4.9.0.tgz#4177764ba69243c97aa26829d59d9501acb2bd71"
@@ -2225,6 +2232,13 @@ decode-uri-component@^0.2.0:
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
+deep-assign@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/deep-assign/-/deep-assign-3.0.0.tgz#c8e4c4d401cba25550a2f0f486a2e75bc5f219a2"
+ integrity sha512-YX2i9XjJ7h5q/aQ/IM9PEwEnDqETAIYbggmdDB3HLTlSgo1CxPsj6pvhPG68rq6SVE0+p+6Ywsm5fTYNrYtBWw==
+ dependencies:
+ is-obj "^1.0.0"
+
deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@@ -3507,6 +3521,11 @@ is-number@^3.0.0:
dependencies:
kind-of "^3.0.2"
+is-obj@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
+ integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
+
is-plain-object@^2.0.3, is-plain-object@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"