aboutsummaryrefslogtreecommitdiff
path: root/src/screens
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens')
-rw-r--r--src/screens/chat/ChatListScreen.tsx15
-rw-r--r--src/screens/chat/ChatResultsCell.tsx117
-rw-r--r--src/screens/chat/ChatResultsList.tsx102
-rw-r--r--src/screens/chat/ChatSearchBar.tsx112
-rw-r--r--src/screens/chat/NewChatModal.tsx161
-rw-r--r--src/screens/chat/index.ts4
6 files changed, 503 insertions, 8 deletions
diff --git a/src/screens/chat/ChatListScreen.tsx b/src/screens/chat/ChatListScreen.tsx
index 3290116b..daea9984 100644
--- a/src/screens/chat/ChatListScreen.tsx
+++ b/src/screens/chat/ChatListScreen.tsx
@@ -19,6 +19,7 @@ import {
LocalUserType,
} from '../../types';
+import NewChatModal from './NewChatModal';
type ChatListScreenNavigationProp = StackNavigationProp<
MainStackParams,
'ChatList'
@@ -29,8 +30,10 @@ interface ChatListScreenProps {
/*
* Screen that displays all of the user's active conversations.
*/
-const ChatListScreen: React.FC<ChatListScreenProps> = () => {
- const {chatClient} = useContext(ChatContext);
+const ChatListScreen: React.FC<ChatListScreenProps> = ({navigation}) => {
+ const {chatClient, setChannel} = useContext(ChatContext);
+ const [modalVisible, setChatModalVisible] = useState(false);
+
const [clientReady, setClientReady] = useState(false);
const state: RootState = useStore().getState();
const loggedInUserId = state.user.user.userId;
@@ -67,12 +70,7 @@ const ChatListScreen: React.FC<ChatListScreenProps> = () => {
<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();
+ setChatModalVisible(true);
}}
/>
{clientReady && (
@@ -100,6 +98,7 @@ const ChatListScreen: React.FC<ChatListScreenProps> = () => {
</View>
</Chat>
)}
+ <NewChatModal {...{modalVisible, setChatModalVisible}} />
</SafeAreaView>
<TabsGradient />
</View>
diff --git a/src/screens/chat/ChatResultsCell.tsx b/src/screens/chat/ChatResultsCell.tsx
new file mode 100644
index 00000000..d947c122
--- /dev/null
+++ b/src/screens/chat/ChatResultsCell.tsx
@@ -0,0 +1,117 @@
+import {useNavigation} from '@react-navigation/native';
+import React, {useContext, useEffect, useState} from 'react';
+import {Alert, Image, StyleSheet, Text, View} from 'react-native';
+import {TouchableOpacity} from 'react-native-gesture-handler';
+import {ChatContext} from '../../App';
+import {ERROR_FAILED_TO_CREATE_CHANNEL} from '../../constants/strings';
+import {loadImageFromURL} from '../../services';
+import {ProfilePreviewType, UserType} from '../../types';
+import {createChannel, normalize, SCREEN_WIDTH} from '../../utils';
+import {defaultUserProfile} from '../../utils/users';
+
+interface ChatResults {
+ profileData: ProfilePreviewType;
+ loggedInUser: UserType;
+ setChatModalVisible: Function;
+}
+
+const ChatResultsCell: React.FC<ChatResults> = ({
+ profileData: {id, username, first_name, last_name, thumbnail_url},
+ loggedInUser,
+ setChatModalVisible,
+}) => {
+ const [avatar, setAvatar] = useState<string | undefined>(undefined);
+ const {chatClient, setChannel} = useContext(ChatContext);
+
+ useEffect(() => {
+ (async () => {
+ if (thumbnail_url !== undefined) {
+ try {
+ const response = await loadImageFromURL(thumbnail_url);
+ if (response) {
+ setAvatar(response);
+ }
+ } catch (error) {
+ console.log('Error while downloading ', error);
+ throw error;
+ }
+ }
+ })();
+ }, [thumbnail_url]);
+
+ const navigation = useNavigation();
+ const createChannelIfNotPresentAndNavigate = async () => {
+ try {
+ setChatModalVisible(false);
+ const channel = await createChannel(loggedInUser.userId, id, chatClient);
+ setChannel(channel);
+ setTimeout(() => {
+ navigation.navigate('Chat');
+ }, 100);
+ } catch (error) {
+ Alert.alert(ERROR_FAILED_TO_CREATE_CHANNEL);
+ }
+ };
+
+ const userCell = () => {
+ return (
+ <TouchableOpacity
+ onPress={createChannelIfNotPresentAndNavigate}
+ style={styles.cellContainer}>
+ <Image
+ defaultSource={defaultUserProfile()}
+ source={{uri: avatar}}
+ style={styles.imageContainer}
+ />
+ <View style={[styles.initialTextContainer, styles.multiText]}>
+ <Text style={styles.initialTextStyle}>{`@${username}`}</Text>
+ <Text style={styles.secondaryTextStyle}>
+ {first_name + ' ' + last_name}
+ </Text>
+ </View>
+ </TouchableOpacity>
+ );
+ };
+
+ return userCell();
+};
+
+const styles = StyleSheet.create({
+ cellContainer: {
+ flexDirection: 'row',
+ paddingHorizontal: 25,
+ paddingVertical: 15,
+ width: SCREEN_WIDTH,
+ },
+ imageContainer: {
+ width: SCREEN_WIDTH * 0.112,
+ height: SCREEN_WIDTH * 0.112,
+ borderRadius: (SCREEN_WIDTH * 0.112) / 2,
+ },
+ categoryBackground: {
+ backgroundColor: 'rgba(196, 196, 196, 0.45)',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ categoryImage: {
+ width: '40%',
+ height: '40%',
+ },
+ initialTextContainer: {
+ marginLeft: SCREEN_WIDTH * 0.08,
+ flexDirection: 'column',
+ justifyContent: 'center',
+ },
+ initialTextStyle: {
+ fontWeight: '500',
+ fontSize: normalize(14),
+ },
+ secondaryTextStyle: {
+ fontWeight: '500',
+ fontSize: normalize(12),
+ color: '#828282',
+ },
+ multiText: {justifyContent: 'space-between'},
+});
+
+export default ChatResultsCell;
diff --git a/src/screens/chat/ChatResultsList.tsx b/src/screens/chat/ChatResultsList.tsx
new file mode 100644
index 00000000..b9970772
--- /dev/null
+++ b/src/screens/chat/ChatResultsList.tsx
@@ -0,0 +1,102 @@
+import {useBottomTabBarHeight} from '@react-navigation/bottom-tabs';
+import React, {useEffect, useState} from 'react';
+import {
+ Keyboard,
+ SectionList,
+ SectionListData,
+ StyleSheet,
+ Text,
+ View,
+} from 'react-native';
+import {useSelector} from 'react-redux';
+import {NO_RESULTS_FOUND} from '../../constants/strings';
+import {RootState} from '../../store/rootreducer';
+import {PreviewType, ScreenType} from '../../types';
+import {normalize, SCREEN_WIDTH} from '../../utils';
+import ChatResultsCell from './ChatResultsCell';
+
+interface ChatResultsProps {
+ // TODO: make sure results come in as same type, regardless of profile, category, badges
+ results: SectionListData<any>[];
+ previewType: PreviewType;
+ screenType: ScreenType;
+ setChatModalVisible: Function;
+}
+
+const ChatResultsList: React.FC<ChatResultsProps> = ({
+ results,
+ setChatModalVisible,
+}) => {
+ const [showEmptyView, setshowEmptyView] = useState<boolean>(false);
+ const {user: loggedInUser} = useSelector((state: RootState) => state.user);
+ const tabbarHeight = useBottomTabBarHeight();
+
+ useEffect(() => {
+ if (results && results.length > 0) {
+ let showEmpty = true;
+
+ results.forEach((e) => {
+ if (e.data.length > 0) {
+ showEmpty = false;
+ }
+ });
+ setshowEmptyView(showEmpty);
+ }
+ }, [results]);
+
+ return showEmptyView ? (
+ <View style={styles.container} onTouchStart={Keyboard.dismiss}>
+ <Text style={styles.noResultsTextStyle}>{NO_RESULTS_FOUND}</Text>
+ </View>
+ ) : (
+ <SectionList
+ onScrollBeginDrag={Keyboard.dismiss}
+ contentContainerStyle={[{paddingBottom: tabbarHeight}]}
+ sections={results}
+ keyExtractor={(item, index) => item.id + index}
+ renderItem={({item}) => (
+ <ChatResultsCell
+ profileData={item}
+ setChatModalVisible={setChatModalVisible}
+ loggedInUser={loggedInUser}
+ />
+ )}
+ stickySectionHeadersEnabled={false}
+ ListEmptyComponent={() => (
+ <View style={styles.empty}>
+ <Text>Start a new chat by searching for someone</Text>
+ </View>
+ )}
+ />
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ marginTop: 30,
+ alignItems: 'center',
+ },
+ sectionHeaderStyle: {
+ width: '100%',
+ height: 0.5,
+ marginVertical: 5,
+ backgroundColor: '#C4C4C4',
+ },
+ noResultsTextContainer: {
+ justifyContent: 'center',
+ flexDirection: 'row',
+ width: SCREEN_WIDTH,
+ },
+ noResultsTextStyle: {
+ fontWeight: '500',
+ fontSize: normalize(14),
+ },
+ empty: {
+ marginTop: 20,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+});
+
+export default ChatResultsList;
diff --git a/src/screens/chat/ChatSearchBar.tsx b/src/screens/chat/ChatSearchBar.tsx
new file mode 100644
index 00000000..4916ec45
--- /dev/null
+++ b/src/screens/chat/ChatSearchBar.tsx
@@ -0,0 +1,112 @@
+import React from 'react';
+import {
+ Keyboard,
+ NativeSyntheticEvent,
+ StyleSheet,
+ Text,
+ TextInput,
+ TextInputProps,
+ TextInputSubmitEditingEventData,
+ TouchableOpacity,
+ View,
+ ViewStyle,
+} from 'react-native';
+import {normalize} from 'react-native-elements';
+import Animated, {useAnimatedStyle} from 'react-native-reanimated';
+
+interface SearchBarProps extends TextInputProps {
+ onCancel: () => void;
+ animationProgress: Animated.SharedValue<number>;
+ searching: boolean;
+ placeholder: string;
+}
+const ChatSearchBar: React.FC<SearchBarProps> = ({
+ onFocus,
+ onBlur,
+ onChangeText,
+ value,
+ onCancel,
+ searching,
+ animationProgress,
+ onLayout,
+ placeholder,
+}) => {
+ const handleSubmit = (
+ e: NativeSyntheticEvent<TextInputSubmitEditingEventData>,
+ ) => {
+ e.preventDefault();
+ Keyboard.dismiss();
+ };
+
+ /*
+ * On-search marginRight style ("cancel" button slides and fades in).
+ */
+ const animatedStyles = useAnimatedStyle<ViewStyle>(() => ({
+ marginRight: animationProgress.value * 58,
+ opacity: animationProgress.value,
+ }));
+
+ return (
+ <View style={styles.container} onLayout={onLayout}>
+ <Animated.View style={styles.inputContainer}>
+ <Animated.View style={styles.searchTextContainer}>
+ <Text style={styles.searchTextStyes}>To:</Text>
+ </Animated.View>
+ <TextInput
+ style={[styles.input]}
+ placeholderTextColor={'#828282'}
+ onSubmitEditing={handleSubmit}
+ clearButtonMode="always"
+ autoCapitalize="none"
+ autoCorrect={false}
+ {...{placeholder, value, onChangeText, onFocus, onBlur}}
+ />
+ </Animated.View>
+ <Animated.View style={animatedStyles}>
+ <TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
+ <Text style={styles.cancelText}>Cancel</Text>
+ </TouchableOpacity>
+ </Animated.View>
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ height: 40,
+ paddingHorizontal: 20,
+ flexDirection: 'row',
+ zIndex: 2,
+ },
+ searchTextContainer: {marginHorizontal: 12},
+ searchTextStyes: {fontWeight: 'bold', fontSize: 14, lineHeight: 17},
+ inputContainer: {
+ flexGrow: 1,
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingHorizontal: 8,
+ borderRadius: 20,
+ backgroundColor: '#F0F0F0',
+ },
+ searchIcon: {
+ marginRight: 8,
+ },
+ input: {
+ flex: 1,
+ fontSize: 16,
+ color: '#000',
+ letterSpacing: normalize(0.5),
+ },
+ cancelButton: {
+ height: '100%',
+ position: 'absolute',
+ justifyContent: 'center',
+ paddingHorizontal: 8,
+ },
+ cancelText: {
+ color: '#818181',
+ fontWeight: '500',
+ },
+});
+
+export default ChatSearchBar;
diff --git a/src/screens/chat/NewChatModal.tsx b/src/screens/chat/NewChatModal.tsx
new file mode 100644
index 00000000..95e46ecd
--- /dev/null
+++ b/src/screens/chat/NewChatModal.tsx
@@ -0,0 +1,161 @@
+import React, {useEffect, useState} from 'react';
+import {
+ Keyboard,
+ SectionListData,
+ StatusBar,
+ StyleSheet,
+ Text,
+ View,
+} from 'react-native';
+import {useSharedValue} from 'react-native-reanimated';
+import {BottomDrawer} from '../../components';
+import {
+ SEARCH_ENDPOINT_MESSAGES,
+ SEARCH_ENDPOINT_SUGGESTED,
+} from '../../constants';
+import {loadSearchResults} from '../../services';
+import {ScreenType} from '../../types';
+import {normalize} from '../../utils';
+import {ChatResultsList, ChatSearchBar} from './index';
+interface NewChatModalProps {
+ modalVisible: boolean;
+ setChatModalVisible: (open: boolean) => void;
+}
+
+const NewChatModal: React.FC<NewChatModalProps> = ({
+ modalVisible,
+ setChatModalVisible,
+}) => {
+ const [searching, setSearching] = useState(false);
+ /*
+ * Animated value
+ */
+ const animationProgress = useSharedValue<number>(0);
+ const [results, setResults] = useState<SectionListData<any>[]>([]);
+ const [query, setQuery] = useState<string>('');
+ const handleFocus = () => {
+ setSearching(true);
+ };
+ const handleBlur = () => {
+ Keyboard.dismiss();
+ };
+ const handleCancel = () => {
+ setSearching(false);
+ };
+
+ const getDefaultSuggested = async () => {
+ const searchResults = await loadSearchResults(
+ `${SEARCH_ENDPOINT_SUGGESTED}`,
+ );
+ console.log(searchResults);
+ const sanitizedResult = [
+ {
+ title: 'users',
+ data: searchResults?.users,
+ },
+ ];
+ console.log(searchResults, sanitizedResult);
+ setResults(sanitizedResult);
+ };
+
+ const getQuerySuggested = async () => {
+ const searchResults = await loadSearchResults(
+ `${SEARCH_ENDPOINT_MESSAGES}?query=${query}`,
+ );
+ if (query.length > 2) {
+ const sanitizedResult = [
+ {
+ title: 'users',
+ data: searchResults?.users,
+ },
+ ];
+ setResults(sanitizedResult);
+ } else {
+ setResults([]);
+ }
+ };
+
+ useEffect(() => {
+ if (query.length === 0) {
+ getDefaultSuggested();
+ }
+
+ if (!searching) {
+ return;
+ }
+
+ if (query.length < 3) {
+ return;
+ }
+ getQuerySuggested();
+ }, [query]);
+
+ const _modalContent = () => {
+ return (
+ <View style={styles.modalShadowContainer}>
+ <View style={styles.titleContainerStyles}>
+ <Text style={styles.titleTextStyles}>New Message</Text>
+ </View>
+ <ChatSearchBar
+ onCancel={handleCancel}
+ onChangeText={setQuery}
+ onBlur={handleBlur}
+ onFocus={handleFocus}
+ value={query}
+ {...{animationProgress, searching}}
+ placeholder={''}
+ />
+ {results.length > 0 && (
+ <View style={styles.headerContainerStyles}>
+ <Text style={styles.headerTextStyles}>Suggested</Text>
+ </View>
+ )}
+ <ChatResultsList
+ {...{results, setChatModalVisible}}
+ previewType={'Search'}
+ screenType={ScreenType.Search}
+ />
+ </View>
+ );
+ };
+
+ return (
+ <View>
+ <StatusBar barStyle="dark-content" />
+ <BottomDrawer
+ initialSnapPosition={'90%'}
+ isOpen={modalVisible}
+ setIsOpen={setChatModalVisible}
+ showHeader={false}>
+ {_modalContent()}
+ </BottomDrawer>
+ </View>
+ );
+};
+
+const styles = StyleSheet.create({
+ modalShadowContainer: {
+ height: '100%',
+ borderRadius: 9,
+ backgroundColor: 'white',
+ },
+ titleContainerStyles: {marginVertical: 24},
+ titleTextStyles: {
+ fontWeight: 'bold',
+ fontSize: normalize(18),
+ lineHeight: normalize(21),
+ textAlign: 'center',
+ },
+ headerContainerStyles: {
+ marginTop: 26,
+ marginBottom: 10,
+ marginHorizontal: 28,
+ },
+ headerTextStyles: {
+ fontWeight: 'bold',
+ fontSize: normalize(17),
+ lineHeight: normalize(20),
+ },
+});
+
+export default NewChatModal;
diff --git a/src/screens/chat/index.ts b/src/screens/chat/index.ts
index d2ccb02b..328eb8bf 100644
--- a/src/screens/chat/index.ts
+++ b/src/screens/chat/index.ts
@@ -1,2 +1,6 @@
export {default as ChatListScreen} from './ChatListScreen';
export {default as ChatScreen} from './ChatScreen';
+export {default as NewChatModal} from './NewChatModal';
+export {default as ChatSearchBar} from './ChatSearchBar';
+export {default as ChatResultsList} from './ChatResultsList';
+export {default as ChatResultsCell} from './ChatResultsCell';