// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { memo, useCallback } from 'react';
import { useSelector } from 'react-redux';
import { isEqual } from 'lodash';
import { Timeline } from '../../components/conversation/Timeline.dom.js';
import { useCallingActions } from '../ducks/calling.preload.js';
import { useConversationsActions } from '../ducks/conversations.preload.js';
import { getPreferredBadgeSelector } from '../selectors/badges.preload.js';
import {
getConversationMessagesSelector,
getConversationSelector,
getHasContactSpoofingReview,
getInvitedContactsForNewlyCreatedGroup,
getMessages,
getTargetedMessage,
} from '../selectors/conversations.dom.js';
import { getSelectedConversationId } from '../selectors/nav.std.js';
import { getIntl, getTheme } from '../selectors/user.std.js';
import { SmartContactSpoofingReviewDialog } from './ContactSpoofingReviewDialog.preload.js';
import { SmartHeroRow } from './HeroRow.preload.js';
import { SmartTimelineItem } from './TimelineItem.preload.js';
import { SmartTypingBubble } from './TypingBubble.preload.js';
import { AttachmentDownloadManager } from '../../jobs/AttachmentDownloadManager.preload.js';
import {
getActiveCall,
getCallSelector,
isInFullScreenCall as getIsInFullScreenCall,
} from '../selectors/calling.std.js';
import { getMidnight } from '../../types/NotificationProfile.std.js';
import type { PropsType as SmartContactSpoofingReviewDialogPropsType } from './ContactSpoofingReviewDialog.preload.js';
import type { RenderItemProps } from './TimelineItem.preload.js';
import { getCallHistorySelector } from '../selectors/callHistory.std.js';
import { getCallIdFromEra } from '../../util/callDisposition.preload.js';
import { mapItemsIntoCollapseSets } from '../../util/CollapseSet.std.js';
import type { CollapseSet } from '../../util/CollapseSet.std.js';
type ExternalProps = {
id: string;
};
function renderItem(props: RenderItemProps): React.JSX.Element {
return (
);
}
function renderContactSpoofingReviewDialog(
props: SmartContactSpoofingReviewDialogPropsType
): React.JSX.Element {
return ;
}
function renderHeroRow(id: string): React.JSX.Element {
return ;
}
function renderTypingBubble(conversationId: string): React.JSX.Element {
return ;
}
export const SmartTimeline = memo(function SmartTimeline({
id,
}: ExternalProps) {
const conversationMessagesSelector = useSelector(
getConversationMessagesSelector
);
const conversationSelector = useSelector(getConversationSelector);
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
const hasContactSpoofingReview = useSelector(getHasContactSpoofingReview);
const i18n = useSelector(getIntl);
const invitedContactsForNewlyCreatedGroup = useSelector(
getInvitedContactsForNewlyCreatedGroup
);
const messages = useSelector(getMessages);
const selectedConversationId = useSelector(getSelectedConversationId);
const targetedMessage = useSelector(getTargetedMessage);
const theme = useSelector(getTheme);
const isInFullScreenCall = useSelector(getIsInFullScreenCall);
const conversation = conversationSelector(id);
const conversationMessages = conversationMessagesSelector(id);
const callHistorySelector = useSelector(getCallHistorySelector);
const activeCall = useSelector(getActiveCall);
const callSelector = useSelector(getCallSelector);
const {
clearInvitedServiceIdsForNewlyCreatedGroup,
clearTargetedMessage,
closeContactSpoofingReview,
discardMessages,
loadNewerMessages,
loadNewestMessages,
loadOlderMessages,
markMessageRead,
scrollToOldestUnreadMention,
setCenterMessage,
setIsNearBottom,
targetMessage,
} = useConversationsActions();
const { maybePeekGroupCall } = useCallingActions();
const getTimestampForMessage = useCallback(
(messageId: string): undefined | number => {
return messages[messageId]?.timestamp;
},
[messages]
);
const {
acceptedMessageRequest,
isBlocked = false,
isGroupV1AndDisabled,
removalStage,
typingContactIdTimestamps = {},
unreadCount,
unreadMentionsCount,
type: conversationType,
} = conversation ?? {};
const {
haveNewest,
haveOldest,
isNearBottom,
items,
messageChangeCounter,
messageLoadingState,
oldestUnseenIndex,
scrollToIndex,
scrollToIndexCounter,
totalUnseen,
} = conversationMessages;
const previousCollapseSet = React.useRef | undefined>(
undefined
);
const midnightToday = getMidnight(Date.now());
const { collapseSets, updatedOldestLastSeenIndex, updatedScrollToIndex } =
React.useMemo(() => {
const result = mapItemsIntoCollapseSets({
activeCall,
allowMultidayDaySets: true,
callHistorySelector,
callSelector,
getCallIdFromEra,
items,
messages,
midnightToday,
oldestUnseenIndex,
scrollToIndex,
});
const { resultUnseenIndex, resultScrollToIndex } = result;
let { resultSets } = result;
// Params messages changes a lot, items less often, call selectors possibly a lot.
// But we need to massage items based on the values from these params. So, if we
// generate the same data, we would like to return the same object.
if (
previousCollapseSet.current &&
isEqual(resultSets, previousCollapseSet.current)
) {
resultSets = previousCollapseSet.current;
}
previousCollapseSet.current = resultSets;
return {
collapseSets: resultSets,
updatedOldestLastSeenIndex: resultUnseenIndex,
updatedScrollToIndex: resultScrollToIndex,
};
}, [
activeCall,
callHistorySelector,
callSelector,
items,
messages,
midnightToday,
oldestUnseenIndex,
scrollToIndex,
]);
const isConversationSelected = selectedConversationId === id;
const isIncomingMessageRequest =
!acceptedMessageRequest && removalStage !== 'justNotification';
const isSomeoneTyping = Object.keys(typingContactIdTimestamps).length > 0;
const targetedMessageId = targetedMessage?.id;
return (
);
});