271 lines
8.6 KiB
JavaScript
271 lines
8.6 KiB
JavaScript
import { useState, useRef, useEffect, useGlobal, getGlobal } from 'reactn'
|
|
import { Pressable, TextInput, View, Image, Text, Keyboard, FlatList } from 'react-native'
|
|
import { responsiveHeight } from '../actions/responsiveSizes.js'
|
|
import { useDataFromRef } from 'react-native-minuit/src/hooks'
|
|
import { useKeyboard } from '@react-native-community/hooks'
|
|
import moment from 'moment'
|
|
|
|
import { Fonts, gutters, Palette } from '../styles'
|
|
import Style, { bubbleStyle } from '../styles/Style'
|
|
|
|
import firebase, { chatsRef } from '../config/firebase'
|
|
|
|
import { formatNameForConfidentiality } from '../helpers/index.js'
|
|
import useLayoutType from '../hooks/useLayoutType.js'
|
|
|
|
import TypingLoader from './TypingLoader'
|
|
import Avatar from './Avatar'
|
|
import RenderChatFile from './RenderChatFile.js'
|
|
import HyperlinkContainer from './HyperlinkContainer.js'
|
|
|
|
export default ({
|
|
chatID = null,
|
|
|
|
layout = 'default', // default | taskSideBar
|
|
|
|
containerStyle = {},
|
|
messageListContainerStyle = {},
|
|
}) => {
|
|
const [currentUID] = useGlobal('currentUID')
|
|
const [currentProjectData] = useGlobal('currentProjectData')
|
|
|
|
const [isTyping] = useState(false)
|
|
|
|
const flatListRef = useRef()
|
|
|
|
const { isNative } = useLayoutType()
|
|
const { keyboardShown = false } = useKeyboard()
|
|
|
|
const { data: messageList } = useDataFromRef({
|
|
ref: chatID
|
|
? chatsRef.doc(chatID).collection('messages').orderBy('createdAt', 'desc').limit(50)
|
|
: null,
|
|
simpleRef: false,
|
|
listener: true,
|
|
condition: chatID,
|
|
refreshArray: [chatID],
|
|
documentID: 'messageID',
|
|
})
|
|
|
|
let conversation = [
|
|
isTyping ? { senderID: 'minuit.ai', userTyping: true } : null,
|
|
...(messageList || []),
|
|
].filter((item) => item)
|
|
|
|
if (layout === 'default') {
|
|
conversation = conversation.reverse()
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (flatListRef?.current && isNative && keyboardShown) {
|
|
flatListRef?.current?.scrollToEnd?.({ animated: true })
|
|
}
|
|
}, [keyboardShown, isNative])
|
|
|
|
return (
|
|
<View
|
|
style={{
|
|
height: responsiveHeight(85, true),
|
|
...containerStyle,
|
|
}}
|
|
>
|
|
<View style={{ flex: 1, ...messageListContainerStyle }}>
|
|
<FlatList
|
|
ref={flatListRef}
|
|
data={conversation}
|
|
estimatedItemSize={50}
|
|
scrollEnabled
|
|
contentContainerStyle={{
|
|
paddingBottom: responsiveHeight(40),
|
|
}}
|
|
showsVerticalScrollIndicator={false}
|
|
keyExtractor={(item, index) =>
|
|
item?.messageID ? `${item?.messageID?.toString()}-${index}` : `no-messageID-${index}`
|
|
}
|
|
renderItem={({
|
|
item: {
|
|
createdAt = null,
|
|
senderID,
|
|
senderName = '',
|
|
senderProfilePicture = null,
|
|
text = '',
|
|
userTyping = false,
|
|
files = [],
|
|
},
|
|
index,
|
|
}) => {
|
|
const isCurrentUser = senderID === currentUID
|
|
const isChatbot = senderID === 'minuit.ai'
|
|
|
|
const senderData = currentProjectData?.teamMembers?.[senderID] || {}
|
|
|
|
const isDayChange =
|
|
moment(createdAt?.toDate()).format('DD/MM/YYYY') !==
|
|
moment(conversation[index - 1]?.createdAt?.toDate() || new Date()).format(
|
|
'DD/MM/YYYY'
|
|
) || !conversation[index - 1]
|
|
|
|
return (
|
|
<>
|
|
{isDayChange && (
|
|
<View
|
|
style={{
|
|
...Style.containerCenter,
|
|
marginBottom: gutters / 2,
|
|
}}
|
|
>
|
|
<Text
|
|
style={{
|
|
...Fonts({
|
|
type: 'default',
|
|
color: Palette.white,
|
|
style: { textAlign: 'center', opacity: 0.5 },
|
|
}),
|
|
}}
|
|
>
|
|
{moment(createdAt?.toDate()).format('[Le] DD/MM/YYYY [à] HH:mm')}
|
|
</Text>
|
|
|
|
<View
|
|
style={{
|
|
...Style.separatorHorizontal,
|
|
width: '100%',
|
|
}}
|
|
/>
|
|
</View>
|
|
)}
|
|
|
|
<View
|
|
style={[
|
|
Style.containerRow,
|
|
{
|
|
flexDirection: isCurrentUser ? 'row-reverse' : 'row',
|
|
alignItems: 'flex-end',
|
|
width: '100%',
|
|
marginBottom: gutters / 2,
|
|
},
|
|
]}
|
|
>
|
|
{!isCurrentUser && (
|
|
<View
|
|
style={{
|
|
...Style.containerRound,
|
|
...Style.containerCenter,
|
|
...(isCurrentUser
|
|
? {
|
|
marginLeft: gutters / 2,
|
|
backgroundColor: Palette.primary,
|
|
}
|
|
: {
|
|
marginRight: gutters / 2,
|
|
backgroundColor: 'transparent',
|
|
borderColor: Palette.primary,
|
|
borderWidth: 1,
|
|
}),
|
|
}}
|
|
>
|
|
<Avatar
|
|
name={isChatbot ? 'm' : senderData?.name || senderName || ''}
|
|
url={senderData?.profilePictureURL || senderProfilePicture || null}
|
|
size={35}
|
|
/>
|
|
</View>
|
|
)}
|
|
|
|
<View
|
|
style={{
|
|
alignItems: isCurrentUser ? 'flex-end' : 'flex-start',
|
|
}}
|
|
>
|
|
{files?.map((props, index) => (
|
|
<RenderChatFile
|
|
key={index}
|
|
{...props}
|
|
containerStyle={{
|
|
marginBottom:
|
|
index !== files?.length - 1
|
|
? gutters / 2
|
|
: text?.length > 0 || userTyping
|
|
? gutters / 2
|
|
: 0,
|
|
}}
|
|
/>
|
|
)) || null}
|
|
|
|
{(text?.length > 0 || userTyping) && (
|
|
<View
|
|
style={{
|
|
...bubbleStyle({
|
|
isCurrentUser,
|
|
customStyle: {
|
|
marginBottom: files?.length > 0 ? gutters / 2 : 0,
|
|
},
|
|
}),
|
|
}}
|
|
>
|
|
{userTyping ? (
|
|
<TypingLoader />
|
|
) : (
|
|
<HyperlinkContainer>
|
|
<Text
|
|
style={Fonts({
|
|
type: 'default',
|
|
style: {
|
|
color: Palette.white,
|
|
textAlign: isCurrentUser ? 'right' : 'left',
|
|
width: '100%',
|
|
},
|
|
})}
|
|
>
|
|
{text}
|
|
</Text>
|
|
</HyperlinkContainer>
|
|
)}
|
|
</View>
|
|
)}
|
|
</View>
|
|
</View>
|
|
</>
|
|
)
|
|
}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
export const onSendMessage = async ({
|
|
chatID = null,
|
|
projectID = null,
|
|
message = '',
|
|
setMessage = () => {},
|
|
setIsTyping = () => {},
|
|
customPayload = {},
|
|
}) => {
|
|
try {
|
|
const currentUID = getGlobal()?.currentUID || null
|
|
const { name = '', profilePictureURL = null } = getGlobal()?.currentUserData || {}
|
|
|
|
if (message.length > 0 || customPayload?.files?.length > 0) {
|
|
Keyboard.dismiss()
|
|
setMessage('')
|
|
|
|
const messageData = {
|
|
projectID,
|
|
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
|
|
senderID: currentUID,
|
|
senderName: formatNameForConfidentiality({ name }),
|
|
senderProfilePicture: profilePictureURL || null,
|
|
text: message,
|
|
...customPayload,
|
|
}
|
|
|
|
await chatsRef.doc(chatID).collection('messages').add(messageData)
|
|
}
|
|
} catch (error) {
|
|
console.log(error)
|
|
} finally {
|
|
setIsTyping(false)
|
|
}
|
|
}
|