UNPKG

softchatjs-react-native

Version:

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

1 lines 162 kB
{"version":3,"sources":["../../../../../src/components/Chat/ChatItem/Layouts/Stacked.tsx","../../../../../src/components/Chat/MessageAvatar.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/Chat/ChatItem/Sticker.tsx","../../../../../src/utils/index.ts","../../../../../src/components/Chat/ChatItem/Reactions.tsx","../../../../../src/components/Chat/ChatItem/Quoted.tsx","../../../../../src/assets/icons.tsx","../../../../../src/components/Chat/ChatItem/Preview.tsx","../../../../../src/components/Chat/ChatItem/Media/index.tsx","../../../../../src/components/Modals/ImagePreview.tsx","../../../../../src/components/Chat/ChatInput.tsx","../../../../../src/components/Chat/ChatItem/Media/VoiceMessage.tsx","../../../../../src/components/Chat/ChatItem/Media/Video.tsx","../../../../../src/components/Modals/VideoViewer.tsx"],"sourcesContent":["import { View, Text, ViewStyle, TouchableOpacity, Linking } from \"react-native\";\r\nimport React, { useCallback, useMemo } from \"react\";\r\nimport {\r\n ChatBubbleRenderProps,\r\n} from \"../../../../types\";\r\nimport MessageAvatar from \"../../MessageAvatar\";\r\nimport Sticker from \"../Sticker\";\r\nimport Animated from \"react-native-reanimated\";\r\nimport { formatMessageTime, truncate } from \"../../../../utils\";\r\nimport Reactions from \"../Reactions\";\r\nimport Quoted from \"../Quoted\";\r\nimport { useConfig } from \"../../../../contexts/ChatProvider\";\r\nimport Preview from \"../Preview\";\r\nimport MediaMessage from \"../Media\";\r\nimport { Message, AttachmentTypes, MessageStates } from \"softchatjs-core\"\r\nimport { BroadcastIcon } from \"../../../../assets/icons\";\r\n\r\n\r\ntype StackedProps = {\r\n message: Message;\r\n animatedStyles: ViewStyle;\r\n renderStateIcon: (color: string) => JSX.Element;\r\n chatUserId: string;\r\n recipientId: string;\r\n myMessage: boolean;\r\n renderChatBubble?: (props: ChatBubbleRenderProps) => void;\r\n onScrollToIndex: (messageId: string) => void;\r\n isPending?: boolean;\r\n retryUpload: () => void;\r\n};\r\n\r\nexport default function Stacked(props: StackedProps) {\r\n const { theme, fontFamily } = useConfig();\r\n const {\r\n message,\r\n animatedStyles,\r\n renderStateIcon,\r\n chatUserId,\r\n recipientId,\r\n renderChatBubble,\r\n myMessage,\r\n onScrollToIndex,\r\n isPending,\r\n retryUpload\r\n } = props;\r\n\r\n const RenderAttachment = useCallback(() => {\r\n switch (message.attachmentType) {\r\n case AttachmentTypes.STICKER:\r\n return <Sticker message={message} />;\r\n case AttachmentTypes.MEDIA:\r\n return <MediaMessage message={message} isPending={isPending} recipientId={recipientId} />;\r\n default:\r\n return <></>;\r\n }\r\n }, [message]);\r\n\r\n const hasTextMessage = useMemo(() => {\r\n return message.message.length > 0;\r\n }, [message.message]);\r\n\r\n const renderMessageWithLinks = (message: string) => {\r\n if (!message) return null;\r\n\r\n const urlRegex = /(https?:\\/\\/[^\\s]+)/gi;\r\n\r\n const parts = message.split(urlRegex);\r\n\r\n return parts.map((part, index) => {\r\n if (urlRegex.test(part)) {\r\n return (\r\n <Text\r\n key={index}\r\n style={{\r\n fontFamily,\r\n textDecorationLine: \"underline\",\r\n textTransform: \"lowercase\",\r\n }}\r\n onPress={() => Linking.openURL(part)}\r\n >\r\n {part}\r\n </Text>\r\n );\r\n } else {\r\n return <Text style={{ fontFamily }} key={index}>{part}</Text>;\r\n }\r\n });\r\n };\r\n\r\n return (\r\n <Animated.View\r\n style={[\r\n isPending ? { opacity: 0.7 } : animatedStyles,\r\n {\r\n paddingVertical: 10,\r\n paddingHorizontal: 10,\r\n flex: 1,\r\n borderBottomWidth: 0,\r\n borderColor: theme?.divider,\r\n // backgroundColor: theme?.background.primary\r\n },\r\n ]}\r\n >\r\n {message.quotedMessage && (\r\n <Quoted\r\n onPress={() =>\r\n onScrollToIndex(message.quotedMessage?.messageId as string)\r\n }\r\n quotedMessage={message.quotedMessage}\r\n layout=\"stacked\"\r\n theme={theme}\r\n />\r\n )}\r\n <>\r\n {renderChatBubble ? (\r\n renderChatBubble({ message })\r\n ) : (\r\n <View\r\n style={{\r\n flexDirection: \"row\",\r\n alignItems: \"flex-start\",\r\n width: \"100%\",\r\n flex: 1,\r\n }}\r\n >\r\n <MessageAvatar\r\n size={45}\r\n initials={message.messageOwner?.username.substring(0, 2)}\r\n imgUrl={message.messageOwner?.profileUrl}\r\n style={{\r\n marginEnd: 5,\r\n backgroundColor: message.messageOwner?.color,\r\n }}\r\n />\r\n\r\n <View style={{ flex: 1, paddingHorizontal: 10, width: \"100%\" }}>\r\n <View\r\n style={{\r\n flexDirection: \"row\",\r\n alignItems: \"center\",\r\n marginBottom: 5,\r\n }}\r\n >\r\n {message.isBroadcast && (\r\n <BroadcastIcon color={theme?.chatBubble.right.messageColor} size={13} />\r\n )}\r\n <Text style={[{ color: theme?.text.secondary, fontFamily }]}>\r\n {message.messageOwner?.username}\r\n </Text>\r\n <Text\r\n style={[\r\n {\r\n fontFamily,\r\n color: theme?.text.disabled,\r\n marginStart: 10,\r\n fontSize: 9,\r\n marginEnd: 5,\r\n },\r\n ]}\r\n >\r\n {formatMessageTime(message.createdAt)}\r\n </Text>\r\n {myMessage && <>{renderStateIcon(theme?.icon as string)}</>}\r\n </View>\r\n\r\n <RenderAttachment />\r\n {hasTextMessage && (\r\n <View style={{ flex: 1, marginBottom: 10 }}>\r\n <Preview\r\n message={message.message}\r\n color={theme?.text.secondary as string}\r\n />\r\n <Text\r\n style={{\r\n color: theme?.text.secondary,\r\n fontFamily,\r\n fontSize: 17,\r\n }}\r\n >\r\n {renderMessageWithLinks(message.message || \"\")}{\" \"}\r\n {message.lastEdited && (\r\n <Text\r\n style={{\r\n fontSize: 11,\r\n fontFamily,\r\n fontStyle: \"italic\",\r\n color: theme?.text.disabled,\r\n }}\r\n >\r\n (edited)\r\n </Text>\r\n )}\r\n </Text>\r\n </View>\r\n )}\r\n {isPending === true && message.messageState !== MessageStates.FAILED && (\r\n <Text\r\n style={[\r\n {\r\n color: theme?.text.disabled,\r\n fontFamily,\r\n fontSize: 11,\r\n marginTop: 5,\r\n fontStyle: \"italic\",\r\n marginStart: 15\r\n },\r\n ]}\r\n >\r\n Uploading...\r\n </Text>\r\n )}\r\n {isPending === true && message.messageState === MessageStates.FAILED && (\r\n <Text\r\n onPress={retryUpload}\r\n style={[\r\n {\r\n color: 'tomato',\r\n fontSize: 11,\r\n marginTop: 5,\r\n fontStyle: \"italic\",\r\n marginStart: 15,\r\n fontFamily\r\n },\r\n ]}\r\n >\r\n Upload failed\r\n </Text>\r\n )}\r\n \r\n <Reactions\r\n layout=\"stacked\"\r\n reactions={message.reactions}\r\n position={\"left\"}\r\n conversationId={message.conversationId}\r\n messageId={message.messageId}\r\n chatUserId={chatUserId}\r\n recipientId={recipientId}\r\n />\r\n </View>\r\n </View>\r\n )}\r\n </>\r\n </Animated.View>\r\n );\r\n}\r\n","import React from \"react\"\r\nimport { TouchableOpacity, Text, ViewStyle } from \"react-native\";\r\nimport { Colors } from \"../../constants/Colors\";\r\nimport { getRandomColor } from \"../../utils\";\r\nimport { Image } from \"expo-image\";\r\nimport { useConfig } from \"../../contexts/ChatProvider\";\r\n\r\ntype MessageAvatarProps = {\r\n initials: string;\r\n size: number;\r\n imgUrl?: string;\r\n style?: ViewStyle\r\n};\r\n\r\nexport default function MessageAvatar(props: MessageAvatarProps) {\r\n const { imgUrl, initials, size = 40, style } = props;\r\n const { fontFamily } = useConfig();\r\n\r\n return (\r\n <TouchableOpacity\r\n style={{\r\n height: size,\r\n width: size,\r\n borderRadius: size,\r\n backgroundColor: 'black',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n ...style\r\n }}\r\n >\r\n {imgUrl ? (\r\n <Image\r\n source={{ uri: imgUrl }}\r\n cachePolicy=\"disk\"\r\n style={{\r\n height: size,\r\n width: size,\r\n borderRadius: size,\r\n backgroundColor: Colors.greyLighter,\r\n }}\r\n />\r\n ) : (\r\n <Text\r\n style={{\r\n fontSize: size/2,\r\n textTransform: \"uppercase\",\r\n color: \"white\",\r\n fontFamily\r\n }}\r\n >\r\n {initials}\r\n </Text>\r\n )}\r\n </TouchableOpacity>\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 { Message } from \"softchatjs-core\";\r\nimport React, { useCallback } from \"react\";\r\nimport { Image } from \"expo-image\";\r\n\r\ntype SickerProps = {\r\n message: Message;\r\n};\r\n\r\nexport default function Sticker(props: SickerProps) {\r\n \r\n const { message } = props;\r\n\r\n const renderSicker = useCallback(() => {\r\n return (\r\n <Image\r\n source={{ uri: message.attachedMedia[0].mediaUrl }}\r\n cachePolicy=\"disk\"\r\n style={{ height: 70, width: 70, borderRadius: 8 }}\r\n />\r\n )\r\n },[])\r\n\r\n return (\r\n <>{renderSicker()}</>\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 React from \"react\";\r\nimport { View, TouchableOpacity, Image, Text, Platform, ViewStyle } from \"react-native\";\r\nimport { Reaction, ServerActions } from \"softchatjs-core\";\r\nimport { useConfig } from \"../../../contexts/ChatProvider\";\r\nimport theme from \"../../../theme\";\r\n\r\ntype ReactionProps = {\r\n reactions: Reaction[];\r\n position: \"left\" | \"right\";\r\n conversationId: string, \r\n messageId: string,\r\n chatUserId: string,\r\n recipientId: string,\r\n layout?: 'stacked'\r\n};\r\n\r\nexport default function Reactions(props: ReactionProps) {\r\n const { reactions, position, conversationId, messageId, chatUserId, recipientId, layout } = props;\r\n const { client, theme, fontScale } = useConfig()\r\n\r\n const removeReaction = (selected: Reaction) => {\r\n if(client){\r\n if(selected.uid === chatUserId) {\r\n const index = reactions.indexOf(selected);\r\n if(index !== -1) {\r\n const updateReactions = reactions.filter(r => reactions.indexOf(r) !== index);\r\n client.messageClient(conversationId).reactToMessage(\r\n {\r\n conversationId: conversationId,\r\n messageId: messageId,\r\n reactions: updateReactions,\r\n to: recipientId\r\n }\r\n )\r\n }\r\n }\r\n }\r\n }\r\n\r\n return (\r\n <View style={[{ flexDirection: \"row\" }]}>\r\n {reactions.map((reaction, i) => (\r\n <TouchableOpacity\r\n key={i}\r\n onPress={() => removeReaction(reaction)}\r\n style={{\r\n backgroundColor: position === \"left\" ? theme?.background.primary : theme?.background.secondary,\r\n borderRadius: 45,\r\n borderWidth: 1,\r\n borderColor: theme?.divider,\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n flexDirection: 'row',\r\n }}\r\n >\r\n <Text\r\n style={{ fontSize: Platform.OS === 'android'? 16 * fontScale : 18 * fontScale }}\r\n >{reaction.emoji}</Text>\r\n </TouchableOpacity>\r\n ))}\r\n </View>\r\n );\r\n}\r\n","import React from \"react\";\r\nimport {\r\n StyleSheet,\r\n View,\r\n TouchableOpacity,\r\n Image,\r\n Text,\r\n ViewStyle,\r\n} from \"react-native\";\r\nimport {\r\n AttachmentTypes,\r\n Conversation,\r\n MediaType,\r\n Message,\r\n UserMeta,\r\n} from \"softchatjs-core\";\r\nimport { ChatTheme } from \"../../../types\";\r\nimport { useCallback } from \"react\";\r\nimport Sticker from \"./Sticker\";\r\nimport MessageAvatar from \"../MessageAvatar\";\r\nimport { convertToMinutes, truncate } from \"../../../utils\";\r\nimport { useConfig } from \"../../../contexts/ChatProvider\";\r\nimport { MicIcon } from \"../../../assets/icons\";\r\nimport Preview from \"./Preview\";\r\n\r\ntype QuotedProps = {\r\n quotedMessage: Message | null;\r\n onPress: () => void;\r\n layout?: \"stacked\";\r\n theme?: ChatTheme;\r\n position?: \"left\" | \"right\";\r\n chatUserId?: string;\r\n};\r\n\r\nexport default function Quoted(props: QuotedProps) {\r\n const { quotedMessage, layout, onPress, theme, position, chatUserId } = props;\r\n const { fontFamily, fontScale } = useConfig();\r\n\r\n if (!quotedMessage) {\r\n return null;\r\n }\r\n\r\n // const RenderQuotedMessagePreview = useCallback(() => {\r\n // switch (quotedMessage.attachmentType) {\r\n // case AttachmentTypes.STICKER:\r\n // return (\r\n // <View style={{ padding: 3, borderWidth: 1, borderRadius: 3 }}>\r\n // <Text\r\n // style={{\r\n // color: theme?.text.secondary,\r\n // fontSize: 10,\r\n // fontFamily,\r\n // }}\r\n // >\r\n // {quotedMessage.attachmentType}\r\n // </Text>\r\n // </View>\r\n // );\r\n // case AttachmentTypes.MEDIA:\r\n // return (\r\n // <View style={{ padding: 3, borderWidth: 1, borderRadius: 3 }}>\r\n // <Text\r\n // style={{\r\n // color: theme?.text.secondary,\r\n // fontSize: 10,\r\n // fontFamily\r\n // }}\r\n // >\r\n // {quotedMessage.attachmentType}\r\n // </Text>\r\n // </View>\r\n // );\r\n // default:\r\n // return (\r\n // <View style={{ padding: 3, borderWidth: 1, borderRadius: 3 }}>\r\n // <Text\r\n // style={{\r\n // color: theme?.text.secondary,\r\n // fontSize: 10,\r\n // fontFamily\r\n // }}\r\n // >\r\n // media\r\n // </Text>\r\n // </View>\r\n // );\r\n // }\r\n // }, []);\r\n const renderMediaPreview = () => {\r\n switch (quotedMessage.attachmentType) {\r\n case AttachmentTypes.STICKER:\r\n return <Sticker message={quotedMessage} />;\r\n case AttachmentTypes.MEDIA:\r\n var mediaType = quotedMessage.attachedMedia[0]?.type;\r\n if (mediaType === MediaType.IMAGE) {\r\n return <Sticker message={quotedMessage} />;\r\n } else if (mediaType === MediaType.AUDIO) {\r\n return (\r\n <View style={{ flexDirection: \"row\", alignItems: \"center\" }}>\r\n <MicIcon size={20} color={\"white\"} />\r\n <Text\r\n style={{\r\n color: \"white\",\r\n marginStart: 5,\r\n }}\r\n >\r\n {convertToMinutes(\r\n quotedMessage.attachedMedia[0]?.meta?.audioDurationSec ?? 0\r\n )}\r\n </Text>\r\n </View>\r\n );\r\n }\r\n }\r\n };\r\n\r\n if (layout === \"stacked\") {\r\n return (\r\n <TouchableOpacity\r\n onPress={() => onPress?.()}\r\n style={{ flexDirection: \"row\", alignItems: \"center\" }}\r\n >\r\n <View\r\n style={{\r\n height: \"70%\",\r\n width: \"100%\",\r\n flex: 1,\r\n borderLeftWidth: 2,\r\n borderTopWidth: 2,\r\n borderTopLeftRadius: 10,\r\n marginStart: 22,\r\n borderColor: theme?.divider,\r\n }}\r\n />\r\n <View\r\n style={{\r\n width: \"85%\",\r\n top: -8,\r\n flexDirection: \"row\",\r\n alignItems: \"center\",\r\n }}\r\n >\r\n <MessageAvatar\r\n size={20}\r\n initials={quotedMessage?.messageOwner?.username.substring(0, 2)}\r\n imgUrl={quotedMessage.messageOwner.profileUrl}\r\n style={{\r\n marginEnd: 5,\r\n backgroundColor: quotedMessage.messageOwner.color,\r\n }}\r\n />\r\n {quotedMessage.message ? (\r\n <Text style={{ flex: 1, color: theme?.text.disabled, fontFamily }}>\r\n {truncate(quotedMessage?.message, 100)}\r\n </Text>\r\n ) : (\r\n <>{renderMediaPreview()}</>\r\n )}\r\n </View>\r\n </TouchableOpacity>\r\n );\r\n }\r\n\r\n let rightStyle: ViewStyle = {\r\n borderRadius: 10,\r\n alignItems: \"flex-end\",\r\n padding: 8,\r\n backgroundColor: theme?.chatBubble.right.bgColor,\r\n };\r\n\r\n let leftStyle: ViewStyle = {\r\n padding: 8,\r\n borderRadius: 10,\r\n alignItems: \"flex-start\",\r\n backgroundColor: theme?.chatBubble.left.bgColor,\r\n };\r\n\r\n // var replyingTo = (messageOwner: UserMeta) => {\r\n // return chatUserId === messageOwner.uid? `You replied to ${messageOwner.username}` : `${messageOwner.username} replied to you.`\r\n // }\r\n\r\n return (\r\n <TouchableOpacity\r\n onPress={() => onPress?.()}\r\n style={[\r\n {\r\n backgroundColor: \"rgba(0,0,0,.3)\",\r\n padding: 8,\r\n marginTop: 8,\r\n marginLeft: 8,\r\n marginRight: 8,\r\n borderRadius: 10,\r\n borderLeftWidth: 4,\r\n borderTopWidth: 4,\r\n borderTopColor: 'transparent',\r\n borderLeftColor: quotedMessage.messageOwner.color,\r\n },\r\n ]}\r\n >\r\n <Text\r\n style={{\r\n color: quotedMessage.messageOwner.color,\r\n textTransform: \"capitalize\",\r\n fontFamily,\r\n marginBottom: 5,\r\n textShadowColor: \"rgba(0, 0, 0, 0.3)\",\r\n textShadowOffset: { width: 0.5, height: 0.5 },\r\n textShadowRadius: 5,\r\n }}\r\n >\r\n {quotedMessage.messageOwner.uid === chatUserId\r\n ? \"You\"\r\n : quotedMessage.messageOwner.username}\r\n </Text>\r\n <>{renderMediaPreview()}</>\r\n <Preview\r\n message={quotedMessage.message}\r\n color={\r\n position === \"left\"\r\n ? (theme?.chatBubble.left.messageColor as string)\r\n : (theme?.chatBubble.right.messageColor as string)\r\n }\r\n />\r\n {quotedMessage.message && (\r\n <Text\r\n style={{\r\n display: quotedMessage.message ? \"flex\" : \"none\",\r\n fontFamily,\r\n color: \"white\",\r\n fontSize: 14 * fontScale,\r\n marginTop: 5\r\n }}\r\n >\r\n {quotedMessage.message}\r\n </Text>\r\n )}\r\n </TouchableOpacity>\r\n );\r\n}\r\n\r\nconst styles = StyleSheet.create({\r\n main: {\r\n padding: 5,\r\n borderLeftWidth: 2,\r\n marginBottom: 5,\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\nexport function EmojiIcon(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=\"M12 17.5C14.33 17.5 16.3 16.04 17.11 14H6.89C7.69 16.04 9.67 17.5 12 17.5ZM8.5 11C8.89782 11 9.27936 10.842 9.56066 10.5607C9.84196 10.2794 10 9.89782 10 9.5C10 9.10218 9.84196 8.72064 9.56066 8.43934C9.27936 8.15804 8.89782 8 8.5 8C8.10218 8 7.72064 8.15804 7.43934 8.43934C7.15804 8.72064 7 9.10218 7 9.5C7 9.89782 7.15804 10.2794 7.43934 10.5607C7.72064 10.842 8.10218 11 8.5 11ZM15.5 11C15.8978 11 16.2794 10.842 16.5607 10.5607C16.842 10.2794 17 9.89782 17 9.5C17 9.10218 16.842 8.72064 16.5607 8.43934C16.2794 8.15804 15.8978 8 15.5 8C15.1022 8 14.7206 8.15804 14.4393 8.43934C14.158 8.72064 14 9.10218 14 9.5C14 9.89782 14.158 10.2794 14.4393 10.5607C14.7206 10.842 15.1022 11 15.5 11ZM12 20C9.87827 20 7.84344 19.1571 6.34315 17.6569C4.84285 16.1566 4 14.1217 4 12C4 9.87827 4.84285 7.84344 6.34315 6.34315C7.84344 4.84285 9.87827 4 12 4C14.1217 4 16.1566 4.84285 17.6569 6.34315C19.1571 7.84344 20 9.87827 20 12C20 14.1217 19.1571 16.1566 17.6569 17.6569C16.1566 19.1571 14.1217 20 12 20ZM12 2C6.47 2 2 6.5 2 12C2 14.6522 3.05357 17.1957 4.92893 19.0711C5.85752 19.9997 6.95991 20.7362 8.17317 21.2388C9.38642 21.7413 10.6868 22 12 22C14.6522 22 17.1957 20.9464 19.0711 19.0711C20.9464 17.1957 22 14.6522 22 12C22 10.6868 21.7413 9.38642 21.2388 8.17317C20.7362 6.95991 19.9997 5.85752 19.0711 4.92893C18.1425 4.00035 17.0401 3.26375 15.8268 2.7612C14.6136 2.25866 13.3132 2 12 2Z\"\r\n fill={color}\r\n />\r\n </Svg>\r\n );\r\n}\r\n\r\nexport function SendIcon(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 fill-rule=\"evenodd\"\r\n clip-rule=\"evenodd\"\r\n d=\"M9.94011 12.646L7.69211 11.897C5.33911 11.113 4.16211 10.721 4.16211 9.99998C4.16211 9.27998 5.33911 8.88698 7.69211 8.10298L16.2051 5.26498C17.8611 4.71298 18.6891 4.43698 19.1261 4.87398C19.5631 5.31098 19.2871 6.13898 18.7361 7.79398L15.8971 16.308C15.1131 18.661 14.7211 19.838 14.0001 19.838C13.2801 19.838 12.8871 18.661 12.1031 16.308L11.3531 14.061L15.7071 9.70698C15.8893 9.51838 15.9901 9.26578 15.9878 9.00358C15.9855 8.74138 15.8803 8.49057 15.6949 8.30516C15.5095 8.11976 15.2587 8.01459 14.9965 8.01231C14.7343 8.01003 14.4817 8.11082 14.2931 8.29298L9.94011 12.646Z\"\r\n fill={color}\r\n />\r\n </Svg>\r\n );\r\n}\r\n\r\nexport function AttachmentIcon(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=\"M21.4381 11.662L12.2481 20.852C11.1222 21.9778 9.59528 22.6103 8.00309 22.6103C6.41091 22.6103 4.88394 21.9778 3.75809 20.852C2.63225 19.7261 1.99976 18.1992 1.99976 16.607C1.99976 15.0148 2.63225 13.4878 3.75809 12.362L12.9481 3.17198C13.6987 2.42142 14.7166 1.99976 15.7781 1.99976C16.8395 1.99976 17.8575 2.42142 18.6081 3.17198C19.3587 3.92254 19.7803 4.94052 19.7803 6.00198C19.7803 7.06344 19.3587 8.08142 18.6081 8.83198L9.40809 18.022C9.22227 18.2078 9.00167 18.3552 8.75889 18.4558C8.5161 18.5563 8.25588 18.6081 7.99309 18.6081C7.7303 18.6081 7.47009 18.5563 7.2273 18.4558C6.98451 18.3552 6.76391 18.2078 6.57809 18.022C6.39227 17.8362 6.24487 17.6156 6.14431 17.3728C6.04374 17.13 5.99198 16.8698 5.99198 16.607C5.99198 16.3442 6.04374 16.084 6.14431 15.8412C6.24487 15.5984 6.39227 15.3778 6.57809 15.192L15.0681 6.71198\"\r\n stroke={color}\r\n strokeWidth=\"1.5\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n />\r\n </Svg>\r\n );\r\n}\r\n\r\nexport function MicIcon(props: Icon) {\r\n const { size = 25, color = \"black\" } = props;\r\n return (\r\n <Svg width={size} height={size} viewBox=\"0 0 256 256\" fill=\"none\">\r\n <Path\r\n d=\"M128 176C140.726 175.987 152.928 170.925 161.927 161.927C170.925 152.928 175.987 140.726 176 128V64C176 51.2696 170.943 39.0606 161.941 30.0589C152.939 21.0571 140.73 16 128 16C115.27 16 103.061 21.0571 94.0589 30.0589C85.0571 39.0606 80 51.2696 80 64V128C80.0132 140.726 85.0746 152.928 94.0735 161.927C103.072 170.925 115.274 175.987 128 176ZM96 64C96 55.5131 99.3714 47.3737 105.373 41.3726C111.374 35.3714 119.513 32 128 32C136.487 32 144.626 35.3714 150.627 41.3726C156.629 47.3737 160 55.5131 160 64V128C160 136.487 156.629 144.626 150.627 150.627C144.626 156.629 136.487 160 128 160C119.513 160 111.374 156.629 105.373 150.627C99.3714 144.626 96 136.487 96 128V64ZM136 207.6V232C136 234.122 135.157 236.157 133.657 237.657C132.157 239.157 130.122 240 128 240C125.878 240 123.843 239.157 122.343 237.657C120.843 236.157 120 234.122 120 232V207.6C100.276 205.593 81.9976 196.344 68.6984 181.641C55.3992 166.938 48.0244 147.825 48 128C48 125.878 48.8429 123.843 50.3431 122.343C51.8434 120.843 53.8783 120 56 120C58.1217 120 60.1566 120.843 61.6569 122.343C63.1571 123.843 64 125.878 64 128C64 144.974 70.7428 161.253 82.7452 173.255C94.7475 185.257 111.026 192 128 192C144.974 192 161.253 185.257 173.255 173.255C185.257 161.253 192 144.974 192 128C192 125.878 192.843 123.843 194.343 122.343C195.843 120.843 197.878 120 200 120C202.122 120 204.157 120.843 205.657 122.343C207.157 123.843 208 125.878 208 128C207.976 147.825 200.601 166.938 187.302 181.641C174.002 196.344 155.724 205.593 136 207.6Z\"\r\n fill={color}\r\n />\r\n </Svg>\r\n );\r\n}\r\n\r\nexport function ReplyIcon(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=\"M8 9.8V10.7L9.7 11C12.3 11.4 14.2 12.4 15.6 13.7C13.9 13.2 12.1 12.9 10 12.9H8V14.2L5.8 12L8 9.8ZM10 5L3 12L10 19V14.9C15 14.9 18.5 16.5 21 20C20 15 17 10 10 9\"\r\n fill={color}\r\n />\r\n </Svg>\r\n );\r\n}\r\n\r\nexport function SearchIcon(props: Icon) {\r\n const { size = 25, color = \"black\" } = props;\r\n return (\r\n <Svg width={size} height={size} viewBox=\"0 0 18 18\" fill=\"none\">\r\n <Path\r\n d=\"M7.76999 15.3C6.2807 15.3 4.82485 14.8584 3.58655 14.031C2.34825 13.2036 1.38311 12.0275 0.813181 10.6516C0.243253 9.27567 0.0941338 7.76164 0.38468 6.30096C0.675227 4.84029 1.39239 3.49857 2.44548 2.44548C3.49857 1.39239 4.84029 0.675227 6.30096 0.38468C7.76164 0.0941338 9.27567 0.243253 10.6516 0.813181C12.0275 1.38311 13.2036 2.34825 14.031 3.58655C14.8584 4.82485 15.3 6.2807 15.3 7.76999C15.3 8.75885 15.1052 9.73802 14.7268 10.6516C14.3484 11.5652 13.7937 12.3953 13.0945 13.0945C12.3953 13.7937 11.5652 14.3484 10.6516 14.7268C9.73802 15.1052 8.75885 15.3 7.76999 15.3ZM7.76999 1.74999C6.58331 1.74999 5.42327 2.10189 4.43657 2.76118C3.44988 3.42046 2.68084 4.35754 2.22672 5.45389C1.77259 6.55025 1.65377 7.75665 1.88528 8.92054C2.11679 10.0844 2.68824 11.1535 3.52735 11.9926C4.36647 12.8317 5.43556 13.4032 6.59945 13.6347C7.76334 13.8662 8.96974 13.7474 10.0661 13.2933C11.1625 12.8391 12.0995 12.0701 12.7588 11.0834C13.4181 10.0967 13.77 8.93668 13.77 7.74999C13.77 6.15869 13.1379 4.63257 12.0126 3.50735C10.8874 2.38213 9.36129 1.74999 7.76999 1.74999Z\"\r\n fill={color}\r\n />\r\n <Path\r\n d=\"M17 17.75C16.9014 17.7504 16.8038 17.7312 16.7128 17.6934C16.6218 17.6557 16.5392 17.6001 16.47 17.53L12.34 13.4C12.2075 13.2578 12.1354 13.0697 12.1388 12.8754C12.1422 12.6811 12.2209 12.4958 12.3583 12.3583C12.4958 12.2209 12.6811 12.1422 12.8754 12.1388C13.0697 12.1354 13.2578 12.2075 13.4 12.34L17.53 16.47C17.6704 16.6106 17.7493 16.8012 17.7493 17C17.7493 17.1987 17.6704 17.3893 17.53 17.53C17.4607 17.6001 17.3782 17.6557 17.2872 17.6934C17.1961 17.7312 17.0985 17.7504 17 17.75Z\"\r\n fill={color}\r\n />\r\n </Svg>\r\n );\r\n}\r\n\r\nexport function KeyboardIcon(props: Icon) {\r\n const { size = 25, color = \"black\" } = props;\r\n\r\n return (\r\n <Svg width={size} height={size} viewBox=\"0 0 24 24\"