0% found this document useful (0 votes)
15 views

Untitled Document-9

This document contains code for React Native screens used in a messaging app. The NewChatScreen allows users to search for other users to start a new chat with. It displays a loading indicator while searching, and results or a no results message. It also conditions rendering based on whether it is for a group chat. The ChatListScreen displays a list of a user's existing chats, sorted by most recent. It includes options to start a new chat or group chat. Upon selecting a user, it navigates to the ChatScreen for that conversation.

Uploaded by

Fred
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views

Untitled Document-9

This document contains code for React Native screens used in a messaging app. The NewChatScreen allows users to search for other users to start a new chat with. It displays a loading indicator while searching, and results or a no results message. It also conditions rendering based on whether it is for a group chat. The ChatListScreen displays a list of a user's existing chats, sorted by most recent. It includes options to start a new chat or group chat. Upon selecting a user, it navigates to the ChatScreen for that conversation.

Uploaded by

Fred
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 18

import React, { useEffect, useState } from "react";

import { View, Text, StyleSheet, Button, TextInput, ActivityIndicator, FlatList } from


"react-native";
import { HeaderButtons, Item } from "react-navigation-header-buttons";
import CustomHeaderButton from "../components/CustomHeaderButton";
import PageContainer from "../components/PageContainer";
import { Feather } from "@expo/vector-icons";
import colors from "../constants/colors";
import commonStyles from "../constants/commonStyles";
import { searchUsers } from "../components/utils/actions/userActions";
import DataItem from "../components/DataItem";
import { useDispatch, useSelector } from "react-redux";
import { setStoredUsers } from "../store/userSlice";

const NewChatScreen = (props) => {

const dispatch = useDispatch();

const [isLoading, setIsLoading] = useState(false);


const [users, setUsers] = useState();
const [noResultsFound, setNoResultsFound] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [chatName, setChatName] = useState('');

//Gets the logged in user's data


const userData = useSelector((state) => state.auth.userData);

//Checks if it is a group chat


const isGroupChat = props.route.params && props.route.params.isGroupChat;

//Disables group chat creation button


const isGroupChatDisabled = chatName === "";

//this will be the button that allows us to close the page or create group chat
useEffect(() => {
props.navigation.setOptions({
headerLeft: () => {
return (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item title="Close" onPress={() => props.navigation.goBack()} />
</HeaderButtons>
);
},
headerRight: () => {
return (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
{ //Only renders if it is a group chat
isGroupChat &&
<Item title="Create" onPress={() => {}} />
}
</HeaderButtons>
);
},
headerTitle: isGroupChat ? "Add participants" : "New chat",
});
}, []);

//auto searching for users, to make it less expensive


useEffect(() => {
const delaySearch = setTimeout(async () => {
if (!searchTerm || searchTerm === "") {
setUsers();
setNoResultsFound(false);
return;
}

//Toggles the no users found page


setIsLoading(true);

const usersResult = await searchUsers(searchTerm);


delete usersResult[userData.userId];
setUsers(usersResult);

if (Object.keys(usersResult).length === 0){


setNoResultsFound(true);
}
else {
setNoResultsFound(false);

dispatch(setStoredUsers({ newUsers: usersResult}))


}
setIsLoading(false);
}, 500);
//Each time use effect renders, clear the timeout
return () => clearTimeout(delaySearch);
}, [searchTerm])

const userPressed = userId => {


props.navigation.navigate("ChatList", {
selectedUserId: userId,
});
}

//This will return the searchbox for chats


return (
//if GroupChat?
<PageContainer>
{isGroupChat && (
<View style={styles.chatNameContainer}>
<View style={styles.inputContainer}>
<TextInput
style={styles.textbox}
placeholder="Enter a chat name"
autoCorrect={false}
autoComplete={false}
/>
</View>
</View>
)}
{/*Sets the view for the search bar*/}
<View style={styles.searchContainer}>
<Feather name="search" size={15} color={colors.lightGrey} />

<TextInput
placeholder="Search"
style={styles.searchBox}
onChangeText={(text) => setSearchTerm(text)}
/>
</View>

{/*Activity indicator when loading*/}


{isLoading && (
<View style={commonStyles.center}>
<ActivityIndicator size={"large"} color={colors.primary} />
</View>
)}

{/*Passes the data through and displays it in the search tab */}
{!isLoading && !noResultsFound && users && (
<FlatList
data={Object.keys(users)}
renderItem={(itemData) => {
const userId = itemData.item;
const userData = users[userId];

return (
<DataItem
title={`${userData.firstName} ${userData.lastName}`}
subTitle={userData.about}
image={userData.profilePicture}
onPress={() => userPressed(userId)}
/>
);
}}
/>
)}

{!isLoading && noResultsFound && (


<View style={commonStyles.center}>
<Feather
name="help-circle"
size={55}
color={colors.lightGrey}
style={styles.noResultsIcon}
/>
<Text style={styles.noResultsText}>No users found...</Text>
</View>
)}

{!isLoading && !users && (


<View style={commonStyles.center}>
<Feather
name="users"
size={55}
color={colors.lightGrey}
style={styles.noResultsIcon}
/>
<Text style={styles.noResultsText}>
Enter a name to search for a user
</Text>
</View>
)}
</PageContainer>
);
};

