import _, { noop } from 'lodash'; import React, { useCallback } from 'react'; import { InView } from 'react-intersection-observer'; import { useDispatch, useSelector } from 'react-redux'; import { getMessageById } from '../../data/data'; import { Constants } from '../../session'; import { getConversationController } from '../../session/conversations'; import { fetchMessagesForConversation, markConversationFullyRead, showScrollToBottomButton, } from '../../state/ducks/conversations'; import { areMoreMessagesBeingFetched, getHaveDoneFirstScroll, getLoadedMessagesLength, getMostRecentMessageId, getOldestMessageId, getSelectedConversationKey, } from '../../state/selectors/conversations'; import { getIsAppFocused } from '../../state/selectors/section'; type ReadableMessageProps = { children: React.ReactNode; messageId: string; className?: string; receivedAt: number | undefined; isUnread: boolean; onContextMenu?: (e: React.MouseEvent) => void; }; const debouncedTriggerLoadMore = _.debounce( (loadedMessagesLength: number, selectedConversationKey: string | undefined) => { const numMessages = loadedMessagesLength + Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT; (window.inboxStore?.dispatch as any)( fetchMessagesForConversation({ conversationKey: selectedConversationKey as string, count: numMessages, }) ); }, 100 ); export const ReadableMessage = (props: ReadableMessageProps) => { const { messageId, onContextMenu, className, receivedAt, isUnread } = props; const isAppFocused = useSelector(getIsAppFocused); const dispatch = useDispatch(); const selectedConversationKey = useSelector(getSelectedConversationKey); const loadedMessagesLength = useSelector(getLoadedMessagesLength); const haveDoneFirstScroll = useSelector(getHaveDoneFirstScroll); const mostRecentMessageId = useSelector(getMostRecentMessageId); const oldestMessageId = useSelector(getOldestMessageId); const fetchingMore = useSelector(areMoreMessagesBeingFetched); const shouldMarkReadWhenVisible = isUnread; const onVisible = useCallback( // tslint:disable-next-line: cyclomatic-complexity async (inView: boolean | Object) => { // when the view first loads, it needs to scroll to the unread messages. // we need to disable the inview on the first loading if (!haveDoneFirstScroll) { if (inView === true) { window.log.info('onVisible but waiting for first scroll event'); } return; } // we are the most recent message if (mostRecentMessageId === messageId) { // make sure the app is focused, because we mark message as read here if (inView === true && isAppFocused) { dispatch(showScrollToBottomButton(false)); void getConversationController() .get(selectedConversationKey as string) ?.markRead(receivedAt || 0) .then(() => { dispatch(markConversationFullyRead(selectedConversationKey as string)); }); } else if (inView === false) { dispatch(showScrollToBottomButton(true)); } } if (inView === true && isAppFocused && oldestMessageId === messageId && !fetchingMore) { debouncedTriggerLoadMore(loadedMessagesLength, selectedConversationKey); } // this part is just handling the marking of the message as read if needed if ( (inView === true || ((inView as any).type === 'focus' && (inView as any).returnValue === true)) && isAppFocused ) { if (shouldMarkReadWhenVisible) { const found = await getMessageById(messageId); if (found && Boolean(found.get('unread'))) { // mark the message as read. // this will trigger the expire timer. await found.markRead(Date.now()); } } } }, [ selectedConversationKey, haveDoneFirstScroll, mostRecentMessageId, oldestMessageId, fetchingMore, isAppFocused, loadedMessagesLength, receivedAt, shouldMarkReadWhenVisible, messageId, debouncedTriggerLoadMore, ] ); return ( // tslint:disable-next-line: use-simple-attributes {props.children} ); };