UNPKG

softchatjs-react-native

Version:

React native UI SDK for softchatjs-core. Create a free account at: https://www.softchatjs.com

1 lines 122 kB
{"version":3,"sources":["../../../src/components/Conversations/index.tsx","../../../src/components/Conversations/Conversation.tsx","../../../src/utils/index.ts","../../../src/components/Conversations/ConversationAvatar.tsx","../../../src/constants/Colors.ts","../../../src/contexts/ChatProvider.tsx","../../../src/contexts/ModalProvider.tsx","../../../src/theme/colors.ts","../../../src/theme/index.ts","../../../src/contexts/MessageStateContext.tsx","../../../src/constants/defaultUser.ts","../../../src/components/Badge.tsx","../../../src/assets/icons.tsx","../../../src/components/Search.tsx","../../../src/components/Modals/UserList.tsx","../../../src/components/Chat/MessageAvatar.tsx"],"sourcesContent":["import React, { useMemo } from \"react\";\r\nimport {\r\n ActivityIndicator,\r\n Button,\r\n FlatList,\r\n Modal,\r\n Text,\r\n TextInput,\r\n TouchableOpacity,\r\n View,\r\n} from \"react-native\";\r\nimport {\r\n forwardRef,\r\n useCallback,\r\n useEffect,\r\n useImperativeHandle,\r\n useRef,\r\n useState,\r\n} from \"react\";\r\nimport {\r\n ConversationListRenderProps,\r\n ConversationHeaderRenderProps,\r\n Children,\r\n} from \"../../types\";\r\nimport { ConversationItem } from \"./Conversation\";\r\nimport { GestureHandlerRootView } from \"react-native-gesture-handler\";\r\nimport {\r\n getConversationTitle,\r\n getUserInfoWithId,\r\n restructureMessages,\r\n} from \"../../utils\";\r\nimport { useConfig } from \"../../contexts/ChatProvider\";\r\nimport {\r\n ChatEventGenerics,\r\n ConnectionEvent,\r\n ConversationListMeta,\r\n Events,\r\n Conversation, \r\n Message,\r\n UserMeta,\r\n ConversationListItem\r\n} from \"softchatjs-core\";\r\nimport { ChatIcon, ChatIconPlus, XIcon } from \"../../assets/icons\";\r\nimport Search from \"../Search\";\r\nimport { StatusBar } from \"expo-status-bar\";\r\nimport { useMessageState } from \"../../contexts/MessageStateContext\";\r\nimport VoiceMessage from \"../Chat/ChatItem/Media/VoiceMessage\";\r\nimport { useModalProvider } from \"../../contexts/ModalProvider\";\r\nimport UserList from \"../../components/Modals/UserList\";\r\nimport { } from \"../../../../softchatjs-core/dist/types\";\r\n\r\ntype ConversationProps = {\r\n /**\r\n * Function to open a Conversation\r\n * @example: onOpen: ({ activeConversation }) => navigation.navigate('Chat')\r\n */\r\n onOpen: (props: {\r\n activeConversation: ConversationListItem\r\n }) => void;\r\n /**\r\n * \r\n * @description Render a your own conversation list items \r\n * @returns JSX Element\r\n */\r\n renderItem?: (props: {\r\n conversationDetails: ConversationListRenderProps;\r\n }) => void;\r\n /**\r\n * The user initiating the chat, 'uid' and 'username' are required\r\n * @example { uid: \"1234\", username: \"abc-123\" }\r\n */\r\n user: UserMeta;\r\n /**\r\n * @description Render a custom conversation header, props: { isConnected: boolean, isConnecting: boolean }\r\n * @returns JSX Element\r\n */\r\n renderHeader?: (props: ConversationHeaderRenderProps) => void;\r\n /**\r\n * \r\n * @description Render a custom placeholder component props: { loading: boolean }\r\n * @returns JSX Element\r\n */\r\n renderPlaceHolder?: ({ loading }: { loading: boolean }) => Children;\r\n /**\r\n * @description The list of users a conversation can be initiated with, accepts an array of [{ uid: \"1234\", username: \"abc-123\" }]\r\n * - If the users array is empty, the new chat button will be hidden\r\n */\r\n users?: UserMeta[];\r\n /**\r\n * @description You can pass a stored conversation map to the store this show your previous conversation before the client is initialized\r\n */\r\n store?: ConversationListMeta\r\n};\r\n\r\nexport type ConversationsRefs = {\r\n retryConnection: () => void;\r\n};\r\n\r\nconst retrieveFromCache = (store: ConversationListMeta) => {\r\n try {\r\n const values = Object.values(store).flat() as {\r\n conversation: Conversation;\r\n lastMessage: Message;\r\n unread: string[];\r\n }[];\r\n values.sort(\r\n (a, b) =>\r\n new Date(b.lastMessage?.createdAt).getTime() -\r\n new Date(a.lastMessage?.createdAt).getTime()\r\n );\r\n return values\r\n } catch (error) {\r\n return []\r\n }\r\n}\r\n\r\nconst Conversations = forwardRef((props: ConversationProps, ref) => {\r\n const {\r\n onOpen,\r\n renderItem,\r\n renderHeader,\r\n user,\r\n renderPlaceHolder,\r\n users = [],\r\n store = {},\r\n } = props;\r\n \r\n const { client, theme, fontFamily } = useConfig();\r\n const { activeVoiceMessage, unload, setUserMeta } = useMessageState();\r\n const [searchVal, setSearchVal] = useState(\"\");\r\n const { displayModal } = useModalProvider();\r\n\r\n const flatListRef = useRef<\r\n FlatList<{\r\n conversation: Conversation;\r\n lastMessage: Message;\r\n unread: string[];\r\n }>\r\n >(null);\r\n \r\n const [conversationList, setConversationList] = useState<{ conversation: Conversation; lastMessage: Message; unread: string[] }[]>([ ...retrieveFromCache(store)]);\r\n\r\n const [connectionStatus, setConnectionStatus] = useState<ConnectionEvent>({\r\n isConnected: false,\r\n fetchingConversations: false,\r\n connecting: false,\r\n });\r\n\r\n const reconnect = () => {\r\n if (client) {\r\n client.initializeUser(user, {\r\n connectionConfig: { reset: true },\r\n });\r\n }\r\n };\r\n\r\n useImperativeHandle(ref, () => ({\r\n retryConnection: () => {},\r\n }));\r\n\r\n const handleConnectionChanged = (\r\n event: ChatEventGenerics<ConnectionEvent>\r\n ) => {\r\n setConnectionStatus(event);\r\n };\r\n\r\n var sortConversations = (data: ConversationListMeta) => {\r\n const values = Object.values(data).flat();\r\n try {\r\n values.sort(\r\n (a, b) =>\r\n new Date(b.lastMessage?.createdAt).getTime() -\r\n new Date(a.lastMessage?.createdAt).getTime()\r\n );\r\n return values\r\n } catch (error) {\r\n return values\r\n }\r\n }\r\n\r\n const handleConversationListChanged = (\r\n event: ChatEventGenerics<{ conversationListMeta: ConversationListMeta }>\r\n ) => {\r\n try {\r\n setConversationList(sortConversations(event.conversationListMeta));\r\n } catch (error) {\r\n }\r\n };\r\n\r\n useEffect(() => {\r\n if (client) {\r\n setUserMeta(user);\r\n // client.initializeUser(user);\r\n }\r\n }, [user, client]);\r\n\r\n const getMemoizedConversations = () => {\r\n const res = client.getConversations();\r\n setConversationList(sortConversations(res))\r\n }\r\n\r\n const getConnectionStatus = () => {\r\n const connStatus = client.getConnectionStatus();\r\n setConnectionStatus(connStatus);\r\n }\r\n\r\n useEffect(() => {\r\n if(client){\r\n getMemoizedConversations();\r\n getConnectionStatus();\r\n client.subscribe(Events.CONNECTION_CHANGED, handleConnectionChanged);\r\n client.subscribe(\r\n Events.CONVERSATION_LIST_META_CHANGED,\r\n handleConversationListChanged\r\n );\r\n return () => {\r\n client.unsubscribe(Events.CONNECTION_CHANGED, handleConnectionChanged);\r\n client.unsubscribe(\r\n Events.CONVERSATION_LIST_META_CHANGED,\r\n handleConversationListChanged\r\n );\r\n };\r\n }\r\n \r\n }, [client]);\r\n\r\n const renderConversations = useCallback(\r\n ({\r\n item,\r\n index,\r\n }: {\r\n item: {\r\n conversation: Conversation;\r\n lastMessage: Message;\r\n unread: string[];\r\n };\r\n index: number;\r\n }) => {\r\n // sort message from oldest to newes\r\n // var messages = item?.conversation.messages.sort(\r\n // (a, b) => new Date(b.createdAt) - new Date(a.createdAt)\r\n // );\r\n\r\n if (renderItem) {\r\n let conversationTitle = getConversationTitle(\r\n user.uid,\r\n item.conversation\r\n );\r\n const userInfo = getUserInfoWithId(\r\n user.uid,\r\n item.conversation.participantList\r\n );\r\n\r\n const imageUrl = item.conversation.conversationType === \"private-chat\"\r\n ? userInfo?.receivingUser?.profileUrl\r\n : item.conversation.groupMeta?.groupIcon;\r\n \r\n\r\n return (\r\n <TouchableOpacity\r\n onPress={() =>\r\n onOpen({ activeConversation: item })\r\n }\r\n >\r\n <>\r\n {renderItem({\r\n conversationDetails: {\r\n title: conversationTitle,\r\n recipient: userInfo.presentUser,\r\n lastMessage: item.lastMessage,\r\n imageUrl\r\n },\r\n })}\r\n </>\r\n </TouchableOpacity>\r\n );\r\n }\r\n\r\n return (\r\n <ConversationItem\r\n onPress={() =>\r\n onOpen({ activeConversation: item })\r\n }\r\n chatUserId={user.uid}\r\n key={index}\r\n conversation={{ ...item.conversation }}\r\n lastMessage={item.lastMessage}\r\n unread={item.unread}\r\n isLastItem={conversationList.length === index + 1}\r\n />\r\n );\r\n },\r\n [renderItem, conversationList]\r\n );\r\n\r\n // const startChat = () => {\r\n // if(client){\r\n // if(userId && username && message){\r\n // const msClient = client.newConversation({ uid: userId, username });\r\n // msClient.create(message)\r\n // showModal(false)\r\n // }\r\n // }\r\n // }\r\n // console.log(JSON.stringify(conversationList[0]))\r\n\r\n const filteredConversations = useMemo(() => {\r\n try {\r\n const userId = user.uid\r\n const data = conversationList.filter((c) => {\r\n // Check if any participant in participantList meets the conditions\r\n const participantMatch = c.conversation.participantList.some((participant) => {\r\n const username = participant.participantDetails.username.toLowerCase();\r\n const firstname = participant.participantDetails?.firstname?.toLowerCase() || \"\";\r\n const lastname = participant.participantDetails?.lastname?.toLowerCase() || \"\";\r\n const uid = participant.participantDetails?.uid;\r\n \r\n return (\r\n uid !== userId && // Exclude participants with this specific userId\r\n (\r\n username.includes(searchVal.toLowerCase()) ||\r\n firstname.includes(searchVal.toLowerCase()) ||\r\n lastname.includes(searchVal.toLowerCase())\r\n )\r\n );\r\n });\r\n return participantMatch;\r\n });\r\n \r\n return data;\r\n } catch (error) {\r\n return conversationList\r\n }\r\n }, [conversationList, searchVal]);\r\n\r\n\r\n return (\r\n <>\r\n <GestureHandlerRootView style={{ flex: 1 }}>\r\n <StatusBar style=\"auto\" />\r\n <View\r\n style={{\r\n flex: 1,\r\n height: \"100%\",\r\n width: \"100%\",\r\n backgroundColor: theme?.background.primary,\r\n paddingHorizontal: 20,\r\n }}\r\n >\r\n {/* <Text style={{ color:'white' }}>{JSON.stringify(conversationList[0].conversation.participantList)}</Text> */}\r\n {renderHeader ? (\r\n <>\r\n {renderHeader({\r\n isConnected: connectionStatus.isConnected,\r\n isConnecting: connectionStatus.connecting,\r\n })}\r\n </>\r\n ) : (\r\n <>\r\n {/* {activeVoiceMessage && (\r\n <View\r\n style={{\r\n borderWidth: 1,\r\n padding: 5,\r\n borderColor: theme?.divider,\r\n borderRadius: 10,\r\n marginTop: 10,\r\n flexDirection: \"row\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n }}\r\n >\r\n <VoiceMessage media={activeVoiceMessage} textColor=\"white\" />\r\n <TouchableOpacity\r\n onPress={unload}\r\n style={{\r\n borderWidth: 1,\r\n marginStart: 5,\r\n borderColor: theme?.icon,\r\n borderRadius: 100,\r\n padding: 2,\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n }}\r\n >\r\n <XIcon color={theme?.icon} size={15} />\r\n </TouchableOpacity>\r\n </View>\r\n )} */}\r\n <View\r\n style={{\r\n width: \"100%\",\r\n height: 40,\r\n flexDirection: \"row\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n }}\r\n >\r\n <View>\r\n {connectionStatus.connecting ? (\r\n <View\r\n style={{ flexDirection: \"row\", alignItems: \"center\" }}\r\n >\r\n <ActivityIndicator />\r\n <Text\r\n style={{\r\n fontFamily,\r\n marginStart: 5,\r\n color: theme?.text.secondary,\r\n }}\r\n >\r\n Connecting...\r\n </Text>\r\n </View>\r\n ) : (\r\n <>\r\n {connectionStatus.isConnected ? (\r\n <View\r\n style={{ flexDirection: \"row\", alignItems: \"center\" }}\r\n >\r\n <Text\r\n style={{\r\n fontFamily,\r\n marginStart: 5,\r\n color: theme?.text.secondary,\r\n }}\r\n >\r\n Active\r\n </Text>\r\n <View\r\n style={{\r\n height: 5,\r\n width: 5,\r\n backgroundColor: \"green\",\r\n marginStart: 5,\r\n }}\r\n />\r\n </View>\r\n ) : (\r\n <View\r\n style={{ flexDirection: \"row\", alignItems: \"center\" }}\r\n >\r\n <Text\r\n style={{\r\n marginStart: 5,\r\n fontFamily,\r\n color: theme?.text.secondary,\r\n }}\r\n >\r\n Offline\r\n </Text>\r\n <View\r\n style={{\r\n height: 5,\r\n width: 5,\r\n backgroundColor: \"lightgrey\",\r\n marginStart: 5,\r\n }}\r\n />\r\n </View>\r\n )}\r\n </>\r\n )}\r\n </View>\r\n\r\n {\r\n !connectionStatus.isConnected && (\r\n <TouchableOpacity\r\n style={{\r\n padding: 5,\r\n paddingHorizontal: 10,\r\n display: connectionStatus.connecting ? \"none\" : \"flex\",\r\n backgroundColor: theme?.action.primary,\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n borderRadius: 5,\r\n }}\r\n onPress={() => reconnect()}\r\n >\r\n <Text style={{ color: \"white\", fontFamily }}>\r\n Connect\r\n </Text>\r\n </TouchableOpacity>\r\n )\r\n // : (\r\n // <TouchableOpacity\r\n // style={{\r\n // padding: 5,\r\n // backgroundColor: \"red\",\r\n // alignItems: \"center\",\r\n // justifyContent: \"center\",\r\n // display: connectionStatus.connecting? 'none' : 'flex',\r\n // }}\r\n // onPress={() => {}}\r\n // >\r\n // <Text style={{ color: \"white\" }}>Disconnect</Text>\r\n // </TouchableOpacity>\r\n // )\r\n }\r\n </View>\r\n </>\r\n )}\r\n\r\n <FlatList\r\n ref={flatListRef}\r\n data={filteredConversations}\r\n renderItem={renderConversations}\r\n showsVerticalScrollIndicator={false}\r\n ListHeaderComponent={\r\n <View>\r\n <Text\r\n style={{\r\n fontFamily,\r\n fontSize: 25,\r\n color: theme?.text.secondary,\r\n }}\r\n >\r\n Chats\r\n </Text>\r\n <Search\r\n value={searchVal}\r\n setValue={setSearchVal}\r\n placeholder=\"Search chats\"\r\n containerStyle={{ paddingHorizontal: 0 }}\r\n />\r\n </View>\r\n }\r\n ListEmptyComponent={\r\n renderPlaceHolder ? (\r\n <>\r\n {renderPlaceHolder({\r\n loading: connectionStatus.fetchingConversations,\r\n })}\r\n </>\r\n ) : (\r\n <View style={{ alignItems: \"center\", marginTop: 50 }}>\r\n <ChatIcon size={100} color={theme?.action.primary} />\r\n <Text\r\n style={{\r\n marginStart: 5,\r\n color: theme?.text.disabled,\r\n marginTop: 20,\r\n fontFamily,\r\n }}\r\n >\r\n Your conversations will appear here\r\n </Text>\r\n </View>\r\n )\r\n }\r\n />\r\n </View>\r\n <TouchableOpacity\r\n style={{\r\n display: users.length > 0 ? \"flex\" : \"none\",\r\n position: \"absolute\",\r\n bottom: 70,\r\n right: 20,\r\n }}\r\n onPress={() =>\r\n displayModal({\r\n children: (\r\n <UserList data={users as UserMeta[]} goToChat={() => {}} />\r\n ),\r\n })\r\n }\r\n >\r\n <ChatIconPlus size={70} color={theme?.action.primary} />\r\n </TouchableOpacity>\r\n </GestureHandlerRootView>\r\n </>\r\n );\r\n});\r\n\r\nexport default Conversations;\r\n","import { TouchableOpacity, View, StyleSheet, Text, Image } from \"react-native\";\r\nimport { Conversation, Message } from \"softchatjs-core\";\r\nimport React, { useCallback, useEffect, useMemo, useRef } from \"react\";\r\nimport {\r\n formatConversationTime,\r\n getConversationTitle,\r\n getParticipant,\r\n getUnreadMessageIds,\r\n truncate,\r\n} from \"../../utils\";\r\nimport { Colors } from \"../../constants/Colors\";\r\nimport { ConversationAvatar } from \"./ConversationAvatar\";\r\nimport { UnreadMessagesBadge } from \"../Badge\";\r\nimport { stone } from \"../../theme/colors\";\r\nimport { useConfig } from \"../../contexts/ChatProvider\";\r\nimport Draggeble from \"../Draggable\";\r\n\r\nexport type ConversationItemProps = {\r\n conversation: Conversation;\r\n chatUserId: string;\r\n isLastItem: boolean;\r\n onPress: () => void;\r\n lastMessage: Message;\r\n unread: string[];\r\n};\r\n\r\nexport const ConversationItem = (props: ConversationItemProps) => {\r\n const { theme, fontFamily, fontScale } = useConfig();\r\n const { conversation, chatUserId, isLastItem, onPress, lastMessage, unread } =\r\n props;\r\n\r\n let conversationTitle = useMemo(() => {\r\n return getConversationTitle(chatUserId, conversation);\r\n }, [chatUserId, conversation]);\r\n\r\n let getUnreadMessages = useMemo(() => {\r\n return getUnreadMessageIds(conversation, chatUserId);\r\n }, [conversation]);\r\n\r\n const renderLastMessage = useCallback(() => {\r\n if(!lastMessage){\r\n return null\r\n }\r\n if (\r\n lastMessage.reactions.length > 0 &&\r\n chatUserId !== lastMessage?.reactions[lastMessage.reactions.length - 1].uid\r\n ) {\r\n\r\n return (\r\n <Text style={{ fontSize: 15.5 * fontScale, color: theme?.text.secondary, fontFamily }}>\r\n <Text style={{ fontStyle: \"italic\" }}>\r\n @\r\n {\r\n getParticipant(\r\n lastMessage.reactions[lastMessage.reactions.length - 1]?.uid,\r\n conversation.participantList\r\n )?.participantDetails.username\r\n }\r\n </Text>{\" \"}\r\n reacted \"{lastMessage.reactions[0]?.emoji}\" to your message.\r\n </Text>\r\n );\r\n }\r\n if (lastMessage.message) {\r\n return (\r\n <Text style={{ fontSize: 15.5 * fontScale, color: theme?.text.secondary, fontFamily }}>\r\n {lastMessage.from === chatUserId ? \"You: \" : \"\"}\r\n {truncate(lastMessage.message, 35)}\r\n </Text>\r\n );\r\n } else {\r\n return (\r\n <View style={{ flexDirection: \"row\", alignItems: \"center\" }}>\r\n <Text style={{ fontSize: 15.5 * fontScale, color: theme?.text.secondary, fontFamily }}>\r\n {lastMessage.from === chatUserId ? \"You: \" : \"\"}\r\n </Text>\r\n <View style={{ padding: 3, borderWidth: 1, borderColor: theme?.divider, borderRadius: 3 }}>\r\n <Text\r\n style={{\r\n fontFamily,\r\n fontSize: 10 * fontScale,\r\n color: theme?.text.secondary\r\n }}\r\n >\r\n {lastMessage.attachmentType || \"media\"}\r\n </Text>\r\n </View>\r\n </View>\r\n );\r\n }\r\n }, [lastMessage]);\r\n\r\n return (\r\n // <Draggeble actionContainer={\r\n // <View style={{ flex: 1, backgroundColor: 'red', width: '100%', height: '100%' }}>\r\n // <Text>hello</Text>\r\n // </View>\r\n // }>\r\n <TouchableOpacity\r\n style={styles.listItemContainer}\r\n onPress={() => onPress()}\r\n >\r\n <ConversationAvatar\r\n chatUserId={chatUserId}\r\n participantList={conversation.participantList}\r\n type={conversation.conversationType}\r\n groupMeta={conversation.groupMeta}\r\n conversationTitle={conversationTitle}\r\n />\r\n <View\r\n style={[\r\n {\r\n ...styles.listItem,\r\n },\r\n !isLastItem && {\r\n borderBottomWidth: 0.5,\r\n borderBottomColor: theme?.divider,\r\n },\r\n ]}\r\n >\r\n <View\r\n style={{ flexDirection: \"row\", flex: 1, alignItems: \"center\" }}\r\n >\r\n <View style={{ flex: 1 }}>\r\n <Text\r\n style={{\r\n textTransform: \"capitalize\",\r\n fontSize: 20 * fontScale,\r\n color: theme?.text.secondary,\r\n fontFamily\r\n }}\r\n >\r\n {conversationTitle}\r\n </Text>\r\n <>{renderLastMessage()}</>\r\n </View>\r\n </View>\r\n\r\n <View style={{ alignItems: \"flex-end\" }}>\r\n {unread.length > 0 && <UnreadMessagesBadge label={unread.length} />}\r\n {lastMessage && (\r\n <Text\r\n style={{\r\n fontSize: 15.5 * fontScale,\r\n color: theme?.text.secondary,\r\n fontFamily\r\n }}\r\n >\r\n {formatConversationTime(lastMessage?.createdAt)}\r\n </Text>\r\n )}\r\n </View>\r\n </View>\r\n </TouchableOpacity>\r\n // </Draggeble>\r\n\r\n );\r\n};\r\n\r\nconst styles = StyleSheet.create({\r\n main: {\r\n height: \"100%\",\r\n width: \"100%\",\r\n },\r\n conversationTitle: {\r\n fontSize: 20,\r\n textTransform: \"capitalize\",\r\n },\r\n messageText: {\r\n fontSize: 15.5,\r\n },\r\n messageTime: {},\r\n avatarInitials: {\r\n fontSize: 30,\r\n textTransform: \"capitalize\",\r\n color: \"white\",\r\n },\r\n listItemContainer: {\r\n flexDirection: \"row\",\r\n height: 80,\r\n flex: 1,\r\n alignItems: \"center\",\r\n },\r\n listItem: {\r\n width: \"100%\",\r\n height: \"100%\",\r\n flexDirection: \"row\",\r\n alignItems: \"center\",\r\n flex: 1,\r\n justifyContent: \"space-between\",\r\n },\r\n typing: {\r\n fontStyle: \"italic\",\r\n color: \"green\",\r\n },\r\n});\r\n","import moment from 'moment';\r\n\r\nimport { StringOrNumber, Participant } from \"../types\";\r\nimport { Conversation, Message, MessageStates, UserMeta, ParticipantListInfo } from \"softchatjs-core\";\r\n\r\nimport { GestureResponderEvent } from 'react-native';\r\n\r\nexport function generateConversationId(str1: StringOrNumber, str2: StringOrNumber) {\r\n const sortedStrings = [str1, str2].sort();\r\n const combinedString = sortedStrings.join('_');\r\n const hash = hashCode(combinedString);\r\n return hash.toString()\r\n}\r\n\r\nfunction hashCode(str: string) {\r\n let hash = 0;\r\n if (str.length == 0) {\r\n return hash;\r\n }\r\n for (let i = 0; i < str.length; i++) {\r\n let char = str.charCodeAt(i);\r\n hash = ((hash << 5) - hash) + char;\r\n hash = hash & hash; // Convert to 32bit integer\r\n }\r\n return hash;\r\n}\r\n\r\nexport const generateId = () => {\r\n let uuid = '';\r\n const characters = 'abcdef0123456789';\r\n for (let i = 0; i < 32; i++) {\r\n const randomNumber = Math.floor(Math.random() * characters.length);\r\n const character = characters.charAt(randomNumber);\r\n if (i === 8 || i === 12 || i === 16 || i === 20) {\r\n uuid += '-';\r\n }\r\n uuid += character;\r\n }\r\n return uuid;\r\n}\r\n\r\n\r\nexport const getUserInfoWithId = (userId: string, participantList: ParticipantListInfo[]): {\r\n presentUser: UserMeta | undefined,\r\n receivingUser: UserMeta | undefined,\r\n} => {\r\n let presentUser = participantList.find(participant => participant.participantId === userId);\r\n let otherParticipants = participantList.filter(participant => participant.participantId !== userId)\r\n return { presentUser: presentUser?.participantDetails, receivingUser: otherParticipants[0]?.participantDetails };\r\n};\r\n\r\nexport const truncate = (str: string, len: number) => {\r\n return str.length > len ? str.substring(0, len)+'...' : str;\r\n}\r\n\r\nexport const getConversationTitle = (userId: string, converstaion: Conversation) => {\r\n if(converstaion.conversationType !== 'group-chat'){\r\n const userInfos = getUserInfoWithId(userId, converstaion.participantList);\r\n\r\n const firstname = userInfos.receivingUser?.firstname\r\n const username = userInfos.receivingUser?.username\r\n return firstname? firstname : username\r\n }\r\n return converstaion.groupMeta?.groupName || 'no-groupname'\r\n}\r\n\r\nexport const getUsernameInitials = (username: string) =>{\r\n return username.substring(0, 1)\r\n}\r\n\r\nexport function formatMessageTime(time: Date | string) {\r\n return moment(new Date(time)).format(\"hh:mm a\");\r\n}\r\n\r\nexport function formatConversationTime(time: Date | string) {\r\n const now = moment();\r\n const then = moment(time);\r\n const duration = moment.duration(now.diff(then));\r\n\r\n // Get the largest unit\r\n const years = Math.floor(duration.asYears());\r\n if (years > 0) return years + 'yr';\r\n\r\n const months = Math.floor(duration.asMonths());\r\n if (months > 0) return months + 'mo';\r\n\r\n const weeks = Math.floor(duration.asWeeks());\r\n if (weeks > 0) return weeks + 'w';\r\n\r\n const days = Math.floor(duration.asDays());\r\n if (days > 0) return days + 'd';\r\n\r\n const hours = Math.floor(duration.asHours());\r\n if (hours > 0) return hours + 'h';\r\n\r\n const minutes = Math.floor(duration.asMinutes());\r\n if (minutes > 0) return minutes + 'm';\r\n\r\n // If duration is less than 1 minute\r\n return 'Just now';\r\n}\r\n\r\nexport const generateFillerTimestamps = () => {\r\n return {\r\n createdAt: new Date(),\r\n updatedAt: new Date(),\r\n }\r\n}\r\n\r\nexport const getUnreadMessageIds = (conversation: Conversation, userId: string) => {\r\n var ids: string[] = []\r\n conversation.messages.map(m => {\r\n if (m.messageState === MessageStates.SENT && m.from !== userId) {\r\n ids.push(m.messageId)\r\n }\r\n })\r\n return ids\r\n}\r\n\r\nexport const getQuotedMessage = (messageId: string, messages: Message[]) => {\r\n const message = messages.find(msg => msg.messageId === messageId)\r\n return message\r\n}\r\n\r\nexport const stopPropagation = (event: GestureResponderEvent) => {\r\n event.stopPropagation();\r\n};\r\n\r\nexport const getRandomColor = (): string => {\r\n const letters = '0123456789ABCDEF';\r\n let color = '#';\r\n for (let i = 0; i < 6; i++) {\r\n color += letters[Math.floor(Math.random() * 16)];\r\n }\r\n return color\r\n};\r\n\r\nexport const getParticipant = (uid: string, participantList: ParticipantListInfo[]) => {\r\n return participantList.find(p => p.participantDetails.uid === uid);\r\n}\r\n\r\nexport function convertToMinutes(seconds: number) {\r\n var _seconds = Number(seconds.toFixed(0))\r\n const minutes = Math.floor(_seconds / 60);\r\n const remainingSeconds = _seconds % 60;\r\n\r\n // Pad the numbers to always have two digits\r\n const paddedMinutes = String(minutes).padStart(2, '0');\r\n const paddedSeconds = String(remainingSeconds).padStart(2, '0');\r\n\r\n return `${paddedMinutes}:${paddedSeconds}`;\r\n}\r\n\r\nexport const restructureMessages = (data: Array<string | Message>) => {\r\n const groupMessagesByDate = data.reduce((acc, item) => {\r\n if(typeof item !== \"string\") {\r\n var date = moment(item.createdAt).format('MMMM DD, YYYY');\r\n if(acc[date]) {\r\n acc[date].unshift(item)\r\n }else{\r\n acc[date] = [ item ]\r\n }\r\n }\r\n return acc\r\n },{} as {[key: string]: Array<Message>});\r\n\r\n const _messages: Array<string | Message> = Object.entries(groupMessagesByDate).flatMap(\r\n ([date, messages]) => [...messages.reverse(), date]\r\n );\r\n return _messages\r\n}\r\n\r\n","import {\r\n TouchableOpacity,\r\n View,\r\n StyleSheet,\r\n Text,\r\n FlatList,\r\n} from \"react-native\";\r\nimport {\r\n Conversation,\r\n ConversationType,\r\n GroupChatMeta,\r\n ParticipantListInfo,\r\n PrivateChatMeta,\r\n} from \"softchatjs-core\";\r\nimport React, { useMemo, useRef } from \"react\";\r\nimport {\r\n getUserInfoWithId,\r\n} from \"../../utils\";\r\nimport { Colors } from \"../../constants/Colors\";\r\nimport { Image } from \"expo-image\";\r\nimport { useConfig } from \"../../contexts/ChatProvider\";\r\n\r\nconst avatarSize = 50;\r\n\r\nexport const ConversationAvatar = ({\r\n type,\r\n chatUserId,\r\n participantList,\r\n groupMeta,\r\n conversationTitle,\r\n}: {\r\n conversationTitle: string | undefined;\r\n chatUserId: string;\r\n type: ConversationType;\r\n participantList: ParticipantListInfo[];\r\n groupMeta: GroupChatMeta | null;\r\n}) => {\r\n\r\n const { fontFamily, fontScale } = useConfig();\r\n \r\n const userInfo = useMemo(() => {\r\n if (type === \"private-chat\") {\r\n return getUserInfoWithId(chatUserId, participantList);\r\n }\r\n return null;\r\n }, [chatUserId, participantList, type]);\r\n\r\n const imageUri = type === \"private-chat\"\r\n ? userInfo?.receivingUser?.profileUrl\r\n : groupMeta?.groupIcon;\r\n \r\n const initials = conversationTitle ? conversationTitle.substring(0, 1) : \"\";\r\n \r\n return (\r\n imageUri ? (\r\n <Image source={{ uri: imageUri }} style={styles.avatar} cachePolicy=\"disk\" />\r\n ) : (\r\n <View style={styles.avatar}>\r\n <Text style={{ ...styles.avatarInitials, fontSize: 30 * fontScale, fontFamily }}>{initials}</Text>\r\n </View>\r\n )\r\n );\r\n};\r\n\r\nconst styles = StyleSheet.create({\r\n main: {\r\n height: \"100%\",\r\n width: \"100%\",\r\n },\r\n conversationTitle: {\r\n color: \"black\",\r\n fontSize: 20,\r\n textTransform: \"capitalize\",\r\n },\r\n messageText: {\r\n color: \"black\",\r\n fontSize: 17,\r\n },\r\n messageTime: {},\r\n avatar: {\r\n height: avatarSize,\r\n width: avatarSize,\r\n borderRadius: avatarSize,\r\n backgroundColor: Colors.greyLighter,\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n marginEnd: 10,\r\n },\r\n avatarInitials: {\r\n fontSize: 30,\r\n textTransform: \"capitalize\",\r\n color: \"white\",\r\n },\r\n listItem: {\r\n height: 80,\r\n width: \"100%\",\r\n paddingHorizontal: 10,\r\n flexDirection: \"row\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n },\r\n});\r\n","export const Colors = {\r\n greyLighter: '#F0F0F0'\r\n}","import React, { createContext, useContext, useEffect } from \"react\";\r\nimport { ChatTheme, Config } from \"../types\";\r\nimport ModalProvider from \"./ModalProvider\";\r\nimport ChatClient from \"softchatjs-core\";\r\nimport defaultTheme from \"../theme\";\r\nimport { MessageStateProvider } from \"./MessageStateContext\";\r\n\r\ntype ChatProvider = {\r\n children: JSX.Element;\r\n theme?: ChatTheme;\r\n fontFamily: string | undefined;\r\n /**\r\n * Multiplier to adjust the font size dynamically.\r\n * A value of 1 keeps the default size, while values like 0.5 or 1.5 scale it down or up.\r\n */\r\n fontScale?: number\r\n};\r\n\r\nconst ConfigContext = createContext<\r\n Omit<ChatProvider, \"children\"> & { client: ChatClient | null }\r\n>({\r\n theme: defaultTheme,\r\n client: null,\r\n fontFamily: undefined,\r\n fontScale: 1\r\n});\r\n\r\nexport function useConfig() {\r\n return useContext(ConfigContext);\r\n}\r\n\r\nexport default function ChatProvider(\r\n props: ChatProvider & { client: ChatClient | null }\r\n) {\r\n const { children, client, theme = defaultTheme, fontFamily, fontScale } = props;\r\n\r\n return (\r\n <ConfigContext.Provider value={{ theme, client, fontFamily, fontScale }}>\r\n <MessageStateProvider>\r\n <ModalProvider>{children}</ModalProvider>\r\n </MessageStateProvider>\r\n </ConfigContext.Provider>\r\n );\r\n}\r\n","import React, { createContext, useContext, useState } from \"react\";\r\nimport {\r\n KeyboardAvoidingView,\r\n Modal,\r\n ScrollView,\r\n StyleSheet,\r\n TouchableWithoutFeedback,\r\n View,\r\n ViewStyle,\r\n Text,\r\n} from \"react-native\";\r\nimport { Children } from \"../types\";\r\nimport { stopPropagation } from \"../utils\";\r\n\r\ntype ModalProps = {\r\n dismissable?: boolean;\r\n justifyContent?: ViewStyle[\"justifyContent\"];\r\n children: Children | null;\r\n animation?: \"none\" | \"fade\" | \"slide\" | undefined;\r\n containerWidth?: ViewStyle[\"width\"];\r\n};\r\n\r\ntype ModalProvider = {\r\n displayModal: (child: ModalProps) => void;\r\n resetModal: (cb?: Function) => void;\r\n};\r\n\r\nconst initial: ModalProvider & { modalProps: ModalProps | null } = {\r\n displayModal: () => {},\r\n resetModal: () => {},\r\n modalProps: {\r\n dismissable: true,\r\n justifyContent: \"center\",\r\n children: null,\r\n animation: \"slide\",\r\n containerWidth: \"100%\",\r\n },\r\n};\r\n\r\nconst ModalProviderContext = createContext<ModalProvider>(initial);\r\n\r\nexport const useModalProvider = () => useContext(ModalProviderContext);\r\n\r\nexport default function ModalProvider(props: { children: Children }) {\r\n const { children } = props;\r\n\r\n const [modal, showModal] = useState(false);\r\n const [modalProps, setModalProps] = useState(initial.modalProps);\r\n\r\n const displayModal = (props: ModalProps) => {\r\n showModal(true);\r\n setModalProps({ ...initial.modalProps, ...props });\r\n };\r\n\r\n const dismiss = () => {\r\n if (modalProps?.dismissable) {\r\n showModal(false);\r\n }\r\n };\r\n\r\n const resetModal = (cb?: Function) => {\r\n showModal(false);\r\n cb?.();\r\n };\r\n\r\n return (\r\n <ModalProviderContext.Provider\r\n value={{\r\n displayModal,\r\n resetModal,\r\n }}\r\n >\r\n {children}\r\n <Modal\r\n animationType={modalProps?.animation}\r\n style={{ height: \"100%\", width: \"100%\" }}\r\n visible={modal}\r\n transparent\r\n >\r\n <View\r\n style={{\r\n flex: 1,\r\n height: \"100%\",\r\n width: \"100%\",\r\n alignItems: \"center\",\r\n justifyContent: modalProps?.justifyContent,\r\n backgroundColor: \"rgba(0,0,0,.3)\",\r\n }}\r\n >\r\n <View style={{ flex: 1, width: \"100%\", height: \"100%\" }}>\r\n {modalProps?.children}\r\n </View>\r\n </View>\r\n </Modal>\r\n </ModalProviderContext.Provider>\r\n );\r\n}\r\n","interface Color {\r\n 50: string;\r\n 100: string;\r\n 200: string;\r\n 300: string;\r\n 400: string;\r\n 500: string;\r\n 600: string;\r\n 700: string;\r\n 800: string;\r\n 900: string;\r\n A100?: string;\r\n}\r\n\r\nexport const teal: Color = {\r\n 50: '#DCF2F0',\r\n 100: '#A9DFD8',\r\n 200: '#73CABE',\r\n 300: '#3AB4A4',\r\n 400: '#00A391',\r\n 500: '#00927E',\r\n 600: '#008572',\r\n 700: '#007662',\r\n 800: '#006654',\r\n 900: '#004A38',\r\n};\r\n\r\nexport const fuchsia: Color = {\r\n 50: '#FAE9F7',\r\n 100: '#F2C8ED',\r\n 200: '#EBA3E3',\r\n 300: '#E27DD7',\r\n 400: '#D95ECD',\r\n 500: '#D043C4',\r\n 600: '#C13EBD',\r\n 700: '#AE36B5',\r\n 800: '#9D31AE',\r\n 900: '#7F28A0',\r\n};\r\n\r\nexport const green: Color = {\r\n 50: '#E6F5E4',\r\n 100: '#C2E6BD',\r\n 200: '#9AD693',\r\n 300: '#6FC666',\r\n 400: '#4ABA42',\r\n 500: '#17AE13',\r\n 600: '#029F04',\r\n 700: '#008D00',\r\n 800: '#007C00',\r\n 900: '#005E00',\r\n};\r\n\r\nexport const bluishCyan: Color = {\r\n 50: '#E2F5FE',\r\n 100: '#B5E6FB',\r\n 200: '#84D6F9',\r\n 300: '#56C6F6',\r\n 400: '#36B9F5',\r\n 500: '#20AEF3',\r\n 600: '#1D9FE4',\r\n 700: '#188BD0',\r\n 800: '#167ABC',\r\n 900: '#105A9A',\r\n};\r\n\r\nexport const yellowOrange: Color = {\r\n 50: '#FEF3E2',\r\n 100: '#FDE0B6',\r\n 200: '#FDCD87',\r\n 300: '#FCB859',\r\n 400: '#FBA939',\r\n 500: '#FB9A23',\r\n 600: '#F78F21',\r\n 700: '#F07F1E',\r\n 800: '#EA701B',\r\n 900: '#E05716',\r\n};\r\n\r\nexport const pinkishRed: Color = {\r\n 50: '#FFEBF0',\r\n 100: '#FFCCD6',\r\n 200: '#FB969F',\r\n 300: '#F66B79',\r\n 400: '#FF3F56',\r\n 500: '#FF183B',\r\n 600: '#FC003C',\r\n 700: '#EA0034',\r\n 800: '#DD002C',\r\n 900: '#CF001E',\r\n};\r\n\r\nexport const grey: Color = {\r\n 50: '#F6F6FF',\r\n 100: '#F2F1FF',\r\n 200: '#EBEBF9',\r\n 300: '#CAC9D7',\r\n 400: '#ACACB9',\r\n 500: '#82818F',\r\n 600: '#6D6D7A',\r\n 700: '#4D4D59',\r\n 800: '#2B2B36',\r\n 900: '#21222D',\r\n A100: '#1D1E26',\r\n};\r\n\r\nexport const stone: Color = {\r\n 50: '#F8F8F8',\r\n 100: '#EFEFEF',\r\n 200: '#E8E8E8',\r\n 300: '#D9D9D9',\r\n 400: '#D2D2D2',\r\n 500: '#A0A0A0',\r\n 600: '#87888C',\r\n 700: '#2C2D33',\r\n 800: '#1D1E26',\r\n 900: '#171821',\r\n};\r\n","import { ChatTheme } from \"../types\"\r\nimport { bluishCyan, fuchsia, green, grey, stone, teal } from \"./colors\"\r\n\r\nconst theme: ChatTheme = {\r\n background: {\r\n primary: stone[900],\r\n secondary: grey[900],\r\n disabled: grey[800]\r\n },\r\n text: {\r\n primary: 'black',\r\n secondary: stone[200],\r\n disabled: stone[500],\r\n },\r\n action: {\r\n primary: teal[50],\r\n secondary: stone[300]\r\n },\r\n chatBubble: {\r\n left: {\r\n bgColor: grey[900],\r\n messageColor: stone[200],\r\n messageTimeColor: 'grey',\r\n replyBorderColor: stone[200]\r\n },\r\n right: {\r\n bgColor: \"#474952\",\r\n messageColor: 'white',\r\n messageTimeColor: 'grey',\r\n replyBorderColor: green[900]\r\n },\r\n },\r\n icon: 'white',\r\n divider: stone[700]\r\n}\r\n\r\nexport default theme","import React, {\r\n createContext,\r\n useState,\r\n useContext,\r\n} from \"react\";\r\nimport {\r\n SetState,\r\n} from \"../types\";\r\nimport { Audio, AVPlaybackStatus } from 'expo-av';\r\nimport { Emoticon, Message, SendMessageGenerics, Media, UserMeta, Conversation, ConversationListItem } from \"softchatjs-core\";\r\nimport defaultUser from \"../constants/defaultUser\";\r\n\r\ntype MessageStateContext = {\r\n globalTextMessage: string,\r\n setGlobalTextMessage: SetState<string>,\r\n stickers: Emoticon[],\r\n setStickers: SetState<Emoticon[]>,\r\n pendingMessages: Array<Partial<Message>>,\r\n addNewPendingMessages: (message: Partial<Message>) => void;\r\n removePendingMessage: (messageId: string) => void;\r\n updatePendingMessage: (messageId: string, message: Message) => void;\r\n playVoiceMessage: (media: Media) => void;\r\n pauseVoiceMessage: () => void;\r\n resumeVoiceMessage: () => void;\r\n audioState: \"playing\" | \"paused\" | \"loading\" | null,\r\n unload: () => void;\r\n sound: Audio.Sound | null,\r\n activeVoiceMessage: Media | null,\r\n avPlayBackStatus: AVPlaybackStatus & { positionMillis: number } | null,\r\n userMeta: UserMeta,\r\n setUserMeta: SetState<UserMeta>,\r\n conversationList: Array<ConversationListItem>,\r\n setConversationList: SetState<Array<ConversationListItem>>\r\n};\r\n\r\nconst initialMessageStateContext: MessageStateContext = {\r\n globalTextMessage: '',\r\n setGlobalTextMessage: () => {},\r\n stickers: [],\r\n setStickers: () => {},\r\n pendingMessages: [],\r\n addNewPendingMessages: (message: Partial<Message>) => {},\r\n removePendingMessage: (messageId: string) => {},\r\n updatePendingMessage: (messageId: string, message: Message) => {},\r\n playVoiceMessage: (media: Media) => {},\r\n pauseVoiceMessage: () => {},\r\n resumeVoiceMessage: () => {},\r\n audioState: null,\r\n unload: () => {},\r\n sound: null,\r\n activeVoiceMessage: null,\r\n avPlayBackStatus: null,\r\n userMeta: defaultUser,\r\n setUserMeta: () => {},\r\n conversationList: [],\r\n setConversationList: () => {}\r\n}\r\n\r\nexport default initialMessageStateContext;\r\n\r\nconst MessageStateContext = createContext<MessageStateContext>(\r\n initialMessageStateContext\r\n);\r\n\r\nexport const useMessageState = () => useContext(MessageStateContext);\r\n\r\nexport const MessageStateProvider = ({ children }: { children: JSX.Element }) => {\r\n \r\n const [conversationList, setConversationList] = useState<Array<ConversationListItem>>([]);\r\n const [ globalTextMessage, setGlobalTextMessage ] = useState('');\r\n const [ stickers, setStickers ] = useState<Emoticon[]>([]);\r\n const [ pendingMessages, setPendingMessages ] = useState<Array<Partial<Message>>>([]);\r\n const [sound, setSound] = useState<Audio.Sound | null>(null);\r\n const [ audioState, setAudioState ] = useState<\"playing\" | \"paused\" | \"loading\" | null>(null);\r\n const [ activeVoiceMessage, setActiveVoiceMessage ] = useState<Media | null>(null);\r\n const [ avPlayBackStatus, setAvPlayBackStatus ] = useState<AVPlaybackStatus & { positionMillis: number } | null>(null);\r\n const [ userMeta, setUserMeta ] = useState<UserMeta>(defaultUser);\r\n\r\n const addNewPendingMessages = (message: Partial<Message>) => {\r\n setPendingMessages((prev) => {\r\n return [ ...prev, message ]\r\n });\r\n }\r\n\r\n const removePendingMessage = (messageId: string) => {\r\n setPendingMessages((prev) => {\r\n const filtered = prev.filter(m => m.messageId !== messageId)\r\n return filtered\r\n });\r\n }\r\n\r\n const updatePendingMessage = (messageId: string, updatedMessage: Message) => {\r\n const clonedMessage = JSON.parse(JSON.stringify(updatedMessage));\r\n \r\n setPendingMessages((prev) =>\r\n prev.map((message) =>\r\n message.messageId === messageId ? { ...message, ...clonedMessage } : message\r\n )\r\n );\r\n };\r\n \r\n const onPlaybackStatusUpdate = (data: AVPlaybackStatus & { didJustFinish: boolean , positionMillis: number}) => {\r\n console.log(data)\r\n setAvPlayBackStatus(data)\r\n if(data?.didJustFinish){\r\n setAudioState(null);\r\n unload();\r\n }\r\n };\r\n \r\n const playVoiceMessage = async (media: Media) => {\r\n if (activeVoiceMessage !== null && media.mediaId !== activeVoiceMessage?.mediaId) {\r\n return unload()\r\n }\r\n \r\n setActiveVoiceMessage(media);\r\n setAudioState(\"loading\");\r\n \r\n try {\r\n console.log('Loading Sound');\r\n console.log(media.mediaUrl)\r\n const { sound: avSound } = await Audio.Sound.createAsync({ uri: media.mediaUrl }, {}, onPlaybackStatusUpdate);\r\n setSound(avSound);\r\n console.log('Playing Sound');\r\n setAudioState(\"playing\");\r\n await avSound.playAsync();\r\n } catch (error) {\r\n console.error(\"Error loading audio: \", error);\r\n setAudioState(null); \r\n }\r\n };\r\n\r\n const pauseVoiceMessage = async () => {\r\n await sound?.pauseAsync();\r\n setAudioState(\"paused\")\r\n }\r\n\r\n const resumeVoiceMessage = async () => {\r\n if(audioState === \"paused\"){\r\n await sound?.playAsync();\r\n setAudioState(\"playing\")\r\n }\r\n }\r\n\r\n const unload = () => {\r\n console.log('Unloading Sound');\r\n sound?.stopAsync();\r\n sound?.unloadAsync();\r\n setSound(null);\r\n setActiveVoiceMessage(null);\r\n setAudioState(null)\r\n }\r\n\r\n return (\r\n <MessageStateContext.Provider\r\n value={{\r\n globalTextMessage,\r\n setGlobalTextMessage,\r\n stickers,\r\n setStickers,\r\n pendingMessages, \r\n addNewPendingMessages,\r\n removePendingMessage,\r\n updatePendingMessage,\r\n playVoiceMessage,\r\n pauseVoiceMessage,\r\n resumeVoiceMessage,\r\n audioState,\r\n unload,\r\n sound,\r\n activeVoiceMessage,\r\n avPlayBackStatus,\r\n userMeta,\r\n setUserMeta,\r\n conversationList,\r\n setConversationList\r\n }}\r\n >\r\n {children}\r\n </MessageStateContext.Provider>\r\n );\r\n};\r\n","export default {\r\n id: \"\",\r\n uid: \"\",\r\n username: \"\",\r\n firstname: \"\",\r\n lastname: \"\",\r\n profileUrl: \"\",\r\n color: \"\",\r\n custom: {}\r\n}\r\n","import React from \"react\"\r\nimport { View, Text } from \"react-native\";\r\n\r\ntype BadgeProps = {\r\n label: string | number\r\n}\r\n\r\nexport const UnreadMessagesBadge = (props: BadgeProps) => {\r\n\r\n const { label } = props;\r\n\r\n return (\r\n <View style={{\r\n height: 25,\r\n width: 25,\r\n borderRadius: 25,\r\n backgroundColor: 'lightblue',\r\n alignItems: 'center',\r\n justifyContent: 'center'\r\n }}>\r\n <Text style={{ color: 'white', fontWeight: 'bold' }}>{label}</Text>\r\n </View>\r\n )\r\n\r\n}","import React from \"react\";\r\nimport { View } from \"react-native\";\r\nimport { Svg, Path, G, Defs, ClipPath, Rect } from \"react-native-svg\";\r\n\r\ntype Icon = {\r\n size?: number;\r\n color?: string;\r\n};\r\n\r\nexport function XIcon(props: Icon) {\r\n const { size = 25, color = \"black\" } = props;\r\n return (\r\n <Svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\">\r\n <Path\r\n d=\"M6 18L17.94 6M18 18L6.06 6\"\r\n stroke={color}\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n />\r\n </Svg>\r\n );\r\n}\r\n\r\nexport function TimesIcon(props: Icon) {\r\n const { size = 25, color = \"black\" } = props;\r\n\r\n return <XIcon size={size} color={color} />;\r\n}\r\n\r\nexport function CloseIcon(props: Icon & { bgColor: string }) {\r\n const { size = 25, color = \"black\", bgColor } = props;\r\n return (\r\n <View\r\n style={{ padding: 3, borderRadius: size * 2, backgroundColor: bgColor }}\r\n >\r\n <TimesIcon size={size} color={color} />\r\n </View>\r\n );\r\n}\r\n\r\nexpor