aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Chen <ivan@tagg.id>2021-04-07 16:36:34 -0400
committerGitHub <noreply@github.com>2021-04-07 16:36:34 -0400
commit4cf3bc720ebcc0b16d158caf60fbdf091621c327 (patch)
tree97d8db434060a7bf8579bb2975f9be25331ecb73 /src
parenta3abb3abe322ea84306e1a12cec46972a81a37de (diff)
parent6db092b4b88a71c53088a14e330ec73e208ad958 (diff)
Merge pull request #354 from TaggiD-Inc/chat-poc
[POC] Chat
Diffstat (limited to 'src')
-rw-r--r--src/App.tsx43
-rw-r--r--src/assets/images/cover-placeholder@2x.pngbin8875 -> 9867 bytes
-rw-r--r--src/assets/images/cover-placeholder@3x.pngbin13944 -> 12116 bytes
-rw-r--r--src/components/common/BottomDrawer.tsx18
-rw-r--r--src/components/common/GradientBorderButton.tsx2
-rw-r--r--src/components/common/TabsGradient.tsx2
-rw-r--r--src/components/index.ts1
-rw-r--r--src/components/messages/MessagesHeader.tsx51
-rw-r--r--src/components/messages/index.ts1
-rw-r--r--src/components/profile/Content.tsx55
-rw-r--r--src/components/profile/Cover.tsx2
-rw-r--r--src/components/profile/PublicProfile.tsx2
-rw-r--r--src/components/search/RecentSearches.tsx31
-rw-r--r--src/components/search/SearchBar.tsx33
-rw-r--r--src/components/search/SearchCategories.tsx68
-rw-r--r--src/components/search/SearchResultList.tsx85
-rw-r--r--src/components/search/SearchResults.tsx14
-rw-r--r--src/components/search/SearchResultsBackground.tsx65
-rw-r--r--src/components/suggestedPeople/SPTaggsBar.tsx133
-rw-r--r--src/components/suggestedPeople/index.ts1
-rw-r--r--src/components/taggs/TaggPostFooter.tsx2
-rw-r--r--src/components/taggs/TaggsBar.tsx89
-rw-r--r--src/constants/api.ts10
-rw-r--r--src/screens/chat/ChatListScreen.tsx90
-rw-r--r--src/screens/chat/ChatScreen.tsx36
-rw-r--r--src/screens/onboarding/Login.tsx1
-rw-r--r--src/screens/profile/ProfileScreen.tsx28
-rw-r--r--src/screens/profile/SettingsScreen.tsx6
-rw-r--r--src/screens/search/DiscoverUsers.tsx2
-rw-r--r--src/screens/search/SearchScreen.tsx180
-rw-r--r--src/screens/suggestedPeople/SPBody.tsx20
-rw-r--r--src/screens/suggestedPeople/SuggestedPeopleScreen.tsx2
-rw-r--r--src/store/actions/user.ts16
-rw-r--r--src/types/types.ts39
34 files changed, 680 insertions, 448 deletions
diff --git a/src/App.tsx b/src/App.tsx
index ea3617dc..9510c193 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,18 +1,57 @@
import {NavigationContainer} from '@react-navigation/native';
-import React from 'react';
+import React, {useState} from 'react';
import {Provider} from 'react-redux';
+import {Channel as ChannelType, StreamChat} from 'stream-chat';
+import {OverlayProvider} from 'stream-chat-react-native';
+import {STREAM_CHAT_API} from './constants';
import {navigationRef} from './RootNavigation';
import Routes from './routes';
import store from './store/configureStore';
+import {
+ ChatContextType,
+ LocalAttachmentType,
+ LocalChannelType,
+ LocalCommandType,
+ LocalEventType,
+ LocalMessageType,
+ LocalResponseType,
+ LocalUserType,
+} from './types';
+
+export const ChatContext = React.createContext({} as ChatContextType);
const App = () => {
+ const [channel, setChannel] = useState<
+ ChannelType<
+ LocalAttachmentType,
+ LocalChannelType,
+ LocalCommandType,
+ LocalEventType,
+ LocalMessageType,
+ LocalResponseType,
+ LocalUserType
+ >
+ >();
+ const chatClient = StreamChat.getInstance<
+ LocalAttachmentType,
+ LocalChannelType,
+ LocalCommandType,
+ LocalEventType,
+ LocalMessageType,
+ LocalResponseType,
+ LocalUserType
+ >(STREAM_CHAT_API);
return (
/**
* This is the provider from the redux store, it acts as the root provider for our application
*/
<Provider store={store}>
<NavigationContainer ref={navigationRef}>
- <Routes />
+ <ChatContext.Provider value={{channel, setChannel, chatClient}}>
+ <OverlayProvider>
+ <Routes />
+ </OverlayProvider>
+ </ChatContext.Provider>
</NavigationContainer>
</Provider>
);
diff --git a/src/assets/images/cover-placeholder@2x.png b/src/assets/images/cover-placeholder@2x.png
index 402ac1fe..70294346 100644
--- a/src/assets/images/cover-placeholder@2x.png
+++ b/src/assets/images/cover-placeholder@2x.png
Binary files differ
diff --git a/src/assets/images/cover-placeholder@3x.png b/src/assets/images/cover-placeholder@3x.png
index be87023d..66fa3ce1 100644
--- a/src/assets/images/cover-placeholder@3x.png
+++ b/src/assets/images/cover-placeholder@3x.png
Binary files differ
diff --git a/src/components/common/BottomDrawer.tsx b/src/components/common/BottomDrawer.tsx
index bef9434a..988c1e79 100644
--- a/src/components/common/BottomDrawer.tsx
+++ b/src/components/common/BottomDrawer.tsx
@@ -6,7 +6,7 @@ import {
View,
ViewProps,
} from 'react-native';
-import Animated from 'react-native-reanimated';
+import Animated, {interpolateColors} from 'react-native-reanimated';
import BottomSheet from 'reanimated-bottom-sheet';
import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
@@ -50,6 +50,10 @@ const BottomDrawer: React.FC<BottomDrawerProps> = (props) => {
);
};
+ const backgroundColor = interpolateColors(bgAlpha, {
+ inputRange: [0, 1],
+ outputColorRange: ['rgba(0,0,0,0.3)', 'rgba(0,0,0,0)'],
+ });
return (
<Modal
transparent
@@ -75,17 +79,7 @@ const BottomDrawer: React.FC<BottomDrawerProps> = (props) => {
onPress={() => {
setIsOpen(false);
}}>
- <Animated.View
- style={[
- styles.backgroundView,
- {
- backgroundColor: Animated.interpolateColors(bgAlpha, {
- inputRange: [0, 1],
- outputColorRange: ['rgba(0,0,0,0.3)', 'rgba(0,0,0,0)'],
- }),
- },
- ]}
- />
+ <Animated.View style={[styles.backgroundView, {backgroundColor}]} />
</TouchableWithoutFeedback>
</Modal>
);
diff --git a/src/components/common/GradientBorderButton.tsx b/src/components/common/GradientBorderButton.tsx
index 32ac5c52..a5dbde9d 100644
--- a/src/components/common/GradientBorderButton.tsx
+++ b/src/components/common/GradientBorderButton.tsx
@@ -42,7 +42,7 @@ const GradientBorderButton: React.FC<GradientBorderButtonProps> = ({
};
const styles = StyleSheet.create({
container: {
- marginVertical: 15,
+ marginVertical: 10,
},
gradientContainer: {
width: SCREEN_WIDTH / 2 - 40,
diff --git a/src/components/common/TabsGradient.tsx b/src/components/common/TabsGradient.tsx
index a95e8bc3..07c55042 100644
--- a/src/components/common/TabsGradient.tsx
+++ b/src/components/common/TabsGradient.tsx
@@ -14,7 +14,7 @@ const TabsGradient: React.FC = () => {
};
const styles = StyleSheet.create({
gradient: {
- position: 'absolute',
+ ...StyleSheet.absoluteFillObject,
top: (SCREEN_HEIGHT / 10) * 9,
height: SCREEN_HEIGHT / 10,
width: SCREEN_WIDTH,
diff --git a/src/components/index.ts b/src/components/index.ts
index d5649323..47dc583b 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -6,3 +6,4 @@ export * from './taggs';
export * from './comments';
export * from './moments';
export * from './suggestedPeople';
+export * from './messages';
diff --git a/src/components/messages/MessagesHeader.tsx b/src/components/messages/MessagesHeader.tsx
new file mode 100644
index 00000000..d8445580
--- /dev/null
+++ b/src/components/messages/MessagesHeader.tsx
@@ -0,0 +1,51 @@
+import * as React from 'react';
+import {StyleSheet, View} from 'react-native';
+import {Text} from 'react-native-animatable';
+import {TouchableOpacity} from 'react-native-gesture-handler';
+import {normalize} from '../../utils';
+
+type MessagesHeaderProps = {
+ createChannel: () => void;
+};
+
+const MessagesHeader: React.FC<MessagesHeaderProps> = ({createChannel}) => {
+ return (
+ <View style={styles.header}>
+ <Text style={styles.headerText}>Messages</Text>
+ <Text style={styles.unreadText}>2 unread</Text>
+ <View style={styles.flex} />
+ <TouchableOpacity
+ style={styles.compose}
+ onPress={createChannel}>
+ <Text>Compose</Text>
+ </TouchableOpacity>
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ flex: {
+ flex: 1,
+ },
+ header: {
+ marginHorizontal: '8%',
+ marginTop: '5%',
+ alignItems: 'center',
+ flexDirection: 'row',
+ },
+ headerText: {
+ fontWeight: '700',
+ fontSize: normalize(18),
+ lineHeight: normalize(21),
+ },
+ unreadText: {
+ color: '#8F01FF',
+ marginLeft: 10,
+ fontWeight: '700',
+ lineHeight: normalize(17),
+ fontSize: normalize(14),
+ },
+ compose: {},
+});
+
+export default MessagesHeader;
diff --git a/src/components/messages/index.ts b/src/components/messages/index.ts
new file mode 100644
index 00000000..2d6bb581
--- /dev/null
+++ b/src/components/messages/index.ts
@@ -0,0 +1 @@
+export {default as MessagesHeader} from './MessagesHeader';
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx
index fef92dc1..05098d14 100644
--- a/src/components/profile/Content.tsx
+++ b/src/components/profile/Content.tsx
@@ -1,14 +1,10 @@
import React, {useCallback, useEffect, useRef, useState} from 'react';
-import {
- LayoutChangeEvent,
- NativeScrollEvent,
- NativeSyntheticEvent,
- RefreshControl,
- StyleSheet,
-} from 'react-native';
-import Animated from 'react-native-reanimated';
+import {LayoutChangeEvent, RefreshControl, StyleSheet} from 'react-native';
+import Animated, {
+ useSharedValue,
+ useAnimatedScrollHandler,
+} from 'react-native-reanimated';
import {useDispatch, useSelector, useStore} from 'react-redux';
-import {COVER_HEIGHT} from '../../constants';
import {
blockUnblockUser,
loadFriendsData,
@@ -20,12 +16,11 @@ import {
NO_USER,
} from '../../store/initialStates';
import {RootState} from '../../store/rootreducer';
-import {ContentProps} from '../../types';
+import {ScreenType} from '../../types';
import {
canViewProfile,
fetchUserX,
getUserAsProfilePreviewType,
- SCREEN_HEIGHT,
userLogin,
} from '../../utils';
import TaggsBar from '../taggs/TaggsBar';
@@ -35,8 +30,13 @@ import ProfileBody from './ProfileBody';
import ProfileCutout from './ProfileCutout';
import ProfileHeader from './ProfileHeader';
import PublicProfile from './PublicProfile';
+import {useScrollToTop} from '@react-navigation/native';
-const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
+interface ContentProps {
+ userXId: string | undefined;
+ screenType: ScreenType;
+}
+const Content: React.FC<ContentProps> = ({userXId, screenType}) => {
const dispatch = useDispatch();
const {
user = NO_USER,
@@ -60,6 +60,7 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
* If scrolling is enabled. Set to false before scrolling up for the tutorial.
*/
const [scrollEnabled, setScrollEnabled] = useState<boolean>(true);
+ const y = useSharedValue<number>(0);
/**
* States
@@ -109,45 +110,32 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
* updateUserXFriends updates friends list for the user.
*/
const handleBlockUnblock = async (callback?: () => void) => {
- await dispatch(
+ dispatch(
blockUnblockUser(
loggedInUser,
getUserAsProfilePreviewType(user, profile),
isBlocked,
),
);
- await dispatch(loadFriendsData(loggedInUser.userId));
- await dispatch(updateUserXFriends(user.userId, state));
+ dispatch(loadFriendsData(loggedInUser.userId));
+ dispatch(updateUserXFriends(user.userId, state));
if (callback) {
callback();
}
};
- const handleScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
- /**
- * Set the new y position
- */
- const newY = e.nativeEvent.contentOffset.y;
- y.setValue(newY);
+ const scrollHandler = useAnimatedScrollHandler((event) => {
+ y.value = event.contentOffset.y;
+ });
- /**
- * Do not allow overflow of scroll on bottom of the screen
- * SCREEN_HEIGHT - COVER_HEIGHT = Height of the scroll view
- */
- if (newY >= SCREEN_HEIGHT - COVER_HEIGHT) {
- setShouldBounce(false);
- } else if (newY === 0) {
- setShouldBounce(true);
- }
- };
+ useScrollToTop(scrollViewRef);
return (
<Animated.ScrollView
ref={scrollViewRef}
contentContainerStyle={styles.contentContainer}
style={styles.container}
- onScroll={(e) => handleScroll(e)}
- bounces={shouldBounce}
+ onScroll={scrollHandler}
showsVerticalScrollIndicator={false}
scrollEventThrottle={1}
stickyHeaderIndices={[4]}
@@ -171,7 +159,6 @@ const Content: React.FC<ContentProps> = ({y, userXId, screenType}) => {
/>
<TaggsBar
{...{y, profileBodyHeight, userXId, screenType}}
- whiteRing={undefined}
onLayout={onSocialsBarLayout}
/>
{canViewProfile(state, userXId, screenType) ? (
diff --git a/src/components/profile/Cover.tsx b/src/components/profile/Cover.tsx
index ee804ff3..27777b64 100644
--- a/src/components/profile/Cover.tsx
+++ b/src/components/profile/Cover.tsx
@@ -27,7 +27,7 @@ const Cover: React.FC<CoverProps> = ({userXId, screenType}) => {
const styles = StyleSheet.create({
container: {
- position: 'absolute',
+ ...StyleSheet.absoluteFillObject,
},
image: {
width: IMAGE_WIDTH,
diff --git a/src/components/profile/PublicProfile.tsx b/src/components/profile/PublicProfile.tsx
index eceb2fc3..1c49bff5 100644
--- a/src/components/profile/PublicProfile.tsx
+++ b/src/components/profile/PublicProfile.tsx
@@ -100,7 +100,7 @@ const PublicProfile: React.FC<ContentProps> = ({
scrollViewRef.current
) {
setScrollEnabled(false);
- scrollViewRef.current.getNode().scrollTo({y: 0});
+ scrollViewRef.current.scrollTo({y: 0});
navigation.navigate('MomentUploadPrompt', {
screenType,
momentCategory: momentCategories[0],
diff --git a/src/components/search/RecentSearches.tsx b/src/components/search/RecentSearches.tsx
index 84d35cac..6cea9338 100644
--- a/src/components/search/RecentSearches.tsx
+++ b/src/components/search/RecentSearches.tsx
@@ -6,6 +6,7 @@ import {
StyleSheet,
TouchableOpacityProps,
ScrollView,
+ Keyboard,
} from 'react-native';
import {
PreviewType,
@@ -15,7 +16,7 @@ import {
} from '../../types';
import {TAGG_LIGHT_BLUE} from '../../constants';
import SearchResults from './SearchResults';
-import {SCREEN_HEIGHT} from '../../utils';
+import {useBottomTabBarHeight} from '@react-navigation/bottom-tabs';
interface RecentSearchesProps extends TouchableOpacityProps {
sectionTitle: PreviewType;
@@ -25,35 +26,25 @@ interface RecentSearchesProps extends TouchableOpacityProps {
}
const RecentSearches: React.FC<RecentSearchesProps> = (props) => {
- const {sectionTitle, recents, recentCategories, screenType} = props;
+ const {recents, recentCategories} = props;
return (
- <ScrollView
- style={styles.mainContainer}
- contentContainerStyle={styles.contentContainer}>
+ <>
<View style={styles.header}>
- <Text style={styles.title}>{sectionTitle}</Text>
+ <Text style={styles.title}>Recent</Text>
<TouchableOpacity {...props}>
<Text style={styles.clear}>Clear all</Text>
</TouchableOpacity>
</View>
- <SearchResults
- results={recents}
- categories={recentCategories}
- previewType={sectionTitle}
- screenType={screenType}
- />
- </ScrollView>
+ <ScrollView
+ onScrollBeginDrag={Keyboard.dismiss}
+ contentContainerStyle={{paddingBottom: useBottomTabBarHeight()}}>
+ <SearchResults results={recents} categories={recentCategories} />
+ </ScrollView>
+ </>
);
};
const styles = StyleSheet.create({
- mainContainer: {
- flex: 1,
- },
- contentContainer: {
- paddingBottom: SCREEN_HEIGHT * 0.1,
- flex: 1,
- },
header: {
paddingHorizontal: 25,
paddingVertical: 5,
diff --git a/src/components/search/SearchBar.tsx b/src/components/search/SearchBar.tsx
index 4824b56f..d441b07b 100644
--- a/src/components/search/SearchBar.tsx
+++ b/src/components/search/SearchBar.tsx
@@ -9,20 +9,23 @@ import {
TextInputSubmitEditingEventData,
TouchableOpacity,
View,
+ ViewStyle,
+ LayoutChangeEvent,
} from 'react-native';
import {normalize} from 'react-native-elements';
-import Animated, {interpolate} from 'react-native-reanimated';
+import Animated, {useAnimatedStyle} from 'react-native-reanimated';
import Icon from 'react-native-vector-icons/Feather';
import {useSelector} from 'react-redux';
import {RootState} from '../../store/rootReducer';
-import {getSearchSuggestions, SCREEN_HEIGHT} from '../../utils';
+import {getSearchSuggestions} from '../../utils';
const AnimatedIcon = Animated.createAnimatedComponent(Icon);
interface SearchBarProps extends TextInputProps {
onCancel: () => void;
- top: Animated.Value<number>;
+ animationProgress: Animated.SharedValue<number>;
searching: boolean;
+ onLayout: (e: LayoutChangeEvent) => void;
}
const SearchBar: React.FC<SearchBarProps> = ({
onFocus,
@@ -31,7 +34,8 @@ const SearchBar: React.FC<SearchBarProps> = ({
value,
onCancel,
searching,
- top,
+ animationProgress,
+ onLayout,
}) => {
const handleSubmit = (
e: NativeSyntheticEvent<TextInputSubmitEditingEventData>,
@@ -107,19 +111,15 @@ const SearchBar: React.FC<SearchBarProps> = ({
}, [searching]);
/*
- * Animated nodes used in search bar activation animation.
+ * On-search marginRight style ("cancel" button slides and fades in).
*/
- const marginRight: Animated.Node<number> = interpolate(top, {
- inputRange: [-SCREEN_HEIGHT, 0],
- outputRange: [0, 58],
- });
- const opacity: Animated.Node<number> = interpolate(top, {
- inputRange: [-SCREEN_HEIGHT, 0],
- outputRange: [0, 1],
- });
+ const animatedStyles = useAnimatedStyle<ViewStyle>(() => ({
+ marginRight: animationProgress.value * 58,
+ opacity: animationProgress.value,
+ }));
return (
- <View style={styles.container}>
+ <View style={styles.container} onLayout={onLayout}>
<Animated.View style={styles.inputContainer}>
<AnimatedIcon
name="search"
@@ -131,13 +131,13 @@ const SearchBar: React.FC<SearchBarProps> = ({
style={[styles.input]}
placeholderTextColor={'#828282'}
onSubmitEditing={handleSubmit}
- clearButtonMode="while-editing"
+ clearButtonMode="always"
autoCapitalize="none"
autoCorrect={false}
{...{placeholder, value, onChangeText, onFocus, onBlur}}
/>
</Animated.View>
- <Animated.View style={{marginRight, opacity}}>
+ <Animated.View style={animatedStyles}>
<TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
<Text style={styles.cancelText}>Cancel</Text>
</TouchableOpacity>
@@ -151,6 +151,7 @@ const styles = StyleSheet.create({
height: 40,
paddingHorizontal: 20,
flexDirection: 'row',
+ zIndex: 2,
},
inputContainer: {
flexGrow: 1,
diff --git a/src/components/search/SearchCategories.tsx b/src/components/search/SearchCategories.tsx
index c747b34f..3d142981 100644
--- a/src/components/search/SearchCategories.tsx
+++ b/src/components/search/SearchCategories.tsx
@@ -3,29 +3,40 @@ import React, {useEffect, useState} from 'react';
import {StyleSheet, View} from 'react-native';
import {getSuggestedSearchBubbleSuggestions} from '../../services/ExploreService';
import {SearchCategoryType} from '../../types';
-import {SCREEN_WIDTH} from '../../utils';
import GradientBorderButton from '../common/GradientBorderButton';
+import {useSelector} from 'react-redux';
+import {RootState} from 'src/store/rootReducer';
interface SearchCategoriesProps {
darkStyle?: boolean;
- defaultButtons?: SearchCategoryType[];
+ useSuggestions: boolean;
}
const SearchCategories: React.FC<SearchCategoriesProps> = ({
darkStyle = false,
- defaultButtons,
+ useSuggestions,
}) => {
const navigation = useNavigation();
- const mtSearchCategory: (key: number) => SearchCategoryType = (key) => ({
+ const {
+ profile: {university = ''},
+ } = useSelector((state: RootState) => state.user);
+ const defaultButtons: SearchCategoryType[] = [21, 22, 23, 24].map(
+ (year, index) => ({
+ id: index * -1,
+ name: `${university.split(' ')[0]} '${year}`,
+ category: university,
+ }),
+ );
+ const createloadingCategory: (key: number) => SearchCategoryType = (key) => ({
id: key,
name: '...',
category: '...',
});
const [buttons, setButtons] = useState<SearchCategoryType[]>([
- mtSearchCategory(-1),
- mtSearchCategory(-2),
- mtSearchCategory(-3),
- mtSearchCategory(-4),
+ createloadingCategory(1),
+ createloadingCategory(2),
+ createloadingCategory(3),
+ createloadingCategory(4),
]);
useEffect(() => {
@@ -36,7 +47,7 @@ const SearchCategories: React.FC<SearchCategoriesProps> = ({
setButtons(localButtons);
}
};
- if (!defaultButtons) {
+ if (useSuggestions) {
loadButtons();
} else {
setButtons(defaultButtons);
@@ -45,33 +56,34 @@ const SearchCategories: React.FC<SearchCategoriesProps> = ({
return (
<View style={styles.container}>
- {buttons.map((searchCategory) => (
- <GradientBorderButton
- key={searchCategory.id}
- text={searchCategory.name}
- darkStyle={darkStyle}
- onPress={() => {
- if (searchCategory.name !== '...') {
- navigation.push('DiscoverUsers', {
- searchCategory,
- });
- }
- }}
- />
- ))}
+ <View style={styles.categoryContainer}>
+ {buttons.map((searchCategory, index) => (
+ <GradientBorderButton
+ key={index}
+ text={searchCategory.name}
+ darkStyle={darkStyle}
+ onPress={() => {
+ if (searchCategory.name !== '...') {
+ navigation.push('DiscoverUsers', {
+ searchCategory,
+ });
+ }
+ }}
+ />
+ ))}
+ </View>
</View>
);
};
const styles = StyleSheet.create({
container: {
- zIndex: 0,
- top: '3%',
- alignSelf: 'center',
+ paddingVertical: 20,
+ },
+ categoryContainer: {
flexDirection: 'row',
- width: SCREEN_WIDTH * 0.9,
- flexWrap: 'wrap',
justifyContent: 'space-evenly',
+ flexWrap: 'wrap',
},
});
export default SearchCategories;
diff --git a/src/components/search/SearchResultList.tsx b/src/components/search/SearchResultList.tsx
index 687b2285..a32760e1 100644
--- a/src/components/search/SearchResultList.tsx
+++ b/src/components/search/SearchResultList.tsx
@@ -1,15 +1,23 @@
import React, {useEffect, useState} from 'react';
-import {SectionList, StyleSheet, Text, View} from 'react-native';
+import {
+ SectionList,
+ StyleSheet,
+ Text,
+ View,
+ Keyboard,
+ SectionListData,
+} from 'react-native';
import {useSelector} from 'react-redux';
import {RootState} from '../../store/rootreducer';
import {NO_RESULTS_FOUND} from '../../constants/strings';
import {PreviewType, ScreenType} from '../../types';
-import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import {normalize, SCREEN_WIDTH} from '../../utils';
import SearchResultsCell from './SearchResultCell';
+import {useBottomTabBarHeight} from '@react-navigation/bottom-tabs';
interface SearchResultsProps {
- results: Array<any> | undefined;
- keyboardVisible: boolean;
+ // TODO: make sure results come in as same type, regardless of profile, category, badges
+ results: SectionListData<any>[];
previewType: PreviewType;
screenType: ScreenType;
}
@@ -21,11 +29,8 @@ const sectionHeader: React.FC<Boolean> = (showBorder: Boolean) => {
return null;
};
-const SearchResultList: React.FC<SearchResultsProps> = ({
- results,
- keyboardVisible,
-}) => {
- const [showEmptyView, setshowEmptyView] = useState(false);
+const SearchResultList: React.FC<SearchResultsProps> = ({results}) => {
+ const [showEmptyView, setshowEmptyView] = useState<boolean>(false);
const {user: loggedInUser} = useSelector((state: RootState) => state.user);
useEffect(() => {
@@ -38,57 +43,41 @@ const SearchResultList: React.FC<SearchResultsProps> = ({
}
}, [results]);
- return (
- <View style={styles.container}>
- {showEmptyView && (
- <View style={styles.noResultsTextContainer}>
- <Text style={styles.noResultsTextStyle}>{NO_RESULTS_FOUND}</Text>
- </View>
- )}
- {!showEmptyView && (
- <SectionList
- style={[
- {width: SCREEN_WIDTH},
- keyboardVisible ? styles.keyboardOpen : {},
- ]}
- contentContainerStyle={styles.sectionListContentContainer}
- sections={results}
- keyExtractor={(item, index) => item.id + index}
- renderItem={({section, item}) => {
- return (
- <SearchResultsCell
- profileData={item}
- loggedInUser={loggedInUser}
- />
- );
- }}
- renderSectionHeader={({section: {data}}) =>
- sectionHeader(data.length !== 0)
- }
- />
- )}
+ return showEmptyView ? (
+ <View style={styles.container} onTouchStart={Keyboard.dismiss}>
+ <Text style={styles.noResultsTextStyle}>{NO_RESULTS_FOUND}</Text>
</View>
+ ) : (
+ <SectionList
+ onScrollBeginDrag={Keyboard.dismiss}
+ contentContainerStyle={[{paddingBottom: useBottomTabBarHeight()}]}
+ sections={results}
+ keyExtractor={(item, index) => item.id + index}
+ renderItem={({item}) => {
+ return (
+ <SearchResultsCell profileData={item} loggedInUser={loggedInUser} />
+ );
+ }}
+ renderSectionHeader={({section: {data}}) =>
+ sectionHeader(data.length !== 0)
+ }
+ stickySectionHeadersEnabled={false}
+ />
);
};
const styles = StyleSheet.create({
container: {
- height: SCREEN_HEIGHT,
- paddingBottom: SCREEN_HEIGHT * 0.1,
- },
- sectionListContentContainer: {
- paddingBottom: SCREEN_HEIGHT * 0.15,
- width: SCREEN_WIDTH,
+ flex: 1,
+ marginTop: 30,
+ alignItems: 'center',
},
sectionHeaderStyle: {
width: '100%',
height: 0.5,
- marginBottom: normalize(24),
+ marginVertical: 5,
backgroundColor: '#C4C4C4',
},
- keyboardOpen: {
- marginBottom: SCREEN_HEIGHT * 0.35,
- },
noResultsTextContainer: {
justifyContent: 'center',
flexDirection: 'row',
diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx
index ef518d8b..a73d0b40 100644
--- a/src/components/search/SearchResults.tsx
+++ b/src/components/search/SearchResults.tsx
@@ -1,18 +1,10 @@
import React from 'react';
-import {
- ProfilePreviewType,
- PreviewType,
- ScreenType,
- CategoryPreviewType,
-} from '../../types';
-import {View} from 'react-native';
+import {ProfilePreviewType, CategoryPreviewType} from '../../types';
import SearchResultsCell from './SearchResultCell';
import {useSelector} from 'react-redux';
import {RootState} from '../../store/rootReducer';
interface SearchResultsProps {
results: ProfilePreviewType[];
- previewType: PreviewType;
- screenType: ScreenType;
categories: CategoryPreviewType[];
}
const SearchResults: React.FC<SearchResultsProps> = ({results, categories}) => {
@@ -22,7 +14,7 @@ const SearchResults: React.FC<SearchResultsProps> = ({results, categories}) => {
*/
const {user: loggedInUser} = useSelector((state: RootState) => state.user);
return (
- <View>
+ <>
{categories
.slice(0)
.reverse()
@@ -43,7 +35,7 @@ const SearchResults: React.FC<SearchResultsProps> = ({results, categories}) => {
{...{loggedInUser}}
/>
))}
- </View>
+ </>
);
};
diff --git a/src/components/search/SearchResultsBackground.tsx b/src/components/search/SearchResultsBackground.tsx
index 2833553d..e5236295 100644
--- a/src/components/search/SearchResultsBackground.tsx
+++ b/src/components/search/SearchResultsBackground.tsx
@@ -1,28 +1,55 @@
import React from 'react';
-import {StyleSheet} from 'react-native';
-import Animated, {interpolate} from 'react-native-reanimated';
-import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import {StyleSheet, ViewStyle} from 'react-native';
+import Animated, {
+ useAnimatedStyle,
+ useDerivedValue,
+ interpolate,
+ Extrapolate,
+} from 'react-native-reanimated';
+import {useSafeAreaInsets} from 'react-native-safe-area-context';
interface SearchResultsBackgroundProps {
- top: Animated.Value<number>;
+ animationProgress: Animated.SharedValue<number>;
+ searchBarHeight: number;
+ searching: boolean;
}
const SearchResultsBackground: React.FC<SearchResultsBackgroundProps> = ({
- top,
+ animationProgress,
+ searchBarHeight,
+ searching,
children,
}) => {
- const opacityBackground: Animated.Node<number> = interpolate(top, {
- inputRange: [-SCREEN_HEIGHT, 0],
- outputRange: [0, 1],
- });
- const opacityContent: Animated.Node<number> = interpolate(top, {
- inputRange: [-SCREEN_HEIGHT / 40, 0],
- outputRange: [0, 1],
- });
+ const {top: topInset} = useSafeAreaInsets();
+ /*
+ * On-search container style (opacity fade-in).
+ */
+ const backgroundAnimatedStyles = useAnimatedStyle<ViewStyle>(() => ({
+ opacity: animationProgress.value,
+ }));
+ /*
+ * Derived animation value for contentAnimatedStyles.
+ */
+ const contentAnimationProgress = useDerivedValue<number>(() =>
+ interpolate(animationProgress.value, [0.9, 1], [0, 1], Extrapolate.CLAMP),
+ );
+ /*
+ * On-search content style (delayed opacity fade-in).
+ */
+ const contentAnimatedStyles = useAnimatedStyle<ViewStyle>(() => ({
+ opacity: contentAnimationProgress.value,
+ }));
return (
<Animated.View
- style={[styles.container, {opacity: opacityBackground, top}]}>
- <Animated.View
- style={[styles.contentContainer, {opacity: opacityContent}]}>
+ style={[
+ styles.container,
+ backgroundAnimatedStyles,
+ {
+ // absolute: inset + search screen paddingTop + searchBar + padding
+ paddingTop: topInset + 15 + searchBarHeight + 10,
+ },
+ ]}
+ pointerEvents={searching ? 'auto' : 'none'}>
+ <Animated.View style={[styles.contentContainer, contentAnimatedStyles]}>
{children}
</Animated.View>
</Animated.View>
@@ -30,15 +57,11 @@ const SearchResultsBackground: React.FC<SearchResultsBackgroundProps> = ({
};
const styles = StyleSheet.create({
container: {
- height: SCREEN_HEIGHT,
- width: SCREEN_WIDTH,
- position: 'absolute',
+ ...StyleSheet.absoluteFillObject,
backgroundColor: 'white',
},
contentContainer: {
flex: 1,
- paddingVertical: 10,
- paddingBottom: SCREEN_HEIGHT / 15,
},
});
export default SearchResultsBackground;
diff --git a/src/components/suggestedPeople/SPTaggsBar.tsx b/src/components/suggestedPeople/SPTaggsBar.tsx
new file mode 100644
index 00000000..adac6dcf
--- /dev/null
+++ b/src/components/suggestedPeople/SPTaggsBar.tsx
@@ -0,0 +1,133 @@
+import React, {useEffect, useState} from 'react';
+import {StyleSheet} from 'react-native';
+import Animated from 'react-native-reanimated';
+import {useDispatch, useSelector, useStore} from 'react-redux';
+import {INTEGRATED_SOCIAL_LIST, SOCIAL_LIST} from '../../constants';
+import {getLinkedSocials} from '../../services';
+import {loadIndividualSocial, updateSocial} from '../../store/actions';
+import {RootState} from '../../store/rootReducer';
+import {ScreenType} from '../../types';
+import {canViewProfile} from '../../utils';
+import Tagg from '../taggs/Tagg';
+
+const {View, ScrollView} = Animated;
+interface TaggsBarProps {
+ userXId: string | undefined;
+ screenType: ScreenType;
+ linkedSocials?: string[];
+}
+const TaggsBar: React.FC<TaggsBarProps> = ({
+ userXId,
+ screenType,
+ linkedSocials,
+}) => {
+ let [taggs, setTaggs] = useState<Object[]>([]);
+ let [taggsNeedUpdate, setTaggsNeedUpdate] = useState(true);
+ const {user} = useSelector((state: RootState) =>
+ userXId ? state.userX[screenType][userXId] : state.user,
+ );
+ const state: RootState = useStore().getState();
+ const allowTaggsNavigation = canViewProfile(state, userXId, screenType);
+
+ const dispatch = useDispatch();
+
+ /**
+ * Updates the individual social that needs update
+ * If username is empty, update nonintegrated socials like Snapchat and TikTok
+ * @param socialType Type of the social that needs update
+ */
+ const handleSocialUpdate = (socialType: string, username: string) => {
+ if (username !== '') {
+ dispatch(updateSocial(socialType, username));
+ } else {
+ dispatch(loadIndividualSocial(user.userId, socialType));
+ }
+ };
+
+ /**
+ * This useEffect should be called evey time the user being viewed is changed OR
+ * And update is triggered manually
+ */
+ useEffect(() => {
+ const loadData = async () => {
+ const socials: string[] = linkedSocials
+ ? linkedSocials
+ : await getLinkedSocials(user.userId);
+ const unlinkedSocials = SOCIAL_LIST.filter(
+ (s) => socials.indexOf(s) === -1,
+ );
+ let new_taggs = [];
+ let i = 0;
+ for (let social of socials) {
+ new_taggs.push(
+ <Tagg
+ key={i}
+ social={social}
+ userXId={userXId}
+ user={user}
+ isLinked={true}
+ isIntegrated={INTEGRATED_SOCIAL_LIST.indexOf(social) !== -1}
+ setTaggsNeedUpdate={setTaggsNeedUpdate}
+ setSocialDataNeedUpdate={handleSocialUpdate}
+ whiteRing={true}
+ allowNavigation={allowTaggsNavigation}
+ />,
+ );
+ i++;
+ }
+ if (!userXId) {
+ for (let social of unlinkedSocials) {
+ new_taggs.push(
+ <Tagg
+ key={i}
+ social={social}
+ isLinked={false}
+ isIntegrated={INTEGRATED_SOCIAL_LIST.indexOf(social) !== -1}
+ setTaggsNeedUpdate={setTaggsNeedUpdate}
+ setSocialDataNeedUpdate={handleSocialUpdate}
+ userXId={userXId}
+ user={user}
+ whiteRing={true}
+ allowNavigation={allowTaggsNavigation}
+ />,
+ );
+ i++;
+ }
+ }
+ setTaggs(new_taggs);
+ setTaggsNeedUpdate(false);
+ };
+ if (user.userId) {
+ loadData();
+ }
+ }, [taggsNeedUpdate, user]);
+
+ return taggs.length > 0 ? (
+ <View style={styles.spContainer}>
+ <ScrollView
+ horizontal
+ showsHorizontalScrollIndicator={false}
+ contentContainerStyle={styles.contentContainer}>
+ {taggs}
+ </ScrollView>
+ </View>
+ ) : (
+ <></>
+ );
+};
+
+const styles = StyleSheet.create({
+ spContainer: {
+ shadowColor: '#000',
+ shadowRadius: 10,
+ shadowOffset: {width: 0, height: 2},
+ zIndex: 1,
+ marginBottom: 25,
+ },
+ contentContainer: {
+ alignItems: 'center',
+ paddingBottom: 5,
+ },
+});
+
+export default TaggsBar;
diff --git a/src/components/suggestedPeople/index.ts b/src/components/suggestedPeople/index.ts
index 515f6fb4..339c9ae0 100644
--- a/src/components/suggestedPeople/index.ts
+++ b/src/components/suggestedPeople/index.ts
@@ -1,2 +1,3 @@
export {default as MutualFriends} from './MutualFriends';
export {default as BadgesDropdown} from './BadgesDropdown';
+export {default as SPTaggsBar} from './SPTaggsBar';
diff --git a/src/components/taggs/TaggPostFooter.tsx b/src/components/taggs/TaggPostFooter.tsx
index ae9d889d..750f1793 100644
--- a/src/components/taggs/TaggPostFooter.tsx
+++ b/src/components/taggs/TaggPostFooter.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import {Linking, StyleSheet, View} from 'react-native';
+import {StyleSheet, View} from 'react-native';
import {Text} from 'react-native-animatable';
import {handleOpenSocialUrlOnBrowser} from '../../utils';
import {DateLabel} from '../common';
diff --git a/src/components/taggs/TaggsBar.tsx b/src/components/taggs/TaggsBar.tsx
index ec91b8e5..a5003fbb 100644
--- a/src/components/taggs/TaggsBar.tsx
+++ b/src/components/taggs/TaggsBar.tsx
@@ -1,6 +1,11 @@
-import React, {Fragment, useEffect, useState} from 'react';
-import {StyleSheet, LayoutChangeEvent} from 'react-native';
-import Animated from 'react-native-reanimated';
+import React, {useEffect, useState} from 'react';
+import {LayoutChangeEvent, StyleSheet} from 'react-native';
+import Animated, {
+ Extrapolate,
+ interpolate,
+ useAnimatedStyle,
+ useDerivedValue,
+} from 'react-native-reanimated';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {useDispatch, useSelector, useStore} from 'react-redux';
import {
@@ -15,13 +20,12 @@ import {ScreenType} from '../../types';
import {canViewProfile} from '../../utils';
import Tagg from './Tagg';
-const {View, ScrollView, interpolate, Extrapolate} = Animated;
+const {View, ScrollView} = Animated;
interface TaggsBarProps {
- y: Animated.Value<number>;
+ y: Animated.SharedValue<number>;
profileBodyHeight: number;
userXId: string | undefined;
screenType: ScreenType;
- whiteRing: boolean | undefined;
linkedSocials?: string[];
onLayout: (event: LayoutChangeEvent) => void;
}
@@ -30,7 +34,6 @@ const TaggsBar: React.FC<TaggsBarProps> = ({
profileBodyHeight,
userXId,
screenType,
- whiteRing,
linkedSocials,
onLayout,
}) => {
@@ -43,7 +46,7 @@ const TaggsBar: React.FC<TaggsBarProps> = ({
const allowTaggsNavigation = canViewProfile(state, userXId, screenType);
const dispatch = useDispatch();
-
+ const insetTop = useSafeAreaInsets().top;
/**
* Updates the individual social that needs update
* If username is empty, update nonintegrated socials like Snapchat and TikTok
@@ -82,13 +85,13 @@ const TaggsBar: React.FC<TaggsBarProps> = ({
isIntegrated={INTEGRATED_SOCIAL_LIST.indexOf(social) !== -1}
setTaggsNeedUpdate={setTaggsNeedUpdate}
setSocialDataNeedUpdate={handleSocialUpdate}
- whiteRing={whiteRing ? whiteRing : undefined}
+ whiteRing={false}
allowNavigation={allowTaggsNavigation}
/>,
);
i++;
}
- if (!userXId && !whiteRing) {
+ if (!userXId) {
for (let social of unlinkedSocials) {
new_taggs.push(
<Tagg
@@ -100,7 +103,7 @@ const TaggsBar: React.FC<TaggsBarProps> = ({
setSocialDataNeedUpdate={handleSocialUpdate}
userXId={userXId}
user={user}
- whiteRing={whiteRing ? whiteRing : undefined}
+ whiteRing={false}
allowNavigation={allowTaggsNavigation}
/>,
);
@@ -114,65 +117,55 @@ const TaggsBar: React.FC<TaggsBarProps> = ({
loadData();
}
}, [taggsNeedUpdate, user]);
-
- const shadowOpacity: Animated.Node<number> = interpolate(y, {
- inputRange: [
- PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight,
- PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight + 20,
- ],
- outputRange: [0, 0.2],
- extrapolate: Extrapolate.CLAMP,
- });
- const paddingTop: Animated.Node<number> = interpolate(y, {
- inputRange: [
- PROFILE_CUTOUT_BOTTOM_Y +
- profileBodyHeight -
- (useSafeAreaInsets().top + 10),
- PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight,
- ],
- outputRange: [10, useSafeAreaInsets().top],
- extrapolate: Extrapolate.CLAMP,
- });
+ const paddingTopStylesProgress = useDerivedValue(() =>
+ interpolate(
+ y.value,
+ [PROFILE_CUTOUT_BOTTOM_Y, PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight],
+ [0, 1],
+ Extrapolate.CLAMP,
+ ),
+ );
+ const shadowOpacityStylesProgress = useDerivedValue(() =>
+ interpolate(
+ y.value,
+ [
+ PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight,
+ PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight + insetTop,
+ ],
+ [0, 1],
+ Extrapolate.CLAMP,
+ ),
+ );
+ const animatedStyles = useAnimatedStyle(() => ({
+ shadowOpacity: shadowOpacityStylesProgress.value / 5,
+ paddingTop: paddingTopStylesProgress.value * insetTop,
+ }));
return taggs.length > 0 ? (
- <View
- style={
- whiteRing
- ? [styles.spContainer]
- : [styles.container, {shadowOpacity, paddingTop}]
- }
- onLayout={onLayout}>
+ <View style={[styles.container, animatedStyles]} onLayout={onLayout}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
- contentContainerStyle={styles.contentContainer}>
+ contentContainerStyle={[styles.contentContainer]}>
{taggs}
</ScrollView>
</View>
) : (
- <Fragment />
+ <></>
);
};
const styles = StyleSheet.create({
- spContainer: {
- shadowColor: '#000',
- shadowRadius: 10,
- shadowOffset: {width: 0, height: 2},
- zIndex: 1,
- marginBottom: 25,
- },
container: {
backgroundColor: 'white',
shadowColor: '#000',
shadowRadius: 10,
shadowOffset: {width: 0, height: 2},
zIndex: 1,
- paddingBottom: 5,
},
contentContainer: {
alignItems: 'center',
- paddingBottom: 5,
+ paddingBottom: 15,
},
});
diff --git a/src/constants/api.ts b/src/constants/api.ts
index 22890c33..ffe47687 100644
--- a/src/constants/api.ts
+++ b/src/constants/api.ts
@@ -1,6 +1,12 @@
/* eslint-disable */
-// const BASE_URL: string = 'http://3.22.188.127/'; // prod server
-const BASE_URL: string = 'http://127.0.0.1:8000/'; // local server
+
+// Dev
+const BASE_URL: string = 'http://127.0.0.1:8000/';
+export const STREAM_CHAT_API = 'g2hvnyqx9cmv'
+
+// Prod
+// const BASE_URL: string = 'http://app-prod.tagg.id/';
+// export const STREAM_CHAT_API = 'ur3kg5qz8x5v'
const API_URL: string = BASE_URL + 'api/';
export const LOGIN_ENDPOINT: string = API_URL + 'login/';
diff --git a/src/screens/chat/ChatListScreen.tsx b/src/screens/chat/ChatListScreen.tsx
index c49c790d..9011ed4a 100644
--- a/src/screens/chat/ChatListScreen.tsx
+++ b/src/screens/chat/ChatListScreen.tsx
@@ -1,8 +1,13 @@
-import React from 'react';
-import {View, StyleSheet, Text, TouchableOpacity} from 'react-native';
+import AsyncStorage from '@react-native-community/async-storage';
import {StackNavigationProp} from '@react-navigation/stack';
-
+import React, {useContext, useEffect, useMemo, useState} from 'react';
+import {SafeAreaView, StatusBar, StyleSheet, View} from 'react-native';
+import {useStore} from 'react-redux';
+import {ChannelList, Chat} from 'stream-chat-react-native';
+import {ChatContext} from '../../App';
+import {MessagesHeader} from '../../components/messages';
import {MainStackParams} from '../../routes';
+import {RootState} from '../../store/rootReducer';
type ChatListScreenNavigationProp = StackNavigationProp<
MainStackParams,
@@ -15,19 +20,80 @@ interface ChatListScreenProps {
* Screen that displays all of the user's active conversations.
*/
const ChatListScreen: React.FC<ChatListScreenProps> = ({navigation}) => {
+ const {chatClient, setChannel} = useContext(ChatContext);
+ const [clientReady, setClientReady] = useState(false);
+ const state: RootState = useStore().getState();
+ const loggedInUserId = state.user.user.userId;
+
+ const memoizedFilters = useMemo(
+ () => ({
+ members: {$in: [loggedInUserId]},
+ type: 'messaging',
+ }),
+ [],
+ );
+
+ useEffect(() => {
+ const setupClient = async () => {
+ const chatToken = await AsyncStorage.getItem('chatToken');
+ await chatClient.connectUser(
+ {
+ id: loggedInUserId,
+ },
+ chatToken,
+ );
+ return setClientReady(true);
+ };
+ if (!clientReady) {
+ setupClient().catch((err) => {
+ console.error(err);
+ });
+ }
+ }, []);
+
return (
- <View style={styles.container}>
- <Text style={styles.placeholder}>I am the chat list.</Text>
- <TouchableOpacity
- style={styles.button}
- onPress={() => navigation.navigate('Chat')}>
- <Text>Let's go to a conversation!</Text>
- </TouchableOpacity>
+ <View style={styles.background}>
+ <SafeAreaView>
+ <StatusBar barStyle="dark-content" />
+ <MessagesHeader
+ createChannel={() => {
+ // TODO: (CHAT) change me
+ const channel = chatClient.channel('messaging', {
+ name: 'Awesome channel with foobar',
+ members: [loggedInUserId, 'd5295557-59ce-49fc-aa8a-442874dbffc3'],
+ });
+ channel.create();
+ }}
+ />
+ {clientReady && (
+ <Chat client={chatClient}>
+ <View style={styles.chatContainer}>
+ <ChannelList
+ filters={memoizedFilters}
+ onSelect={(channel) => {
+ setChannel(channel);
+ navigation.navigate('Chat');
+ }}
+ options={{
+ presence: true,
+ state: true,
+ watch: true,
+ }}
+ sort={{last_message_at: -1}}
+ />
+ </View>
+ </Chat>
+ )}
+ </SafeAreaView>
</View>
);
};
const styles = StyleSheet.create({
+ background: {
+ flex: 1,
+ backgroundColor: 'white',
+ },
container: {
flex: 1,
justifyContent: 'center',
@@ -43,6 +109,10 @@ const styles = StyleSheet.create({
padding: 15,
borderRadius: 5,
},
+ chatContainer: {
+ height: '100%',
+ marginTop: 10,
+ },
});
export default ChatListScreen;
diff --git a/src/screens/chat/ChatScreen.tsx b/src/screens/chat/ChatScreen.tsx
index af83f504..eeb1a7d6 100644
--- a/src/screens/chat/ChatScreen.tsx
+++ b/src/screens/chat/ChatScreen.tsx
@@ -1,7 +1,14 @@
-import React from 'react';
-import {View, StyleSheet, Text} from 'react-native';
-import {StackNavigationProp} from '@react-navigation/stack';
-
+import {useBottomTabBarHeight} from '@react-navigation/bottom-tabs';
+import {StackNavigationProp, useHeaderHeight} from '@react-navigation/stack';
+import React, {useContext} from 'react';
+import {StyleSheet, View} from 'react-native';
+import {
+ Channel,
+ Chat,
+ MessageInput,
+ MessageList,
+} from 'stream-chat-react-native';
+import {ChatContext} from '../../App';
import {MainStackParams} from '../../routes';
type ChatScreenNavigationProp = StackNavigationProp<MainStackParams, 'Chat'>;
@@ -12,22 +19,25 @@ interface ChatScreenProps {
* Screen that displays all of the user's active conversations.
*/
const ChatScreen: React.FC<ChatScreenProps> = () => {
+ const {channel, chatClient} = useContext(ChatContext);
+ const headerHeight = useHeaderHeight();
+ const tabbarHeight = useBottomTabBarHeight();
+
return (
- <View style={styles.container}>
- <Text style={styles.placeholder}>I am a chat!</Text>
+ <View style={[styles.container, {paddingBottom: tabbarHeight}]}>
+ <Chat client={chatClient}>
+ <Channel channel={channel} keyboardVerticalOffset={headerHeight}>
+ <MessageList onThreadSelect={() => {}} />
+ <MessageInput />
+ </Channel>
+ </Chat>
</View>
);
};
const styles = StyleSheet.create({
container: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- },
- placeholder: {
- fontSize: 14,
- fontWeight: 'bold',
+ backgroundColor: 'white',
},
});
diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx
index 49ca5ff4..dd2bb2e4 100644
--- a/src/screens/onboarding/Login.tsx
+++ b/src/screens/onboarding/Login.tsx
@@ -160,6 +160,7 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => {
await AsyncStorage.setItem('token', data.token);
await AsyncStorage.setItem('userId', data.UserID);
await AsyncStorage.setItem('username', username);
+ await AsyncStorage.setItem('chatToken', data.chatToken);
}
if (statusCode === 200 && data.isOnboarded) {
diff --git a/src/screens/profile/ProfileScreen.tsx b/src/screens/profile/ProfileScreen.tsx
index 0d6af21e..6d9ef020 100644
--- a/src/screens/profile/ProfileScreen.tsx
+++ b/src/screens/profile/ProfileScreen.tsx
@@ -1,15 +1,9 @@
import React from 'react';
import {StatusBar} from 'react-native';
-import Animated from 'react-native-reanimated';
import {Content, TabsGradient} from '../../components';
import {RouteProp} from '@react-navigation/native';
import {MainStackParams} from '../../routes/';
-/**r
- * Profile Screen for a user's profile
- * including posts, messaging, and settings
- */
-
type ProfileScreenRouteProps = RouteProp<MainStackParams, 'Profile'>;
interface ProfileOnboardingProps {
@@ -19,31 +13,11 @@ interface ProfileOnboardingProps {
const ProfileScreen: React.FC<ProfileOnboardingProps> = ({route}) => {
const {screenType} = route.params;
let {userXId} = route.params;
- const y = Animated.useValue(0);
-
- /**
- * This is a double safety check to avoid app crash.
- * Checks if the required userXId is present in the store, if not userXId is set to dummy id
- */
- // if (userXId && !(userXId in useStore().getState().userX[screenType])) {
- // userXId = DUMMY_USERID;
- // }
-
- /**
- * Code under useFocusEffect gets executed every time the screen comes under focus / is being viewed by the user.
- * This is done to reset the users stored in our store for the Search screen.
- * Read more about useFocusEffect here : https://reactnavigation.org/docs/function-after-focusing-screen/
- */
- // useFocusEffect(() => {
- // if (!userXId) {
- // dispatch(resetScreenType(screenType));
- // }
- // });
return (
<>
<StatusBar barStyle="dark-content" />
- <Content {...{y, userXId, screenType}} />
+ <Content {...{userXId, screenType}} />
<TabsGradient />
</>
);
diff --git a/src/screens/profile/SettingsScreen.tsx b/src/screens/profile/SettingsScreen.tsx
index 05e051b5..ecc3bafd 100644
--- a/src/screens/profile/SettingsScreen.tsx
+++ b/src/screens/profile/SettingsScreen.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, {useContext} from 'react';
import {
SafeAreaView,
SectionList,
@@ -17,6 +17,7 @@ import {BackgroundGradientType} from '../../types';
import {normalize, SCREEN_HEIGHT} from '../../utils/layouts';
import SettingsCell from './SettingsCell';
import {useNavigation} from '@react-navigation/core';
+import {ChatContext} from '../../App';
const SettingsScreen: React.FC = () => {
const dispatch = useDispatch();
@@ -24,6 +25,7 @@ const SettingsScreen: React.FC = () => {
const {suggested_people_linked} = useSelector(
(state: RootState) => state.user.profile,
);
+ const {chatClient} = useContext(ChatContext);
return (
<>
@@ -49,7 +51,7 @@ const SettingsScreen: React.FC = () => {
<TouchableOpacity
style={styles.logoutContainerStyles}
onPress={() => {
- dispatch(logout());
+ dispatch(logout(chatClient));
navigation.reset({
index: 0,
routes: [{name: 'SuggestedPeople'}],
diff --git a/src/screens/search/DiscoverUsers.tsx b/src/screens/search/DiscoverUsers.tsx
index b87bfc37..f67585f2 100644
--- a/src/screens/search/DiscoverUsers.tsx
+++ b/src/screens/search/DiscoverUsers.tsx
@@ -126,7 +126,7 @@ const DiscoverUsers: React.FC<DiscoverUsersProps> = ({route}) => {
ListFooterComponent={() => (
<>
<Text style={styles.otherGroups}>Other Groups</Text>
- <SearchCategories darkStyle={true} />
+ <SearchCategories useSuggestions={true} darkStyle={true} />
</>
)}
/>
diff --git a/src/screens/search/SearchScreen.tsx b/src/screens/search/SearchScreen.tsx
index 4f0cabb4..f7e1c467 100644
--- a/src/screens/search/SearchScreen.tsx
+++ b/src/screens/search/SearchScreen.tsx
@@ -1,8 +1,19 @@
import AsyncStorage from '@react-native-community/async-storage';
import {useFocusEffect} from '@react-navigation/native';
import React, {useEffect, useState} from 'react';
-import {Keyboard, ScrollView, StatusBar, StyleSheet} from 'react-native';
-import Animated, {Easing, timing} from 'react-native-reanimated';
+import {
+ Keyboard,
+ StatusBar,
+ StyleSheet,
+ LayoutChangeEvent,
+ SectionListData,
+} from 'react-native';
+import {
+ useSharedValue,
+ withTiming,
+ Easing,
+ runOnJS,
+} from 'react-native-reanimated';
import {SafeAreaView} from 'react-native-safe-area-context';
import {useDispatch, useSelector} from 'react-redux';
import {
@@ -13,22 +24,14 @@ import {
SearchResultsBackground,
TabsGradient,
} from '../../components';
-import {SEARCH_ENDPOINT, TAGG_LIGHT_BLUE} from '../../constants';
+import {SEARCH_ENDPOINT} from '../../constants';
import {loadSearchResults} from '../../services';
import {resetScreenType} from '../../store/actions';
import {RootState} from '../../store/rootReducer';
-import {
- CategoryPreviewType,
- ProfilePreviewType,
- ScreenType,
- SearchCategoryType,
-} from '../../types';
+import {CategoryPreviewType, ProfilePreviewType, ScreenType} from '../../types';
import {
getRecentlySearchedCategories,
getRecentlySearchedUsers,
- normalize,
- SCREEN_HEIGHT,
- SCREEN_WIDTH,
} from '../../utils';
/**
@@ -38,11 +41,8 @@ import {
const SearchScreen: React.FC = () => {
const {recentSearches} = useSelector((state: RootState) => state.taggUsers);
- const {
- profile: {university = ''},
- } = useSelector((state: RootState) => state.user);
const [query, setQuery] = useState<string>('');
- const [results, setResults] = useState<Array<any> | undefined>(undefined);
+ const [results, setResults] = useState<SectionListData<any>[] | undefined>();
const [recents, setRecents] = useState<Array<ProfilePreviewType>>(
recentSearches ?? [],
);
@@ -50,26 +50,12 @@ const SearchScreen: React.FC = () => {
CategoryPreviewType[]
>([]);
const [searching, setSearching] = useState(false);
- const top = Animated.useValue(-SCREEN_HEIGHT);
- const defaultButtons: SearchCategoryType[] = [21, 22, 23, 24].map((year) => ({
- id: -1,
- name: `${university.split(' ')[0]} '${year}`,
- category: university,
- }));
- const [keyboardVisible, setKeyboardVisible] = React.useState(
- 'keyboardVisible',
- );
- useEffect(() => {
- const showKeyboard = () => setKeyboardVisible('keyboardVisibleTrue');
- Keyboard.addListener('keyboardWillShow', showKeyboard);
- return () => Keyboard.removeListener('keyboardWillShow', showKeyboard);
- }, []);
+ /*
+ * Animated value
+ */
+ const animationProgress = useSharedValue<number>(0);
+ const [searchBarHeight, setSearchBarHeight] = useState<number>(0);
- useEffect(() => {
- const hideKeyboard = () => setKeyboardVisible('keyboardVisibleFalse');
- Keyboard.addListener('keyboardWillHide', hideKeyboard);
- return () => Keyboard.removeListener('keyboardWillHide', hideKeyboard);
- }, []);
const dispatch = useDispatch();
/*
@@ -122,12 +108,22 @@ const SearchScreen: React.FC = () => {
useEffect(() => {
if (searching) {
loadRecentlySearched().then(() => {
- timing(top, topInConfig).start();
+ animationProgress.value = withTiming(1, {
+ duration: 180,
+ easing: Easing.bezier(0.31, 0.14, 0.66, 0.82),
+ });
});
} else {
setQuery('');
handleBlur();
- timing(top, topOutConfig).start(() => setResults(undefined));
+ animationProgress.value = withTiming(
+ 0,
+ {duration: 180, easing: Easing.inOut(Easing.ease)},
+ () => {
+ 'worklet';
+ runOnJS(setResults)(undefined);
+ },
+ );
}
}, [searching]);
@@ -153,16 +149,6 @@ const SearchScreen: React.FC = () => {
}
};
- const topInConfig = {
- duration: 180,
- toValue: 0,
- easing: Easing.bezier(0.31, 0.14, 0.66, 0.82),
- };
- const topOutConfig = {
- duration: 180,
- toValue: -SCREEN_HEIGHT,
- easing: Easing.inOut(Easing.ease),
- };
const handleFocus = () => {
setSearching(true);
};
@@ -172,9 +158,12 @@ const SearchScreen: React.FC = () => {
const handleCancel = () => {
setSearching(false);
};
+ const onSearchBarLayout = (e: LayoutChangeEvent) => {
+ setSearchBarHeight(e.nativeEvent.layout.height);
+ };
return (
- <SafeAreaView style={styles.screenContainer}>
+ <SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
<SearchBar
onCancel={handleCancel}
@@ -182,98 +171,39 @@ const SearchScreen: React.FC = () => {
onBlur={handleBlur}
onFocus={handleFocus}
value={query}
- {...{top, searching}}
+ onLayout={onSearchBarLayout}
+ {...{animationProgress, searching}}
/>
- <ScrollView
- scrollEnabled={!searching}
- keyboardShouldPersistTaps={'always'}
- stickyHeaderIndices={[4]}
- contentContainerStyle={styles.contentContainer}
- showsVerticalScrollIndicator={false}>
- <SearchCategories defaultButtons={defaultButtons} />
- <SearchResultsBackground {...{top}}>
- {results === undefined &&
- recents.length + recentCategories.length !== 0 ? (
+ <SearchCategories useSuggestions={false} />
+ <SearchResultsBackground
+ {...{searching, searchBarHeight, animationProgress}}>
+ {results === undefined ? (
+ recents.length + recentCategories.length > 0 && (
<RecentSearches
sectionTitle="Recent"
onPress={clearRecentlySearched}
screenType={ScreenType.Search}
{...{recents, recentCategories}}
/>
- ) : (
- <SearchResultList
- {...{results}}
- keyboardVisible={keyboardVisible === 'keyboardVisibleTrue'}
- previewType={'Search'}
- screenType={ScreenType.Search}
- />
- )}
- </SearchResultsBackground>
- </ScrollView>
+ )
+ ) : (
+ <SearchResultList
+ {...{results}}
+ previewType={'Search'}
+ screenType={ScreenType.Search}
+ />
+ )}
+ </SearchResultsBackground>
<TabsGradient />
</SafeAreaView>
);
};
const styles = StyleSheet.create({
- screenContainer: {
+ container: {
+ flex: 1,
paddingTop: 15,
backgroundColor: '#fff',
},
- contentContainer: {
- height: SCREEN_HEIGHT,
- paddingTop: '2%',
- paddingBottom: SCREEN_HEIGHT / 3,
- paddingHorizontal: '3%',
- },
- header: {
- marginVertical: 20,
- zIndex: 1,
- },
- recentsHeaderContainer: {
- flexDirection: 'row',
- },
- recentsHeader: {
- fontSize: 17,
- fontWeight: 'bold',
- flexGrow: 1,
- },
- clear: {
- fontSize: normalize(17),
- fontWeight: 'bold',
- color: TAGG_LIGHT_BLUE,
- },
- image: {
- width: SCREEN_WIDTH,
- height: SCREEN_WIDTH,
- },
- textContainer: {
- marginTop: '10%',
- },
- headerText: {
- color: '#fff',
- fontSize: normalize(32),
- fontWeight: '600',
- textAlign: 'center',
- marginBottom: '4%',
- marginHorizontal: '10%',
- },
- subtext: {
- color: '#fff',
- fontSize: normalize(16),
- fontWeight: '600',
- textAlign: 'center',
- marginHorizontal: '10%',
- },
- cancelButton: {
- position: 'absolute',
- height: '100%',
- justifyContent: 'center',
- paddingHorizontal: 5,
- },
- cancelText: {
- color: '#818181',
- fontWeight: '600',
- },
});
export default SearchScreen;
diff --git a/src/screens/suggestedPeople/SPBody.tsx b/src/screens/suggestedPeople/SPBody.tsx
index 824f8b1c..fa69d812 100644
--- a/src/screens/suggestedPeople/SPBody.tsx
+++ b/src/screens/suggestedPeople/SPBody.tsx
@@ -3,26 +3,18 @@ import React, {Fragment, useEffect, useMemo, useState} from 'react';
import {StyleSheet, Text, View} from 'react-native';
import {Image} from 'react-native-animatable';
import {TouchableOpacity} from 'react-native-gesture-handler';
-import Animated from 'react-native-reanimated';
-import {useStore} from 'react-redux';
import RequestedButton from '../../assets/ionicons/requested-button.svg';
-import {TaggsBar} from '../../components';
+import {SPTaggsBar} from '../../components';
import {BadgesDropdown, MutualFriends} from '../../components/suggestedPeople';
import {BADGE_DATA} from '../../constants/badges';
-import {RootState} from '../../store/rootReducer';
import {
ProfilePreviewType,
ScreenType,
SuggestedPeopleDataType,
UniversityBadge,
} from '../../types';
-import {
- canViewProfile,
- isIPhoneX,
- normalize,
- SCREEN_HEIGHT,
- SCREEN_WIDTH,
-} from '../../utils';
+import {isIPhoneX, normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
+import {useSharedValue} from 'react-native-reanimated';
interface SPBodyProps {
item: SuggestedPeopleDataType;
@@ -56,7 +48,6 @@ const SPBody: React.FC<SPBodyProps> = ({
}[]
>([]);
const navigation = useNavigation();
- const state: RootState = useStore().getState();
useEffect(() => {
const newBadges: {badge: UniversityBadge; img: any}[] = [];
const findBadgeIcons = (badge: UniversityBadge) => {
@@ -159,12 +150,9 @@ const SPBody: React.FC<SPBodyProps> = ({
{user.id !== loggedInUserId && <FriendButton />}
</View>
</View>
- <TaggsBar
- y={Animated.useValue(0)}
+ <SPTaggsBar
userXId={user.id === loggedInUserId ? undefined : user.id}
- profileBodyHeight={0}
screenType={screenType}
- whiteRing={true}
linkedSocials={social_links}
/>
<View style={styles.marginManager}>
diff --git a/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx b/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx
index a296351f..d6812f41 100644
--- a/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx
+++ b/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx
@@ -226,7 +226,7 @@ const SuggestedPeopleScreen: React.FC = () => {
/>
);
}}
- keyExtractor={(item, index) => index.toString()}
+ keyExtractor={(_, index) => index.toString()}
showsVerticalScrollIndicator={false}
onViewableItemsChanged={onViewableItemsChanged}
onEndReached={() => setPage(page + 1)}
diff --git a/src/store/actions/user.ts b/src/store/actions/user.ts
index e7d985ac..3ebd4190 100644
--- a/src/store/actions/user.ts
+++ b/src/store/actions/user.ts
@@ -1,4 +1,5 @@
import AsyncStorage from '@react-native-community/async-storage';
+import {StreamChat} from 'stream-chat';
import {Action, ThunkAction} from '@reduxjs/toolkit';
import {
getProfilePic,
@@ -164,13 +165,16 @@ export const updateReplyPosted = (
}
};
-export const logout = (): ThunkAction<
- Promise<void>,
- RootState,
- unknown,
- Action<string>
-> => async (dispatch) => {
+export const logout = (
+ client?: StreamChat,
+): ThunkAction<Promise<void>, RootState, unknown, Action<string>> => async (
+ dispatch,
+) => {
try {
+ // do our best effort here to gracefully disconnect the user
+ if (client) {
+ client.disconnectUser();
+ }
await AsyncStorage.clear();
dispatch({type: userLoggedIn.type, payload: {userId: '', username: ''}});
} catch (error) {
diff --git a/src/types/types.ts b/src/types/types.ts
index b012acaf..1a352808 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -1,4 +1,5 @@
import Animated from 'react-native-reanimated';
+import {Channel as ChannelType, StreamChat} from 'stream-chat';
export interface UserType {
userId: string;
@@ -289,3 +290,41 @@ export type ContactType = {
export type UniversityBadgeType = 'Search' | 'Crest';
export type BadgeDataType = Record<UniversityType, any[]>;
+
+// Stream Chat Types
+export type LocalAttachmentType = Record<string, unknown>;
+export type LocalChannelType = Record<string, unknown>;
+export type LocalCommandType = string;
+export type LocalEventType = Record<string, unknown>;
+export type LocalMessageType = Record<string, unknown>;
+export type LocalResponseType = Record<string, unknown>;
+export type LocalUserType = Record<string, unknown>;
+
+export type ChatContextType = {
+ channel:
+ | ChannelType<
+ LocalAttachmentType,
+ LocalChannelType,
+ LocalCommandType,
+ LocalEventType,
+ LocalMessageType,
+ LocalResponseType,
+ LocalUserType
+ >
+ | undefined;
+ setChannel: React.Dispatch<
+ React.SetStateAction<
+ | ChannelType<
+ LocalAttachmentType,
+ LocalChannelType,
+ LocalCommandType,
+ LocalEventType,
+ LocalMessageType,
+ LocalResponseType,
+ LocalUserType
+ >
+ | undefined
+ >
+ >;
+ chatClient: StreamChat;
+};