import React, { useCallback, useState, useEffect, useRef, useMemo } from 'react'; import { useVirtualizer } from '@tanstack/react-virtual'; import { MessageItem } from './MessageItem'; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; import { useInView } from 'react-intersection-observer' import { Box } from '@mui/material'; import { ChatOptions } from './ChatOptions'; export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onReply, handleReaction, chatReferences, tempChatReferences, members, myName, selectedGroup, enableMentions }) => { const parentRef = useRef(); const [messages, setMessages] = useState(initialMessages); const [showScrollButton, setShowScrollButton] = useState(false); const [showScrollDownButton, setShowScrollDownButton] = useState(false); const hasLoadedInitialRef = useRef(false); const isAtBottomRef = useRef(true); // Update message list with unique signatures and tempMessages useEffect(() => { let uniqueInitialMessagesMap = new Map(); // Only add a message if it doesn't already exist in the Map initialMessages.forEach((message) => { if (!uniqueInitialMessagesMap.has(message.signature)) { uniqueInitialMessagesMap.set(message.signature, message); } }); const uniqueInitialMessages = Array.from(uniqueInitialMessagesMap.values()).sort( (a, b) => a.timestamp - b.timestamp ); const totalMessages = [...uniqueInitialMessages, ...(tempMessages || [])] if (totalMessages.length === 0) return; setMessages(totalMessages); setTimeout(() => { const hasUnreadMessages = totalMessages.some((msg) => msg.unread && !msg?.chatReference && !msg?.isTemp); if (parentRef.current) { const { scrollTop, scrollHeight, clientHeight } = parentRef.current; const atBottom = scrollTop + clientHeight >= scrollHeight - 10; // Adjust threshold as needed if (!atBottom && hasUnreadMessages) { setShowScrollButton(hasUnreadMessages); } else { handleMessageSeen(); } } if (!hasLoadedInitialRef.current) { const findDivideIndex = totalMessages.findIndex((item)=> !!item?.divide) const divideIndex = findDivideIndex !== -1 ? findDivideIndex : undefined scrollToBottom(totalMessages, divideIndex); hasLoadedInitialRef.current = true; } }, 500); }, [initialMessages, tempMessages]); const scrollToBottom = (initialMsgs, divideIndex) => { const index = initialMsgs ? initialMsgs.length - 1 : messages.length - 1; if (rowVirtualizer) { if(divideIndex){ rowVirtualizer.scrollToIndex(divideIndex, { align: 'start' }) } else { rowVirtualizer.scrollToIndex(index, { align: 'end' }) } } handleMessageSeen() }; const handleMessageSeen = useCallback(() => { setMessages((prevMessages) => prevMessages.map((msg) => ({ ...msg, unread: false, })) ); setShowScrollButton(false) }, []); const sentNewMessageGroupFunc = useCallback(() => { scrollToBottom(); }, [messages]); useEffect(() => { subscribeToEvent('sent-new-message-group', sentNewMessageGroupFunc); return () => { unsubscribeFromEvent('sent-new-message-group', sentNewMessageGroupFunc); }; }, [sentNewMessageGroupFunc]); const lastSignature = useMemo(()=> { if(!messages || messages?.length === 0) return null const lastIndex = messages.length - 1 return messages[lastIndex]?.signature }, [messages]) // Initialize the virtualizer const rowVirtualizer = useVirtualizer({ count: messages.length, getItemKey: React.useCallback( (index) => messages[index].signature, [messages] ), getScrollElement: () => parentRef.current, estimateSize: () => 80, // Provide an estimated height of items, adjust this as needed overscan: 10, // Number of items to render outside the visible area to improve smoothness observeElementOffset: (instance, cb) => { const offsetCheck = () => { const { scrollHeight, scrollTop, clientHeight } = instance.scrollElement; const atBottom = scrollHeight - scrollTop - clientHeight <= 300; if(showScrollButton){ setShowScrollDownButton(false) } else if(atBottom){ setShowScrollDownButton(false) } else { setShowScrollDownButton(true) } cb(scrollTop); // Pass scroll offset to callback // setShowScrollToBottom(!atBottom); }; // Initial check and continuous monitoring offsetCheck(); instance.scrollElement.addEventListener('scroll', offsetCheck); return () => instance.scrollElement.removeEventListener('scroll', offsetCheck); }, }); const goToMessage = useCallback((idx)=> { rowVirtualizer.scrollToIndex(idx) }, []) return (
{rowVirtualizer.getVirtualItems().map((virtualRow) => { const index = virtualRow.index; let message = messages[index]; let replyIndex = messages.findIndex((msg) => msg?.signature === message?.repliedTo); let reply; let reactions = null; if (message?.repliedTo && replyIndex !== -1) { reply = messages[replyIndex]; } if (message?.message && message?.groupDirectId) { replyIndex = messages.findIndex((msg) => msg?.signature === message?.message?.repliedTo); if (message?.message?.repliedTo && replyIndex !== -1) { reply = messages[replyIndex]; } message = { ...(message?.message || {}), isTemp: true, unread: false, status: message?.status }; } if (chatReferences && chatReferences[message?.signature]) { if (chatReferences[message.signature]?.reactions) { reactions = chatReferences[message.signature]?.reactions; } } let isUpdating = false; if (tempChatReferences && tempChatReferences?.find((item) => item?.chatReference === message?.signature)) { isUpdating = true; } return (
); })}
{showScrollButton && ( )} {showScrollDownButton && ( )}
{enableMentions && ( )}
); };