import { FlatList as FlatListType, NativeScrollEvent, NativeSyntheticEvent, View, } from 'react-native'; import { Col, Container, Fab, FlatList, Row, SafeScreen, StyleSystemThemedProps, Visible, Heading, ChatInput, } from 'components'; import { ChatStatus, Message, MessageDivider } from 'components/room'; import { metrics } from 'config'; import { useProfile, useResponsiveLayout, useRouting, useSettings, useTheme } from 'hooks'; import { FC, MutableRefObject, useCallback, useEffect, useRef, useState } from 'react'; import { isDateSame } from 'utils/dateTime'; import { DirectRoomAction, EmojisAction, MessageAction } from 'components/sheets'; import { Direct } from 'components/room/header'; import { FullScreen } from 'components/calling/full-screen'; import { TopBar } from 'components/calling/top-bar'; import { CloseThick } from 'icons'; import { useReactiveVar } from '@apollo/client'; import { isMessageThreadOpenVar, editingMessageIdVar, isEditingMessageVar, deletingMessageIdVar, directRoomActionPositionVar, } from 'state-management/chat'; import { MessageFragment, MessageStatusEnum, RoomMember, RoomMemberRoleEnum, useDeleteMessageMutation, useMessageAddedOrEditedSubscription, useMultipleMessagesQuery, useOneRoomQuery, } from 'expo-schema'; import { RootStackScreenProps } from 'navigation'; import { NavigationRoutes } from 'navigation/enum'; import { isDirectRoomActionOpenVar } from 'state-management/dialogs'; const { STYLES, Z_INDEX, LIMITS } = metrics; export const Chat: FC = ({ route, isPopUp, ...props }) => { const chatListRef = useRef() as MutableRefObject; const chatInputRef = useRef() as MutableRefObject; const roomActionRef = useRef(null); const { profile } = useProfile(); const { activeThemeMode } = useTheme(); const { params } = useRouting(); const { isChatPopUpOpen, isOnCallPopUp } = useSettings(); const { height, bottom, top, isMobile, isHandHeld } = useResponsiveLayout(); const [offset, setOffset] = useState(0); const [isActionOpen, setIsActionOpen] = useState(false); const [isRoomAction, setIsRoomAction] = useState(false); const [isEmojisOpen, setIsEmojisOpen] = useState(false); const [messages, setMessages] = useState([]); const [messageForEdit, setMessageForEdit] = useState(); const isMessageThreadOpen = useReactiveVar(isMessageThreadOpenVar); const editingMessageId = useReactiveVar(editingMessageIdVar); const isEditingMessage = useReactiveVar(isEditingMessageVar); const deletingMessageId = useReactiveVar(deletingMessageIdVar); const paddingBottom = bottom + STYLES.screenPaddingBottom; const marginTop = top + STYLES.topBarHeight + STYLES.headerHeight; const backgroundColor = activeThemeMode === 'dark' ? 'base' : 'base-01'; const px = isMobile ? 0 : 's'; const isCallActiveOnPopUp = isPopUp && isOnCallPopUp; const isSmall = isMobile || isChatPopUpOpen; const editingMessage = editingMessageId && isEditingMessage; const roomId = isHandHeld ? route.params.roomId : params.id; const position = !isHandHeld ? roomActionRef?.current && ((roomActionRef?.current as unknown as HTMLDivElement).getBoundingClientRect() as DOMRect) : null; const chatInputPosition = !isHandHeld ? chatInputRef?.current && ((chatInputRef?.current as unknown as HTMLDivElement).getBoundingClientRect() as DOMRect) : null; const inputHeight = (chatInputPosition && chatInputPosition.height) as number; const chatHeight = height - marginTop - inputHeight; const { data, refetch } = useMultipleMessagesQuery({ variables: { input: { roomId, limit: LIMITS.messages, offset, status: MessageStatusEnum.Sent, }, }, }); useMessageAddedOrEditedSubscription({ variables: { roomId, }, onSubscriptionData: (sub) => { handleSubscription(sub.subscriptionData.data?.messageAddedOrEdited as MessageFragment); }, }); const { data: roomData } = useOneRoomQuery({ variables: { roomId, }, }); const participant = roomData?.oneRoom?.members?.find( (member) => member.role === RoomMemberRoleEnum.Participant ); useEffect(() => { if (chatListRef.current) { // chatListRef.current.scrollToEnd({ animated: false }); } }, [messages.length]); useEffect(() => { if (data?.multipleMessages) { const key = 'id'; setMessages((state) => [ ...new Map([...state, ...data?.multipleMessages].map((msg) => [msg[key], msg])).values(), ]); } }, [data?.multipleMessages]); useEffect(() => { setMessages([]); }, [roomId]); const isMessageExist = useCallback( (messageId: string) => { const messageResponse = messages.find((message) => message.id === messageId); return messageResponse; }, [messages] ); const updateMessage = useCallback( (message: MessageFragment) => { const index = messages.findIndex((msg) => msg.id === message.id); const updatedMessages = [...messages]; updatedMessages.splice(index, 1, message); setMessages(updatedMessages); }, [messages] ); const addMessage = useCallback( (message: MessageFragment) => { setMessages([...messages, message]); }, [messages] ); const handleSubscription = useCallback( (messageAddedOrEdited: MessageFragment) => { if (messageAddedOrEdited) { const isSent = messageAddedOrEdited.status === MessageStatusEnum.Sent; if (isSent) { if (isMessageExist(messageAddedOrEdited.id)) { updateMessage(messageAddedOrEdited); } else { if (messageAddedOrEdited?.createdBy.id !== profile?.id) { addMessage(messageAddedOrEdited); } } } } }, [addMessage, isMessageExist, profile?.id, updateMessage] ); const onDeleteComplete = useCallback(() => { deletingMessageIdVar(''); }, []); const [deleteMessage] = useDeleteMessageMutation({ onCompleted: onDeleteComplete, }); useEffect(() => { if (deletingMessageId) { deleteMessage({ variables: { id: deletingMessageId, }, }); } }, [deleteMessage, deletingMessageId]); useEffect(() => { if (editingMessage) { const foundMessage = messages?.find((message) => message.id === editingMessageId); if (foundMessage) { setMessageForEdit(foundMessage); } } }, [editingMessageId, editingMessage, messages]); const closeThread = useCallback(() => { isMessageThreadOpenVar(false); }, []); const renderDivider = useCallback( (index: number) => { if (messages) { if (messages[index - 1]) { if (!isDateSame(messages[index - 1].createdAt, messages[index].createdAt)) { return ; } return null; } } return null; }, [messages] ); const openAction = useCallback(() => { setIsActionOpen(true); }, []); const openRoomAction = useCallback(() => { if (isHandHeld) { setIsRoomAction(!isRoomAction); } else { isDirectRoomActionOpenVar(true); } }, [isHandHeld, isRoomAction]); const openEmojisSheet = useCallback(() => { setIsActionOpen(false); setIsEmojisOpen(true); }, []); useEffect(() => { if (position) { const roomActionPosition = position as unknown as DOMRect; directRoomActionPositionVar({ top: roomActionPosition.top, bottom: undefined, x: roomActionPosition.x, }); } }, [position]); const handleScroll = useCallback( (event: NativeSyntheticEvent) => { const offsetY = event.nativeEvent.contentOffset.y; const lastMessagesLength = data?.multipleMessages.length === LIMITS.messages; const isRefetch = lastMessagesLength && offsetY === 0; if (isRefetch) { refetch({ input: { limit: LIMITS.messages, offset: offset + LIMITS.messages, roomId, status: MessageStatusEnum.Sent, }, }); setOffset((state) => state + LIMITS.messages); } }, [data?.multipleMessages, offset, refetch, roomId] ); return ( item.id} renderItem={({ item, index }) => ( {renderDivider(index)} )} /> } /> } title="Thread" caption="General / PSY 345 Summer 2021-22" /> item.id} renderItem={({ item, index }) => ( {renderDivider(index)} )} /> ); }; type ChatType = StyleSystemThemedProps & ChatProps & RootStackScreenProps; interface ChatProps { isPopUp: boolean; }