const styles = StyleSheet.create({


searchContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.extraLightGrey,
height: 30,
marginVertical: 8,
paddingHorizontal: 8,
paddingVertical: 5,
borderRadius: 5
},
searchBox: {
marginleft: 8,
fontSize: 15,
width: '100%'
},
noResultsicon: {
marginBottom: 20
},
noResultsText: {
color: colors.textColor,
fontFamily: 'regular',
letterSpacing: 0.3
},
chatNameContainer: {
paddingVertical: 10,

},
inputContainer: {
width: '100%',
paddingHorizontal: 15,
paddingVertical: 15,
backgroundColor: colors.nearlyWhite,
flexDirection: 'row',
borderRadius: 2
},
textbox: {
color: colors.textColor,
width: '100%',
fontFamily: 'regular',
letterSpacing: 0.3
}
});

export default NewChatScreen;

import React, { useEffect } from "react";


import { View, Text, StyleSheet, Button, FlatList, TouchableOpacity } from
"react-native";
import { HeaderButtons, Item } from "react-navigation-header-buttons";
import { useSelector } from "react-redux";
import CustomHeaderButton from "../components/CustomHeaderButton";
import DataItem from "../components/DataItem";
import PageContainer from "../components/PageContainer";
import PageTitle from "../components/PageTitle";
import colors from "../constants/colors";

const ChatListScreen = (props) => {


//Accesses parameters passed through to screen
const selectedUser = props.route?.params?.selectedUserId;

const storedUsers = useSelector((state) => state.users.storedUsers);

//Gets the logged in user's data


const userData = useSelector((state) => state.auth.userData);
//Gets chat data
const userChats = useSelector(state => {
const chatsData = state.chats.chatsData;
//Returns chat data as an object with values
return Object.values(chatsData).sort((a,b) => {
//Sorts chats displayed according to most recent
return new Date(b.updatedAt) - new Date(a.updatedAt);
});
});

//New chat button


useEffect(() => {
props.navigation.setOptions({
headerRight: () => {
return (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="New chat"
iconName="ios-create-outline"
onPress={() => props.navigation.navigate("NewChat")}
/>
</HeaderButtons>
);
},
});
}, []);

//Handles navigating to chat page


useEffect(() => {
if (!selectedUser) {
return;
}

const chatUsers = [selectedUser, userData.userId];

const navigationProps = {
newChatData: { users: chatUsers },
};

props.navigation.navigate("ChatScreen", navigationProps);
}, [props.route?.params]);

return (
<PageContainer>
<PageTitle text="Chats" />

{/*Group chat button*/}


<View>
<TouchableOpacity onPress={() => props.navigation.navigate("NewChat",
{isGroupChat: true})}>
<Text style={styles.newGroupText}>New Group</Text>
</TouchableOpacity>
</View>

{/*Returns the chat data within a flatlist*/}


<FlatList
data={userChats}
renderItem={(itemData) => {
const chatData = itemData.item;
const chatId = chatData.key;

//Find the first user that isnt the user we are logged in as
const otherUserId = chatData.users.find(
(uid) => uid !== userData.userId
);
//accesses the stored user data
const otherUser = storedUsers[otherUserId];
if (!otherUser) return;

const title = `${otherUser.firstName} ${otherUser.lastName}`;


const subTitle = chatData.latestMessageText || "New chat";
const image = otherUser.profilePicture;

return (
<DataItem
title={title}
subTitle={subTitle}
image={image}
onPress={() =>
props.navigation.navigate("ChatScreen", { chatId })
}
/>
);
}}
/>
</PageContainer>
);
};

const styles = StyleSheet.create({


container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
newGroupText: {
color: colors.blue,
fontSize: 17,
marginBottom: 5
}
});
export default ChatListScreen;

import React, { useCallback, useEffect, useRef, useState } from "react";


import {
View,
Text,
StyleSheet,
Button,
ImageBackground,
TextInput,
TouchableOpacity,
KeyboardAvoidingView,
Image,
ActivityIndicator,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Feather } from "@expo/vector-icons";

