diff options
-rw-r--r-- | src/components/common/AcceptDeclineButtons.tsx | 2 | ||||
-rw-r--r-- | src/components/common/BasicButton.tsx | 77 | ||||
-rw-r--r-- | src/components/common/FriendsButton.tsx | 2 | ||||
-rw-r--r-- | src/components/common/index.ts | 1 | ||||
-rw-r--r-- | src/components/profile/Content.tsx | 11 | ||||
-rw-r--r-- | src/components/profile/Friends.tsx | 4 | ||||
-rw-r--r-- | src/components/profile/ProfileBody.tsx | 103 | ||||
-rw-r--r-- | src/components/profile/ToggleButton.tsx | 12 | ||||
-rw-r--r-- | src/constants/api.ts | 4 | ||||
-rw-r--r-- | src/constants/strings.ts | 1 | ||||
-rw-r--r-- | src/routes/Routes.tsx | 15 | ||||
-rw-r--r-- | src/screens/chat/ChatListScreen.tsx | 86 | ||||
-rw-r--r-- | src/screens/onboarding/CategorySelection.tsx | 4 | ||||
-rw-r--r-- | src/screens/onboarding/InvitationCodeVerification.tsx | 4 | ||||
-rw-r--r-- | src/screens/onboarding/Login.tsx | 8 | ||||
-rw-r--r-- | src/services/ChatService.ts | 22 | ||||
-rw-r--r-- | src/store/actions/user.ts | 1 | ||||
-rw-r--r-- | src/store/reducers/userReducer.ts | 1 | ||||
-rw-r--r-- | src/utils/index.ts | 1 | ||||
-rw-r--r-- | src/utils/messages.ts | 39 | ||||
-rw-r--r-- | src/utils/users.ts | 2 |
21 files changed, 318 insertions, 82 deletions
diff --git a/src/components/common/AcceptDeclineButtons.tsx b/src/components/common/AcceptDeclineButtons.tsx index 167148f0..7bb62fd4 100644 --- a/src/components/common/AcceptDeclineButtons.tsx +++ b/src/components/common/AcceptDeclineButtons.tsx @@ -58,7 +58,7 @@ const styles = StyleSheet.create({ backgroundColor: TAGG_LIGHT_BLUE, }, rejectButton: { - borderWidth: 1, + borderWidth: 2, backgroundColor: 'white', borderColor: TAGG_LIGHT_BLUE, }, diff --git a/src/components/common/BasicButton.tsx b/src/components/common/BasicButton.tsx new file mode 100644 index 00000000..1fe29cd9 --- /dev/null +++ b/src/components/common/BasicButton.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import {StyleProp, StyleSheet, Text, View, ViewStyle} from 'react-native'; +import {TAGG_LIGHT_BLUE} from '../../constants'; +import {TouchableOpacity} from 'react-native-gesture-handler'; +import {normalize} from '../../utils'; + +interface BasicButtonProps { + title: string; + onPress: () => void; + solid?: boolean; + externalStyles?: Record<string, StyleProp<ViewStyle>>; +} +const BasicButton: React.FC<BasicButtonProps> = ({ + title, + onPress, + solid, + externalStyles, +}) => { + return ( + <View style={[styles.container, externalStyles?.container]}> + <TouchableOpacity + style={[ + styles.genericButtonStyle, + solid ? styles.solidButton : styles.outlineButton, + ]} + onPress={onPress}> + <Text + style={[ + styles.buttonTitle, + solid + ? styles.solidButtonTitleColor + : styles.outlineButtonTitleColor, + ]}> + {title} + </Text> + </TouchableOpacity> + </View> + ); +}; + +const styles = StyleSheet.create({ + container: { + height: '100%', + flexDirection: 'column', + justifyContent: 'space-around', + }, + genericButtonStyle: { + justifyContent: 'center', + alignItems: 'center', + borderRadius: 3, + padding: 0, + width: '100%', + height: '100%', + }, + solidButton: { + padding: 0, + backgroundColor: TAGG_LIGHT_BLUE, + }, + outlineButton: { + borderWidth: 2, + backgroundColor: 'white', + borderColor: TAGG_LIGHT_BLUE, + }, + solidButtonTitleColor: { + color: 'white', + }, + outlineButtonTitleColor: { + color: TAGG_LIGHT_BLUE, + }, + buttonTitle: { + fontSize: normalize(15), + fontWeight: '700', + letterSpacing: 1, + }, +}); + +export default BasicButton; diff --git a/src/components/common/FriendsButton.tsx b/src/components/common/FriendsButton.tsx index 46421bd1..6ddad93f 100644 --- a/src/components/common/FriendsButton.tsx +++ b/src/components/common/FriendsButton.tsx @@ -100,7 +100,7 @@ const styles = StyleSheet.create({ button: { justifyContent: 'center', alignItems: 'center', - width: SCREEN_WIDTH * 0.4, + width: SCREEN_WIDTH * 0.42, aspectRatio: 154 / 33, borderWidth: 2, borderColor: TAGG_LIGHT_BLUE, diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 8499dbfa..b5fd0542 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -23,3 +23,4 @@ export {default as AcceptDeclineButtons} from './AcceptDeclineButtons'; export {default as FriendsButton} from './FriendsButton'; export {default as TaggSquareButton} from './TaggSquareButton'; export {default as GradientBorderButton} from './GradientBorderButton'; +export {default as BasicButton} from './BasicButton'; diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx index 05098d14..0052b61d 100644 --- a/src/components/profile/Content.tsx +++ b/src/components/profile/Content.tsx @@ -1,4 +1,10 @@ -import React, {useCallback, useEffect, useRef, useState} from 'react'; +import React, { + useCallback, + useContext, + useEffect, + useRef, + useState, +} from 'react'; import {LayoutChangeEvent, RefreshControl, StyleSheet} from 'react-native'; import Animated, { useSharedValue, @@ -31,6 +37,7 @@ import ProfileCutout from './ProfileCutout'; import ProfileHeader from './ProfileHeader'; import PublicProfile from './PublicProfile'; import {useScrollToTop} from '@react-navigation/native'; +import {ChatContext} from '../../App'; interface ContentProps { userXId: string | undefined; @@ -52,6 +59,8 @@ const Content: React.FC<ContentProps> = ({userXId, screenType}) => { ); const state: RootState = useStore().getState(); + const {chatClient} = useContext(ChatContext); + /* * Used to imperatively scroll to the top when presenting the moment tutorial. */ diff --git a/src/components/profile/Friends.tsx b/src/components/profile/Friends.tsx index c1dca755..b754b71a 100644 --- a/src/components/profile/Friends.tsx +++ b/src/components/profile/Friends.tsx @@ -191,7 +191,7 @@ const styles = StyleSheet.create({ height: '55%', borderColor: TAGG_LIGHT_BLUE, borderWidth: 2, - borderRadius: 2, + borderRadius: 3, padding: 0, backgroundColor: TAGG_LIGHT_BLUE, }, @@ -212,7 +212,7 @@ const styles = StyleSheet.create({ height: '55%', borderColor: TAGG_LIGHT_BLUE, borderWidth: 2, - borderRadius: 2, + borderRadius: 3, padding: 0, }, unfriendButtonTitle: { diff --git a/src/components/profile/ProfileBody.tsx b/src/components/profile/ProfileBody.tsx index b49e71a3..eebdb167 100644 --- a/src/components/profile/ProfileBody.tsx +++ b/src/components/profile/ProfileBody.tsx @@ -1,5 +1,12 @@ -import React from 'react'; -import {LayoutChangeEvent, Linking, StyleSheet, Text, View} from 'react-native'; +import React, {useContext} from 'react'; +import { + Alert, + LayoutChangeEvent, + Linking, + StyleSheet, + Text, + View, +} from 'react-native'; import {normalize} from 'react-native-elements'; import {useDispatch, useSelector, useStore} from 'react-redux'; import {TAGG_DARK_BLUE, TOGGLE_BUTTON_TYPE} from '../../constants'; @@ -9,12 +16,20 @@ import { updateUserXFriends, updateUserXProfileAllScreens, } from '../../store/actions'; +import {canViewProfile} from '../../utils/users'; import {NO_PROFILE} from '../../store/initialStates'; import {RootState} from '../../store/rootReducer'; import {ScreenType} from '../../types'; -import {getUserAsProfilePreviewType} from '../../utils'; -import {FriendsButton} from '../common'; +import { + connectChatAccount, + getUserAsProfilePreviewType, + SCREEN_HEIGHT, + SCREEN_WIDTH, +} from '../../utils'; +import {FriendsButton, BasicButton} from '../common'; import ToggleButton from './ToggleButton'; +import {ChatContext} from '../../App'; +import {useNavigation} from '@react-navigation/core'; interface ProfileBodyProps { onLayout: (event: LayoutChangeEvent) => void; @@ -30,6 +45,9 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({ userXId, screenType, }) => { + const dispatch = useDispatch(); + const navigation = useNavigation(); + const {profile = NO_PROFILE, user} = useSelector((state: RootState) => userXId ? state.userX[screenType][userXId] : state.user, ); @@ -46,8 +64,10 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({ profile, ); + const {chatClient, setChannel} = useContext(ChatContext); + const state: RootState = useStore().getState(); - const dispatch = useDispatch(); + const loggedInUserId = state.user.user.userId; const handleAcceptRequest = async () => { await dispatch(acceptFriendRequest({id, username, first_name, last_name})); @@ -60,6 +80,39 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({ dispatch(updateUserXProfileAllScreens(id, state)); }; + const canMessage = () => { + if ( + userXId && + !isBlocked && + (friendship_status === 'no_record' || + friendship_status === 'friends' || + (friendship_status === 'requested' && + friendship_requester_id === loggedInUserId)) && + canViewProfile(state, userXId, screenType) + ) { + return true; + } else { + return false; + } + }; + + const onPressMessage = async () => { + let connected: boolean = !chatClient.user; + if (!connected) { + connected = await connectChatAccount(loggedInUserId, chatClient); + if (!connected) { + Alert.alert('Unable to connect chat'); + } + } else { + const channel = chatClient.channel('messaging', { + members: [loggedInUserId, String(userXId)], + }); + channel.create(); + setChannel(channel); + navigation.navigate('Chat'); + } + }; + return ( <View onLayout={onLayout} style={styles.container}> <Text style={styles.username}>{`@${username}`}</Text> @@ -85,17 +138,31 @@ const ProfileBody: React.FC<ProfileBodyProps> = ({ /> </View> )} - {userXId && !isBlocked && ( - <View style={styles.buttonsContainer}> - <FriendsButton - userXId={userXId} - screenType={screenType} - friendship_requester_id={friendship_requester_id} - onAcceptRequest={handleAcceptRequest} - onRejectRequest={handleDeclineFriendRequest} - /> - </View> - )} + <View style={styles.simpleRowContainer}> + {userXId && !isBlocked && ( + <View style={styles.buttonsContainer}> + <FriendsButton + userXId={userXId} + screenType={screenType} + friendship_requester_id={friendship_requester_id} + onAcceptRequest={handleAcceptRequest} + onRejectRequest={handleDeclineFriendRequest} + /> + {canMessage() && ( + <BasicButton + title={'Message'} + onPress={onPressMessage} + externalStyles={{ + container: { + width: SCREEN_WIDTH * 0.42, + aspectRatio: 154 / 33, + }, + }} + /> + )} + </View> + )} + </View> </View> ); }; @@ -107,11 +174,15 @@ const styles = StyleSheet.create({ paddingTop: '3.5%', paddingBottom: '2%', }, + simpleRowContainer: {flexDirection: 'row'}, buttonsContainer: { flex: 1, paddingTop: '3.5%', paddingBottom: '2%', width: '50%', + height: SCREEN_HEIGHT * 0.1, + flexDirection: 'row', + justifyContent: 'space-between', }, container: { paddingVertical: '1%', diff --git a/src/components/profile/ToggleButton.tsx b/src/components/profile/ToggleButton.tsx index 236d811c..4697d744 100644 --- a/src/components/profile/ToggleButton.tsx +++ b/src/components/profile/ToggleButton.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import {StyleSheet, Text} from 'react-native'; import {TouchableOpacity} from 'react-native-gesture-handler'; import {TAGG_LIGHT_BLUE} from '../../constants'; -import {getToggleButtonText, SCREEN_WIDTH} from '../../utils'; +import {getToggleButtonText, normalize, SCREEN_WIDTH} from '../../utils'; type ToggleButtonProps = { toggleState: boolean; @@ -34,15 +34,17 @@ const styles = StyleSheet.create({ button: { justifyContent: 'center', alignItems: 'center', - width: SCREEN_WIDTH * 0.4, + width: SCREEN_WIDTH * 0.42, height: SCREEN_WIDTH * 0.08, borderColor: TAGG_LIGHT_BLUE, - borderWidth: 3, - borderRadius: 5, + borderWidth: 2, + borderRadius: 3, marginRight: '2%', }, text: { - fontWeight: 'bold', + fontWeight: '700', + fontSize: normalize(15), + letterSpacing: 1, }, buttonColor: { backgroundColor: TAGG_LIGHT_BLUE, diff --git a/src/constants/api.ts b/src/constants/api.ts index 43294386..cb45b238 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -61,6 +61,10 @@ export const UPDATE_BADGES_ENDPOINT: string = // Register as FCM device export const FCM_ENDPOINT: string = API_URL + 'fcm/'; +// Retrieve Stream Chat token +export const CHAT_ENDPOINT: string = API_URL + 'chat/'; +export const CHAT_TOKEN_ENDPOINT: string = CHAT_ENDPOINT + 'get_token/'; + // Register Social Link (Non-integrated) export const LINK_SNAPCHAT_ENDPOINT: string = API_URL + 'link-sc/'; export const LINK_TIKTOK_ENDPOINT: string = API_URL + 'link-tt/'; diff --git a/src/constants/strings.ts b/src/constants/strings.ts index 300ceb90..94f5552f 100644 --- a/src/constants/strings.ts +++ b/src/constants/strings.ts @@ -35,6 +35,7 @@ export const ERROR_PROFILE_CREATION_SHORT = 'Profile creation failed 😓'; export const ERROR_PROFILE_UPDATE_SHORT = 'Profile update failed. 😔'; export const ERROR_PWD_ACCOUNT = (str: string) => `Please make sure that the email / username entered is registered with us. You may contact our customer support at ${str}`; export const ERROR_REGISTRATION = (str: string) => `Registration failed 😔, ${str}`; +export const ERROR_CHAT_CONNECTION = `Unable to establish chat connection`; export const ERROR_SELECT_BIRTHDAY = 'Please select your birthday'; export const ERROR_SELECT_CLASS_YEAR = 'Please select your Class Year'; export const ERROR_SELECT_GENDER = 'Please select your gender'; diff --git a/src/routes/Routes.tsx b/src/routes/Routes.tsx index 819ca785..5ce0c771 100644 --- a/src/routes/Routes.tsx +++ b/src/routes/Routes.tsx @@ -1,15 +1,17 @@ +import AsyncStorage from '@react-native-community/async-storage'; import messaging from '@react-native-firebase/messaging'; -import React, {useEffect, useState} from 'react'; +import React, {useContext, useEffect, useState} from 'react'; import DeviceInfo from 'react-native-device-info'; import SplashScreen from 'react-native-splash-screen'; -import {useDispatch, useSelector} from 'react-redux'; +import {useDispatch, useSelector, useStore} from 'react-redux'; +import {ChatContext} from '../App'; import {fcmService, getCurrentLiveVersions} from '../services'; import { updateNewNotificationReceived, updateNewVersionAvailable, } from '../store/actions'; import {RootState} from '../store/rootReducer'; -import {userLogin} from '../utils'; +import {userLogin, connectChatAccount} from '../utils'; import Onboarding from './onboarding'; import NavigationBar from './tabs'; @@ -17,6 +19,9 @@ const Routes: React.FC = () => { const { user: {userId}, } = useSelector((state: RootState) => state.user); + const state: RootState = useStore().getState(); + const loggedInUserId = state.user.user.userId; + const {chatClient} = useContext(ChatContext); const [newVersionAvailable, setNewVersionAvailable] = useState(false); const dispatch = useDispatch(); @@ -53,6 +58,10 @@ const Routes: React.FC = () => { }, []); useEffect(() => { + connectChatAccount(loggedInUserId, chatClient); + }, [loggedInUserId]); + + useEffect(() => { const checkVersion = async () => { const liveVersions = await getCurrentLiveVersions(); if (liveVersions && !liveVersions.includes(DeviceInfo.getVersion())) { diff --git a/src/screens/chat/ChatListScreen.tsx b/src/screens/chat/ChatListScreen.tsx index daea9984..a019891a 100644 --- a/src/screens/chat/ChatListScreen.tsx +++ b/src/screens/chat/ChatListScreen.tsx @@ -1,7 +1,6 @@ -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 {Alert, SafeAreaView, StatusBar, StyleSheet, View} from 'react-native'; import {useStore} from 'react-redux'; import {ChannelList, Chat} from 'stream-chat-react-native'; import {ChatContext} from '../../App'; @@ -18,8 +17,9 @@ import { LocalReactionType, LocalUserType, } from '../../types'; - +import {connectChatAccount} from '../../utils'; import NewChatModal from './NewChatModal'; + type ChatListScreenNavigationProp = StackNavigationProp< MainStackParams, 'ChatList' @@ -30,11 +30,9 @@ interface ChatListScreenProps { /* * Screen that displays all of the user's active conversations. */ -const ChatListScreen: React.FC<ChatListScreenProps> = ({navigation}) => { - const {chatClient, setChannel} = useContext(ChatContext); +const ChatListScreen: React.FC<ChatListScreenProps> = () => { + const {chatClient} = useContext(ChatContext); const [modalVisible, setChatModalVisible] = useState(false); - - const [clientReady, setClientReady] = useState(false); const state: RootState = useStore().getState(); const loggedInUserId = state.user.user.userId; @@ -47,20 +45,18 @@ const ChatListScreen: React.FC<ChatListScreenProps> = ({navigation}) => { ); 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); - }); + let connected: boolean = !chatClient.user; + if (!connected) { + connectChatAccount(loggedInUserId, chatClient) + .then((success) => { + if (!success) { + Alert.alert('Something wrong with chat'); + } + }) + .catch((err) => { + console.log('Error connecting to chat: ', err); + Alert.alert('Something wrong with chat'); + }); } }, []); @@ -73,31 +69,29 @@ const ChatListScreen: React.FC<ChatListScreenProps> = ({navigation}) => { setChatModalVisible(true); }} /> - {clientReady && ( - <Chat client={chatClient}> - <View style={styles.chatContainer}> - <ChannelList< - LocalAttachmentType, - LocalChannelType, - LocalCommandType, - LocalEventType, - LocalMessageType, - LocalReactionType, - LocalUserType - > - filters={memoizedFilters} - options={{ - presence: true, - state: true, - watch: true, - }} - sort={{last_message_at: -1}} - maxUnreadCount={99} - Preview={ChannelPreview} - /> - </View> - </Chat> - )} + <Chat client={chatClient}> + <View style={styles.chatContainer}> + <ChannelList< + LocalAttachmentType, + LocalChannelType, + LocalCommandType, + LocalEventType, + LocalMessageType, + LocalReactionType, + LocalUserType + > + filters={memoizedFilters} + options={{ + presence: true, + state: true, + watch: true, + }} + sort={{last_message_at: -1}} + maxUnreadCount={99} + Preview={ChannelPreview} + /> + </View> + </Chat> <NewChatModal {...{modalVisible, setChatModalVisible}} /> </SafeAreaView> <TabsGradient /> diff --git a/src/screens/onboarding/CategorySelection.tsx b/src/screens/onboarding/CategorySelection.tsx index 9d5fbe4d..ab5ff3be 100644 --- a/src/screens/onboarding/CategorySelection.tsx +++ b/src/screens/onboarding/CategorySelection.tsx @@ -1,6 +1,6 @@ import {RouteProp} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; -import React, {useEffect, useState} from 'react'; +import React, {useContext, useEffect, useState} from 'react'; import { Alert, Platform, @@ -12,6 +12,7 @@ import { } from 'react-native'; import {ScrollView} from 'react-native-gesture-handler'; import {useDispatch, useSelector} from 'react-redux'; +import {ChatContext} from '../../App'; import PlusIcon from '../../assets/icons/plus_icon-01.svg'; import {Background, MomentCategory} from '../../components'; import {MOMENT_CATEGORIES} from '../../constants'; @@ -49,6 +50,7 @@ const CategorySelection: React.FC<CategorySelectionProps> = ({ * Same component to be used for category selection while onboarding and while on profile */ const {screenType, user} = route.params; + const {chatClient} = useContext(ChatContext); const isOnBoarding: boolean = screenType === CategorySelectionScreenType.Onboarding; const {userId, username} = user; diff --git a/src/screens/onboarding/InvitationCodeVerification.tsx b/src/screens/onboarding/InvitationCodeVerification.tsx index e160b4b7..6bc0ac9d 100644 --- a/src/screens/onboarding/InvitationCodeVerification.tsx +++ b/src/screens/onboarding/InvitationCodeVerification.tsx @@ -1,7 +1,7 @@ import AsyncStorage from '@react-native-community/async-storage'; import {RouteProp} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; -import React from 'react'; +import React, {useContext} from 'react'; import {Alert, KeyboardAvoidingView, StyleSheet, View} from 'react-native'; import {Text} from 'react-native-animatable'; import { @@ -27,6 +27,7 @@ import { import {OnboardingStackParams} from '../../routes'; import {BackgroundGradientType} from '../../types'; import {SCREEN_WIDTH, userLogin} from '../../utils'; +import {ChatContext} from '../../App'; type InvitationCodeVerificationRouteProp = RouteProp< OnboardingStackParams, @@ -58,6 +59,7 @@ const InvitationCodeVerification: React.FC<InvitationCodeVerificationProps> = ({ setValue, }); const dispatch = useDispatch(); + const {chatClient} = useContext(ChatContext); const handleInvitationCodeVerification = async () => { if (value.length === 6) { diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx index dd2bb2e4..6d9abf82 100644 --- a/src/screens/onboarding/Login.tsx +++ b/src/screens/onboarding/Login.tsx @@ -1,7 +1,7 @@ import AsyncStorage from '@react-native-community/async-storage'; import {RouteProp} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; -import React, {useEffect, useRef} from 'react'; +import React, {useContext, useEffect, useRef} from 'react'; import { Alert, Image, @@ -14,6 +14,7 @@ import { } from 'react-native'; import SplashScreen from 'react-native-splash-screen'; import {useDispatch, useSelector} from 'react-redux'; +import {ChatContext} from '../../App'; import {Background, TaggInput, TaggSquareButton} from '../../components'; import {LOGIN_ENDPOINT, usernameRegex} from '../../constants'; import { @@ -28,7 +29,7 @@ import {OnboardingStackParams} from '../../routes/onboarding'; import {fcmService} from '../../services'; import {RootState} from '../../store/rootReducer'; import {BackgroundGradientType, UniversityType} from '../../types'; -import {normalize, userLogin} from '../../utils'; +import {connectChatAccount, normalize, userLogin} from '../../utils'; import UpdateRequired from './UpdateRequired'; type VerificationScreenRouteProp = RouteProp<OnboardingStackParams, 'Login'>; @@ -47,6 +48,7 @@ interface LoginProps { const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => { // ref for focusing on input fields const inputRef = useRef(); + const {chatClient} = useContext(ChatContext); // login form state const [form, setForm] = React.useState({ @@ -160,7 +162,6 @@ 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) { @@ -168,6 +169,7 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => { try { userLogin(dispatch, {userId: data.UserID, username}); fcmService.sendFcmTokenToServer(); + connectChatAccount(data.UserID, chatClient); } catch (err) { Alert.alert(ERROR_INVALID_LOGIN); } diff --git a/src/services/ChatService.ts b/src/services/ChatService.ts new file mode 100644 index 00000000..e9b1c284 --- /dev/null +++ b/src/services/ChatService.ts @@ -0,0 +1,22 @@ +import AsyncStorage from '@react-native-community/async-storage'; +import {CHAT_TOKEN_ENDPOINT} from '../constants/api'; + +export const loadChatTokenService = async () => { + try { + const token = await AsyncStorage.getItem('token'); + const response = await fetch(CHAT_TOKEN_ENDPOINT, { + method: 'GET', + headers: { + Authorization: 'Token ' + token, + }, + }); + const status = response.status; + if (status === 200) { + const data = await response.json(); + return data.chatToken; + } + return ''; + } catch (error) { + console.log('Error loading chat token in service'); + } +}; diff --git a/src/store/actions/user.ts b/src/store/actions/user.ts index 3ebd4190..c7d0d5a7 100644 --- a/src/store/actions/user.ts +++ b/src/store/actions/user.ts @@ -233,3 +233,4 @@ export const suggestedPeopleAnimatedTutorialFinished = ( console.log('Error while updating suggested people linked state: ', error); } }; + diff --git a/src/store/reducers/userReducer.ts b/src/store/reducers/userReducer.ts index 9ff9ba01..a8789c1d 100644 --- a/src/store/reducers/userReducer.ts +++ b/src/store/reducers/userReducer.ts @@ -90,5 +90,6 @@ export const { setReplyPosted, setSuggestedPeopleImage, clearHeaderAndProfileImages, + // setChatClientReady, } = userDataSlice.actions; export const userDataReducer = userDataSlice.reducer; diff --git a/src/utils/index.ts b/src/utils/index.ts index 739e6fb8..4ff9afac 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,3 +4,4 @@ export * from './common'; export * from './users'; export * from './friends'; export * from './search'; +export * from './messages'; diff --git a/src/utils/messages.ts b/src/utils/messages.ts index d63f2b7a..dc01d579 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -1,4 +1,6 @@ +import AsyncStorage from '@react-native-community/async-storage'; import moment from 'moment'; +import {loadChatTokenService} from '../services/ChatService'; import {RootState} from '../store/rootReducer'; import {ChannelGroupedType} from '../types'; @@ -81,3 +83,40 @@ export const getMember = ( : []; return otherMembers.length === 1 ? otherMembers[0] : undefined; }; + +export const connectChatAccount = async ( + loggedInUserId: string, + chatClient, +) => { + try { + await getChatToken(); + const chatToken = await AsyncStorage.getItem('chatToken'); + if (chatToken) { + await chatClient.connectUser( + { + id: loggedInUserId, + }, + chatToken, + ); + return true; + } else { + console.log('Unable to connect to stream. Empty chat token'); + return false; + } + } catch (err) { + console.log('Error while connecting user to Stream: ', err); + return false; + } +}; + +export const getChatToken = async () => { + try { + const currentChatToken = await AsyncStorage.getItem('chatToken'); + if (currentChatToken === null) { + const chatToken = await loadChatTokenService(); + await AsyncStorage.setItem('chatToken', chatToken); + } + } catch (err) { + console.log(err); + } +}; diff --git a/src/utils/users.ts b/src/utils/users.ts index 22c1c1f0..7148eb79 100644 --- a/src/utils/users.ts +++ b/src/utils/users.ts @@ -12,14 +12,12 @@ import { logout, } from '../store/actions'; import {NO_SOCIAL_ACCOUNTS} from '../store/initialStates'; -import {userLoggedIn} from '../store/reducers'; import {loadUserMomentCategories} from './../store/actions/momentCategories'; import {loadUserX} from './../store/actions/userX'; import {AppDispatch} from './../store/configureStore'; import {RootState} from './../store/rootReducer'; import { ProfilePreviewType, - CategoryPreviewType, ProfileInfoType, ScreenType, UserType, |