import backgroundImage from "../assets/images/background.jpg";


import colors from "../constants/colors";
import { useSelector } from "react-redux";
import PageContainer from "../components/PageContainer";
import Bubble from "../components/Bubble";
import { createChat, sendImage, sendTextMessage } from
"../components/utils/actions/chatActions";
import { FlatList } from "react-native-gesture-handler";
import ReplyTo from "../components/ReplyTo";
import {launchImagePicker, openCamera, uploadImageAsync} from
"../components/utils/imagePickerHelper";
import AwesomeAlert from "react-native-awesome-alerts";

const ChatScreen = (props) => {


const [chatUsers, setChatUsers] = useState([]);
//messageText contains the state and data that the user has entered in textbox
const [messageText, setMessageText] = useState("");
const [chatId, setChatId] = useState(props.route?.params?.chatId);
//Error banner state manager
const [errorBannerText, setErrorBannerText] = useState("");
//Reply state manager
const [replyingTo, setReplyingTo] = useState();
//Image state manager
const [tempImageUri, setTempImageUri] = useState("");
//Loading state manager (for image uploading)
const [isLoading, setIsLoading] = useState(false);

//Handles automatic scrolling of page to bottom


const flatList = useRef();

//userData contains the logged in user's data


const userData = useSelector((state) => state.auth.userData);
const storedUsers = useSelector((state) => state.users.storedUsers);
const storedChats = useSelector((state) => state.chats.chatsData);

//Retrieves chat messages for specific chat through an array with the Id and message
inside
const chatMessages = useSelector((state) => {
//Handles brand new chat if there is nothing in it
if (!chatId) return [];

//Gets all chat messages data for a particular chat


const chatMessagesData = state.messages.messagesData[chatId];

if (!chatMessagesData) return [];

const messageList = [];


for (const key in chatMessagesData) {
//Use the key to retrieve chat message data
const message = chatMessagesData[key];

messageList.push({
key,
...message,
});
}
return messageList;
});

//Handles if there is chatId, set data to be storedChats, else set newChatData


const chatData =
(chatId && storedChats[chatId]) || props.route?.params?.newChatData;

//Returns the other user's first and last name


const getChatTitleFromName = () => {
//Selects any element that does not have the same id as my logged in user
const otherUserId = chatUsers.find((uid) => uid !== userData.userId);
const otherUserData = storedUsers[otherUserId];

return (
otherUserData && `${otherUserData.firstName} ${otherUserData.lastName}`
);
};

//Actually passing chat user's name into the screen


useEffect(() => {
props.navigation.setOptions({
headerTitle: getChatTitleFromName(),
});
setChatUsers(chatData.users);
}, [chatUsers]);

{
/* Function used to manage state changes and handle message sending*/
}
const sendMessage = useCallback(async () => {
try {
let id = chatId;
if (!id) {
//If there is no chatId, create the chat
id = await createChat(userData.userId, props.route.params.newChatData);
setChatId(id);
}
//Sends text message
await sendTextMessage(
chatId,
userData.userId,
messageText,
replyingTo && replyingTo.key
);
//Only clears the text box if the message sends
setMessageText("");
setReplyingTo(null);
} catch (error) {
console.log(error);
setErrorBannerText("Message has failed to send");
setTimeout(() => setErrorBannerText(""), 5000);
}
}, [messageText, chatId]);

//Image picker function


const pickImage = useCallback(async () => {
try {
const tempUri = await launchImagePicker();
if (!tempUri) return;

setTempImageUri(tempUri);
} catch (error) {
console.log(error);
}
}, [tempImageUri]);

//Takes the photo


const takePhoto = useCallback(async () => {
try {
const tempUri = await openCamera();
if (!tempUri) return;

setTempImageUri(tempUri);
} catch (error) {
console.log(error);
}
}, [tempImageUri]);

//Image uploader function


const uploadImage = useCallback(async () => {
setIsLoading(true);

try {
let id = chatId;
if (!id) {
//If there is no chatId, create the chat
id = await createChat(userData.userId, props.route.params.newChatData);
setChatId(id);
}

const uploadUrl = await uploadImageAsync(tempImageUri, true);


setIsLoading(false);

//Send image
await sendImage(
id,
userData.userId,
uploadUrl,
replyingTo && replyingTo.key
);

setReplyingTo(null);

//Timesout in 0.5 seconds to close the popup


setTimeout(() => setTempImageUri(""), 500);
} catch (error) {
console.log(error);
//setIsLoading(false);
}
}, [isLoading, tempImageUri, chatId]);

return (
<SafeAreaView edges={["right", "left", "bottom"]} style={styles.container}>
<KeyboardAvoidingView
style={styles.screen}
behavior={Platform.OS === "ios" ? "padding" : undefined}
keyboardVerticalOffset={75}
>
<ImageBackground
source={backgroundImage}
style={styles.backgroundImage}
>
{/* all chats appear here- UI*/}
<PageContainer style={{ backgroundColor: "transparent" }}>
{!chatId && (
<Bubble text="This is a new chat. Say hello!" type="system" />
)}

{/* Displays error banner text */}


{errorBannerText !== "" && (
<Bubble text={errorBannerText} type="error" />
)}

{/* Styling and displaying chat in appropriate bubbles */}


{chatId && (
<FlatList
ref={(ref) => (flatList.current = ref)}
//Sets flatlist to current point
onContentSizeChange={() =>
flatList.current.scrollToEnd({ animated: false })
}
onLayout={() =>
flatList.current.scrollToEnd({ animated: false })
}
data={chatMessages}
renderItem={(itemData) => {
const message = itemData.item;
//Checks that message is own
const isOwnMessage = message.sentBy === userData.userId;
const messageType = isOwnMessage
? "myMessage"
: "theirMessage";
return (
<Bubble
type={messageType}
text={message.text}
messageId={message.key}
userId={userData.userId}
chatId={chatId}
date={message.sentAt}
setReply={() => setReplyingTo(message)}
//Search the messages in state, and find one that equals the id
we are replying to
replyingTo={
message.replyTo &&
chatMessages.find((i) => i.key === message.replyTo)
}
imageUrl={message.imageUrl}
/>
);
}}
/>
)}
</PageContainer>
{/*Handles UI functions for replying of messages*/}
{replyingTo && (
<ReplyTo
text={replyingTo.text}
user={storedUsers[replyingTo.sentBy]}
onCancel={() => setReplyingTo(null)}
/>
)}
</ImageBackground>

<View style={styles.inputContainer}>
{/* Allows us to add an image from our photos*/}
<TouchableOpacity style={styles.mediaButton} onPress={pickImage}>
<Feather name="plus" size={24} color={colors.blue} />
</TouchableOpacity>

<TextInput
style={styles.textbox}
value={messageText}
onChangeText={(text) => setMessageText(text)}
onSubmitEditing={sendMessage}
/>

{/* Allows us to take an image from our camera*/}


{messageText === "" && (
<TouchableOpacity style={styles.mediaButton} onPress={takePhoto}>
<Feather name="camera" size={24} color={colors.blue} />
</TouchableOpacity>
)}

{/* If not an empty string display button */}


{messageText !== "" && (
<TouchableOpacity
style={{ ...styles.mediaButton, ...styles.sendButton }}
onPress={sendMessage}
>
<Feather name="send" size={20} color={"white"} />
</TouchableOpacity>
)}

<AwesomeAlert //Formats the image sending popup


//If not empty means a path to an image is set
show={tempImageUri !== ""}
title="Send image?"
closeOnTouchOutside={true}
closeOnHardwareBackPress={true}
showCancelButton={true}
showConfirmButton={true}
cancelText="Cancel"
confirmText="Send Image"
confirmButtonColor={colors.primary}
cancelButtonColor={colors.red}
titleStyle={styles.popupTitleStyle}
onCancelPressed={() => setTempImageUri("")}
onConfirmPressed={uploadImage}
onDismiss={() => setTempImageUri("")}
customView={
<View>
{isLoading && (
<ActivityIndicator size="small" color={colors.primary} />
)}

{!isLoading && tempImageUri !== "" && (


<Image
source={{ uri: tempImageUri }}
style={{ width: 200, height: 200 }}
/>
)}
</View>
}
/>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
);
};

const styles = StyleSheet.create({


container: {
flex: 1,
flexDirection: "column",
},
screen: {
flex: 1,
},
backgroundImage: {
flex: 1,
},
inputContainer: {
flexDirection: "row",
paddingVertical: 8,
paddingHorizontal: 10,
height: 50,
},
textbox: {
flex: 1,
borderWidth: 1,
borderRadius: 50,
borderColor: colors.lightGrey,
marginHorizontal: 15,
paddingHorizontal: 12,
},
mediaButton: {
alignItems: 'center',
justifyContent: 'center',
width: 35,
},
sendButton: {
backgroundColor: colors.blue,
borderRadius: 50,
padding: 8,
width: 35
},
popupTitleStyle: {
fontFamily: 'semibold',
letterSpacing: 0.3,
color: colors.textColor
}
});
export default ChatScreen;

You might also like