diff --git a/ts/services/expiringMessagesDeletion.preload.ts b/ts/services/expiringMessagesDeletion.preload.ts index f28f27f909..4b5b92879c 100644 --- a/ts/services/expiringMessagesDeletion.preload.ts +++ b/ts/services/expiringMessagesDeletion.preload.ts @@ -1,7 +1,5 @@ // Copyright 2016 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only - -import { batch } from 'react-redux'; import lodash from 'lodash'; import * as Errors from '../types/errors.std.js'; @@ -49,14 +47,9 @@ class ExpiringMessagesDeletionService { cleanupMessages, }); - batch(() => { - inMemoryMessages.forEach(message => { - log.info('Message expired', { - sentAt: message.get('sent_at'), - }); - - // We do this to update the UI, if this message is being displayed somewhere - window.reduxActions.conversations.messageExpired(message.id); + inMemoryMessages.forEach(message => { + log.info('Message expired', { + sentAt: message.get('sent_at'), }); }); } catch (error) { diff --git a/ts/services/tapToViewMessagesDeletionService.preload.ts b/ts/services/tapToViewMessagesDeletionService.preload.ts index 4c757032c2..ffb9cd54a0 100644 --- a/ts/services/tapToViewMessagesDeletionService.preload.ts +++ b/ts/services/tapToViewMessagesDeletionService.preload.ts @@ -40,9 +40,6 @@ async function eraseTapToViewMessages() { getMessageIdForLogging(message.attributes) ); - // We do this to update the UI, if this message is being displayed somewhere - window.reduxActions.conversations.messageExpired(message.id); - await eraseMessageContents(message, 'view-once-expired'); }) ); diff --git a/ts/state/ducks/conversations.preload.ts b/ts/state/ducks/conversations.preload.ts index 179cfca434..057bd9af8d 100644 --- a/ts/state/ducks/conversations.preload.ts +++ b/ts/state/ducks/conversations.preload.ts @@ -157,15 +157,11 @@ import { initiateMigrationToGroupV2 as doInitiateMigrationToGroupV2, } from '../../groups.preload.js'; import { getMessageById } from '../../messages/getMessageById.preload.js'; -import type { - PanelRenderType, - PanelRequestType, -} from '../../types/Panels.std.js'; +import type { PanelArgsType } from '../../types/Panels.std.js'; import type { ConversationQueueJobData } from '../../jobs/conversationJobQueue.preload.js'; import { isOlderThan } from '../../util/timestamp.std.js'; import { DAY } from '../../util/durations/index.std.js'; import { isNotNil } from '../../util/isNotNil.std.js'; -import { PanelType } from '../../types/Panels.std.js'; import { startConversation } from '../../util/startConversation.dom.js'; import { getMessageSentTimestamp } from '../../util/getMessageSentTimestamp.std.js'; import { removeLinkPreview } from '../../services/LinkPreview.preload.js'; @@ -630,10 +626,9 @@ export type ConversationsStateType = ReadonlyDeep<{ isAnimating: boolean; wasAnimated: boolean; direction: 'push' | 'pop' | undefined; - stack: ReadonlyArray; + stack: ReadonlyArray; watermark: number; }; - targetedMessageForDetails?: ReadonlyMessageAttributesType; lastSelectedMessage: MessageTimestamps | undefined; selectedMessageIds: ReadonlyArray | undefined; @@ -716,7 +711,6 @@ const PANEL_ANIMATION_STARTED = 'conversations/PANEL_ANIMATION_STARTED'; export const MARK_READ = 'conversations/MARK_READ'; export const MESSAGE_CHANGED = 'MESSAGE_CHANGED'; export const MESSAGE_DELETED = 'MESSAGE_DELETED'; -export const MESSAGE_EXPIRED = 'conversations/MESSAGE_EXPIRED'; export const SET_VOICE_NOTE_PLAYBACK_RATE = 'conversations/SET_VOICE_NOTE_PLAYBACK_RATE'; export const CONVERSATION_UNLOADED = 'CONVERSATION_UNLOADED'; @@ -944,13 +938,6 @@ export type MessagesAddedActionType = ReadonlyDeep<{ }; }>; -export type MessageExpiredActionType = ReadonlyDeep<{ - type: typeof MESSAGE_EXPIRED; - payload: { - id: string; - }; -}>; - export type RepairNewestMessageActionType = ReadonlyDeep<{ type: 'REPAIR_NEWEST_MESSAGE'; payload: { @@ -1087,7 +1074,7 @@ export type ToggleConversationInChooseMembersActionType = ReadonlyDeep<{ type PushPanelActionType = ReadonlyDeep<{ type: typeof PUSH_PANEL; - payload: PanelRenderType; + payload: PanelArgsType; }>; type PopPanelActionType = ReadonlyDeep<{ type: typeof POP_PANEL; @@ -1160,7 +1147,6 @@ export type ConversationActionType = | MessageChangedActionType | MessageDeletedActionType | MessageExpandedActionType - | MessageExpiredActionType | MessageTargetedActionType | MessagesAddedActionType | MessagesResetActionType @@ -1261,7 +1247,6 @@ export const actions = { messageChanged, messageDeleted, messageExpanded, - messageExpired, messagesAdded, messagesReset, myProfileChanged, @@ -3314,15 +3299,6 @@ function showSpoiler( }; } -function messageExpired(id: string): MessageExpiredActionType { - return { - type: MESSAGE_EXPIRED, - payload: { - id, - }, - }; -} - function messagesAdded({ conversationId, isActive, @@ -3504,11 +3480,11 @@ function setProfileUpdateError( } export type PushPanelForConversationActionType = ReadonlyDeep< - (panel: PanelRequestType) => unknown + (panel: PanelArgsType) => unknown >; function pushPanelForConversation( - panel: PanelRequestType + panel: PanelArgsType ): ThunkAction { return async (dispatch, getState) => { const { conversations } = getState(); @@ -3519,29 +3495,6 @@ function pushPanelForConversation( return; } - if (panel.type === PanelType.MessageDetails) { - const { messageId } = panel.args; - - const message = - conversations.messagesLookup[messageId] || - (await getMessageById(messageId))?.attributes; - if (!message) { - throw new Error( - 'pushPanelForConversation: could not find message for MessageDetails' - ); - } - dispatch({ - type: PUSH_PANEL, - payload: { - type: PanelType.MessageDetails, - args: { - message, - }, - }, - }); - return; - } - dispatch({ type: PUSH_PANEL, payload: panel, @@ -4855,7 +4808,11 @@ function showConversation({ const { conversations, nav } = getState(); if (nav.selectedLocation.tab !== NavTab.Chats) { - dispatch(navActions.changeLocation({ tab: NavTab.Chats })); + dispatch( + navActions.changeLocation({ + tab: NavTab.Chats, + }) + ); const conversation = window.ConversationController.get(conversationId); conversation?.setMarkedUnread(false); } @@ -5582,30 +5539,6 @@ function visitListsInVerificationData( return result; } -function maybeUpdateSelectedMessageForDetails( - { - messageId, - targetedMessageForDetails, - }: { - messageId: string; - targetedMessageForDetails: ReadonlyMessageAttributesType | undefined; - }, - state: ConversationsStateType -): ConversationsStateType { - if (!state.targetedMessageForDetails) { - return state; - } - - if (state.targetedMessageForDetails.id !== messageId) { - return state; - } - - return { - ...state, - targetedMessageForDetails, - }; -} - export function updateLastMessage( conversationId: string ): ThunkAction { @@ -6423,19 +6356,13 @@ export function reducer( // We don't keep track of messages unless their conversation is loaded... if (!existingConversation) { - return maybeUpdateSelectedMessageForDetails( - { messageId: id, targetedMessageForDetails: data }, - dropPreloadData(state) - ); + return state; } // ...and we've already loaded that message once const existingMessage = getOwn(state.messagesLookup, id); if (!existingMessage) { - return maybeUpdateSelectedMessageForDetails( - { messageId: id, targetedMessageForDetails: data }, - dropPreloadData(state) - ); + return state; } const conversationAttrs = state.conversationLookup[conversationId]; @@ -6457,13 +6384,7 @@ export function reducer( const wasDeletedForEveryone = updatedMessage.deletedForEveryone; return { - ...maybeUpdateSelectedMessageForDetails( - { - messageId: id, - targetedMessageForDetails: updatedMessage, - }, - state - ), + ...state, preloadData: undefined, messagesLookup: { ...state.messagesLookup, @@ -6479,13 +6400,6 @@ export function reducer( }; } - if (action.type === MESSAGE_EXPIRED) { - return maybeUpdateSelectedMessageForDetails( - { messageId: action.payload.id, targetedMessageForDetails: undefined }, - dropPreloadData(state) - ); - } - if (action.type === 'MESSAGE_EXPANDED') { const { id, displayLimit } = action.payload; @@ -6501,13 +6415,6 @@ export function reducer( return { ...state, - ...maybeUpdateSelectedMessageForDetails( - { - messageId: id, - targetedMessageForDetails: updatedMessage, - }, - state - ), messagesLookup: { ...state.messagesLookup, [id]: updatedMessage, @@ -6529,13 +6436,6 @@ export function reducer( return { ...state, - ...maybeUpdateSelectedMessageForDetails( - { - messageId: id, - targetedMessageForDetails: updatedMessage, - }, - state - ), messagesLookup: { ...state.messagesLookup, [id]: updatedMessage, @@ -6682,10 +6582,7 @@ export function reducer( const existingConversation = messagesByConversation[conversationId]; if (!existingConversation) { - return maybeUpdateSelectedMessageForDetails( - { messageId: id, targetedMessageForDetails: undefined }, - dropPreloadData(state) - ); + return state; } // Assuming that we always have contiguous groups of messages in memory, the removal @@ -6735,10 +6632,7 @@ export function reducer( ); return { - ...maybeUpdateSelectedMessageForDetails( - { messageId: id, targetedMessageForDetails: undefined }, - state - ), + ...state, preloadData: undefined, messagesLookup: maybeDropMessageIdsFromMessagesLookup( messagesLookup, @@ -7057,14 +6951,6 @@ export function reducer( watermark, }; - if (action.payload.type === PanelType.MessageDetails) { - return { - ...state, - targetedConversationPanels, - targetedMessageForDetails: action.payload.args.message, - }; - } - return { ...state, targetedConversationPanels, @@ -7098,14 +6984,6 @@ export function reducer( watermark, }; - if (poppedPanel.type === PanelType.MessageDetails) { - return { - ...state, - targetedConversationPanels, - targetedMessageForDetails: undefined, - }; - } - return { ...state, targetedConversationPanels, diff --git a/ts/state/ducks/globalModals.preload.ts b/ts/state/ducks/globalModals.preload.ts index df9b077ecd..14eed8b063 100644 --- a/ts/state/ducks/globalModals.preload.ts +++ b/ts/state/ducks/globalModals.preload.ts @@ -14,7 +14,6 @@ import type { ActionCreator, MessageChangedActionType, MessageDeletedActionType, - MessageExpiredActionType, } from './conversations.preload.js'; import type { MessagePropsType } from '../selectors/message.preload.js'; import type { RecipientsByConversation } from './stories.preload.js'; @@ -39,7 +38,6 @@ import { getGroupMigrationMembers } from '../../groups.preload.js'; import { MESSAGE_CHANGED, MESSAGE_DELETED, - MESSAGE_EXPIRED, actions as conversationsActions, } from './conversations.preload.js'; import { isDownloaded } from '../../util/Attachment.std.js'; @@ -573,7 +571,6 @@ export type GlobalModalsActionType = ReadonlyDeep< | HideWhatsNewModalActionType | MessageChangedActionType | MessageDeletedActionType - | MessageExpiredActionType | ShowBackfillFailureModalActionType | ShowCallQualitySurveyActionType | ShowCriticalIdlePrimaryDeviceModalActionType @@ -1911,12 +1908,8 @@ export function reducer( } if (state.editHistoryMessages != null) { - if ( - action.type === MESSAGE_CHANGED || - action.type === MESSAGE_DELETED || - action.type === MESSAGE_EXPIRED - ) { - if (action.type === MESSAGE_DELETED || action.type === MESSAGE_EXPIRED) { + if (action.type === MESSAGE_CHANGED || action.type === MESSAGE_DELETED) { + if (action.type === MESSAGE_DELETED) { const hasMessageId = state.editHistoryMessages.some( edit => edit.id === action.payload.id ); diff --git a/ts/state/ducks/lightbox.preload.ts b/ts/state/ducks/lightbox.preload.ts index 89a7d1e5de..27a9705eca 100644 --- a/ts/state/ducks/lightbox.preload.ts +++ b/ts/state/ducks/lightbox.preload.ts @@ -10,7 +10,6 @@ import type { MediaItemType } from '../../types/MediaItem.std.js'; import type { MessageChangedActionType, MessageDeletedActionType, - MessageExpiredActionType, } from './conversations.preload.js'; import type { ShowStickerPackPreviewActionType } from './globalModals.preload.js'; import type { ShowToastActionType } from './toast.preload.js'; @@ -45,7 +44,6 @@ import { ToastType } from '../../types/Toast.dom.js'; import { MESSAGE_CHANGED, MESSAGE_DELETED, - MESSAGE_EXPIRED, saveAttachmentFromMessage, } from './conversations.preload.js'; import { showStickerPackPreview } from './globalModals.preload.js'; @@ -111,7 +109,6 @@ type LightboxActionType = | CloseLightboxActionType | MessageChangedActionType | MessageDeletedActionType - | MessageExpiredActionType | ShowLightboxActionType | SetSelectedLightboxIndexActionType | SetLightboxPlaybackDisabledActionType; @@ -601,19 +598,11 @@ export function reducer( }; } - if ( - action.type === MESSAGE_CHANGED || - action.type === MESSAGE_DELETED || - action.type === MESSAGE_EXPIRED - ) { + if (action.type === MESSAGE_CHANGED || action.type === MESSAGE_DELETED) { if (!state.isShowingLightbox) { return state; } - if (action.type === MESSAGE_EXPIRED && !state.isViewOnce) { - return state; - } - if ( action.type === MESSAGE_CHANGED && !action.payload.data.deletedForEveryone diff --git a/ts/state/ducks/mediaGallery.preload.ts b/ts/state/ducks/mediaGallery.preload.ts index 74d77de0d5..3e789d7d35 100644 --- a/ts/state/ducks/mediaGallery.preload.ts +++ b/ts/state/ducks/mediaGallery.preload.ts @@ -17,7 +17,6 @@ import { CONVERSATION_UNLOADED, MESSAGE_CHANGED, MESSAGE_DELETED, - MESSAGE_EXPIRED, } from './conversations.preload.js'; import { useBoundActions } from '../../hooks/useBoundActions.std.js'; @@ -26,7 +25,6 @@ import type { ConversationUnloadedActionType, MessageChangedActionType, MessageDeletedActionType, - MessageExpiredActionType, } from './conversations.preload.js'; import type { MediaTabType, @@ -121,7 +119,6 @@ type MediaGalleryActionType = ReadonlyDeep< | LoadMoreActionType | MessageChangedActionType | MessageDeletedActionType - | MessageExpiredActionType | SetLoadingActionType | SetTabActionType | SetSortOrderActionType @@ -741,7 +738,7 @@ export function reducer( return state; } - if (action.type === MESSAGE_DELETED || action.type === MESSAGE_EXPIRED) { + if (action.type === MESSAGE_DELETED) { return { ...state, media: state.media.filter(item => item.message.id !== action.payload.id), diff --git a/ts/state/selectors/conversations.dom.ts b/ts/state/selectors/conversations.dom.ts index 14f86d9217..6a312c0b8a 100644 --- a/ts/state/selectors/conversations.dom.ts +++ b/ts/state/selectors/conversations.dom.ts @@ -64,7 +64,7 @@ import { createLogger } from '../../logging/log.std.js'; import { TimelineMessageLoadingState } from '../../util/timelineUtil.std.js'; import { isSignalConversation } from '../../util/isSignalConversation.dom.js'; import { reduce } from '../../util/iterables.std.js'; -import type { PanelRenderType } from '../../types/Panels.std.js'; +import type { PanelArgsType } from '../../types/Panels.std.js'; import type { HasStories } from '../../types/Stories.std.js'; import { getHasStoriesSelector } from './stories2.dom.js'; import { canEditMessage } from '../../util/canEditMessage.dom.js'; @@ -1470,16 +1470,16 @@ export const getHideStoryConversationIds = createSelector( export const getActivePanel = createSelector( getConversations, - (conversations): PanelRenderType | undefined => + (conversations): PanelArgsType | undefined => conversations.targetedConversationPanels.stack[ conversations.targetedConversationPanels.watermark ] ); type PanelInformationType = { - currPanel: PanelRenderType | undefined; + currPanel: PanelArgsType | undefined; direction: 'push' | 'pop'; - prevPanel: PanelRenderType | undefined; + prevPanel: PanelArgsType | undefined; }; export const getPanelInformation = createSelector( diff --git a/ts/state/selectors/message.preload.ts b/ts/state/selectors/message.preload.ts index 21171972a2..b9100c8023 100644 --- a/ts/state/selectors/message.preload.ts +++ b/ts/state/selectors/message.preload.ts @@ -8,7 +8,6 @@ import emojiRegex from 'emoji-regex'; import LinkifyIt from 'linkify-it'; import type { ReadonlyDeep } from 'type-fest'; -import type { StateType } from '../reducer.preload.js'; import type { LastMessageStatus, ReadonlyMessageAttributesType, @@ -2455,20 +2454,15 @@ export function getLastChallengeError( return challengeErrors.pop(); } -const getTargetedMessageForDetails = ( - state: StateType -): ReadonlyMessageAttributesType | undefined => - state.conversations.targetedMessageForDetails; - const OUTGOING_KEY_ERROR = 'OutgoingIdentityKeyError'; -export const getMessageDetails = createSelector( +export const getMessageDetailsSelector = createSelector( getAccountSelector, getCachedConversationMemberColorsSelector, getConversationSelector, getIntl, getRegionCode, - getTargetedMessageForDetails, + getMessages, getUserACI, getUserPNI, getUserConversationId, @@ -2483,7 +2477,7 @@ export const getMessageDetails = createSelector( conversationSelector, i18n, regionCode, - message, + messageLookup, ourAci, ourPni, ourConversationId, @@ -2492,142 +2486,152 @@ export const getMessageDetails = createSelector( selectedMessageIds, defaultConversationColor, hasUnidentifiedDeliveryIndicators - ): SmartMessageDetailPropsType | undefined => { - if (!message || !ourConversationId) { - return; - } - - const { - errors: messageErrors = [], - sendStateByConversationId = {}, - unidentifiedDeliveries = [], - unidentifiedDeliveryReceived, - } = message; - - const unidentifiedDeliveriesSet = new Set( - map( - unidentifiedDeliveries, - identifier => - window.ConversationController.getConversationId(identifier) as string - ) - ); - - let conversationIds: Array; - if (isIncoming(message)) { - conversationIds = [ - getAuthorId(message, { - conversationSelector, - ourConversationId, - ourNumber, - ourAci, - }), - ].filter(isNotNil); - } else if (!isEmpty(sendStateByConversationId)) { - if (isMessageJustForMe(sendStateByConversationId, ourConversationId)) { - conversationIds = [ourConversationId]; - } else { - conversationIds = Object.keys(sendStateByConversationId).filter( - id => id !== ourConversationId - ); + ): ((messageId: string) => SmartMessageDetailPropsType | undefined) => + (messageId: string) => { + if (!messageLookup || !ourConversationId) { + return; } - } else { - const messageConversation = window.ConversationController.get( - message.conversationId - ); - const conversationRecipients = messageConversation - ? getRecipients(messageConversation.attributes) || [] - : []; - // Older messages don't have the recipients included on the message, so we fall back - // to the conversation's current recipients - conversationIds = conversationRecipients - .map((id: string) => - window.ConversationController.getConversationId(id) + + const message = messageLookup[messageId]; + if (!message) { + return; + } + + const { + errors: messageErrors = [], + sendStateByConversationId = {}, + unidentifiedDeliveries = [], + unidentifiedDeliveryReceived, + } = message; + + const unidentifiedDeliveriesSet = new Set( + map( + unidentifiedDeliveries, + identifier => + window.ConversationController.getConversationId( + identifier + ) as string ) - .filter(isNotNil); - } + ); - // This will make the error message for outgoing key errors a bit nicer - const allErrors = messageErrors.map(error => { - if (error.name === OUTGOING_KEY_ERROR) { - return { - ...error, - message: i18n('icu:newIdentity'), - }; - } - - return error; - }); - - // If an error has a specific number it's associated with, we'll show it next to - // that contact. Otherwise, it will be a standalone entry. - const errors = allErrors.filter(error => - Boolean(error.serviceId || error.number) - ); - const errorsGroupedById = groupBy(allErrors, error => { - const serviceId = error.serviceId || error.number; - if (!serviceId) { - return null; - } - - return window.ConversationController.getConversationId(serviceId); - }); - - const contacts: ReadonlyArray = - conversationIds.map(id => { - const errorsForContact = getOwn(errorsGroupedById, id); - const isOutgoingKeyError = Boolean( - errorsForContact?.some(error => error.name === OUTGOING_KEY_ERROR) + let conversationIds: Array; + if (isIncoming(message)) { + conversationIds = [ + getAuthorId(message, { + conversationSelector, + ourConversationId, + ourNumber, + ourAci, + }), + ].filter(isNotNil); + } else if (!isEmpty(sendStateByConversationId)) { + if (isMessageJustForMe(sendStateByConversationId, ourConversationId)) { + conversationIds = [ourConversationId]; + } else { + conversationIds = Object.keys(sendStateByConversationId).filter( + id => id !== ourConversationId + ); + } + } else { + const messageConversation = window.ConversationController.get( + message.conversationId ); + const conversationRecipients = messageConversation + ? getRecipients(messageConversation.attributes) || [] + : []; + // Older messages don't have the recipients included on the message, so we fall + // back to the conversation's current recipients + conversationIds = conversationRecipients + .map((id: string) => + window.ConversationController.getConversationId(id) + ) + .filter(isNotNil); + } - let isUnidentifiedDelivery = false; - if (hasUnidentifiedDeliveryIndicators) { - isUnidentifiedDelivery = isIncoming(message) - ? Boolean(unidentifiedDeliveryReceived) - : unidentifiedDeliveriesSet.has(id); + // This will make the error message for outgoing key errors a bit nicer + const allErrors = messageErrors.map(error => { + if (error.name === OUTGOING_KEY_ERROR) { + return { + ...error, + message: i18n('icu:newIdentity'), + }; } - const sendState = getOwn(sendStateByConversationId, id); - - let status = sendState?.status; - - // If a message was only sent to yourself (Note to Self or a lonely group), it - // is shown read. - if (id === ourConversationId && status && isSent(status)) { - status = SendStatus.Read; - } - - const statusTimestamp = sendState?.updatedAt; - - return { - ...conversationSelector(id), - errors: errorsForContact, - isOutgoingKeyError, - isUnidentifiedDelivery, - status, - statusTimestamp: - statusTimestamp === message.timestamp ? undefined : statusTimestamp, - }; + return error; }); - return { - contacts, - errors, - message: getPropsForMessage(message, { - accountSelector, - contactNameColors: cachedConversationMemberColorsSelector( - message.conversationId - ), - conversationSelector, - ourAci, - ourPni, - ourConversationId, - ourNumber, - regionCode, - pinnedMessagesMessageIds, - selectedMessageIds, - defaultConversationColor, - }), - receivedAt: Number(message.received_at_ms || message.received_at), - }; - } + // If an error has a specific number it's associated with, we'll show it next to + // that contact. Otherwise, it will be a standalone entry. + const errors = allErrors.filter(error => + Boolean(error.serviceId || error.number) + ); + const errorsGroupedById = groupBy(allErrors, error => { + const serviceId = error.serviceId || error.number; + if (!serviceId) { + return null; + } + + return window.ConversationController.getConversationId(serviceId); + }); + + const contacts: ReadonlyArray = + conversationIds.map(id => { + const errorsForContact = getOwn(errorsGroupedById, id); + const isOutgoingKeyError = Boolean( + errorsForContact?.some(error => error.name === OUTGOING_KEY_ERROR) + ); + + let isUnidentifiedDelivery = false; + if (hasUnidentifiedDeliveryIndicators) { + isUnidentifiedDelivery = isIncoming(message) + ? Boolean(unidentifiedDeliveryReceived) + : unidentifiedDeliveriesSet.has(id); + } + + const sendState = getOwn(sendStateByConversationId, id); + + let status = sendState?.status; + + // If a message was only sent to yourself (Note to Self or a lonely group), it + // is shown read. + if (id === ourConversationId && status && isSent(status)) { + status = SendStatus.Read; + } + + const statusTimestamp = sendState?.updatedAt; + + return { + ...conversationSelector(id), + errors: errorsForContact, + isOutgoingKeyError, + isUnidentifiedDelivery, + status, + statusTimestamp: + statusTimestamp === message.timestamp + ? undefined + : statusTimestamp, + }; + }); + + return { + contacts, + errors, + message: getPropsForMessage(message, { + accountSelector, + contactNameColors: cachedConversationMemberColorsSelector( + message.conversationId + ), + conversationSelector, + ourAci, + ourPni, + ourConversationId, + ourNumber, + regionCode, + pinnedMessagesMessageIds, + selectedMessageIds, + defaultConversationColor, + }), + receivedAt: Number(message.received_at_ms || message.received_at), + }; + } ); diff --git a/ts/state/smart/ConversationPanel.preload.tsx b/ts/state/smart/ConversationPanel.preload.tsx index 3798c07d88..9e463aab3e 100644 --- a/ts/state/smart/ConversationPanel.preload.tsx +++ b/ts/state/smart/ConversationPanel.preload.tsx @@ -12,7 +12,7 @@ import React, { } from 'react'; import { useSelector } from 'react-redux'; import classNames from 'classnames'; -import type { PanelRenderType } from '../../types/Panels.std.js'; +import type { PanelArgsType } from '../../types/Panels.std.js'; import { createLogger } from '../../logging/log.std.js'; import { PanelType } from '../../types/Panels.std.js'; import { toLogFormat } from '../../types/errors.std.js'; @@ -119,7 +119,7 @@ export const ConversationPanel = memo(function ConversationPanel({ const wasAnimated = useSelector(getWasPanelAnimated); const [lastPanelDoneAnimating, setLastPanelDoneAnimating] = - useState(null); + useState(null); const wasAnimatedRef = useRef(wasAnimated); useEffect(() => { @@ -131,7 +131,7 @@ export const ConversationPanel = memo(function ConversationPanel({ }, [panelInformation?.prevPanel]); const onAnimationDone = useCallback( - (panel: PanelRenderType | null) => { + (panel: PanelArgsType | null) => { setLastPanelDoneAnimating(panel); panelAnimationDone(); }, @@ -277,7 +277,7 @@ export const ConversationPanel = memo(function ConversationPanel({ type PanelPropsType = { conversationId: string; - panel: PanelRenderType; + panel: PanelArgsType; }; const PanelContainer = forwardRef< @@ -414,11 +414,11 @@ function PanelElement({ return ; } - log.warn(toLogFormat(missingCaseError(panel))); + log.warn(toLogFormat(missingCaseError(panel.type))); return null; } -function getPanelKey(panel: PanelRenderType): string { +function getPanelKey(panel: PanelArgsType): string { switch (panel.type) { case PanelType.AllMedia: case PanelType.ChatColorEditor: @@ -433,7 +433,6 @@ function getPanelKey(panel: PanelRenderType): string { case PanelType.StickerManager: return panel.type; case PanelType.MessageDetails: - return `${panel.type}:${panel.args.message.id}`; case PanelType.ContactDetails: return `${panel.type}:${panel.args.messageId}`; default: diff --git a/ts/state/smart/MessageDetail.preload.tsx b/ts/state/smart/MessageDetail.preload.tsx index c1787f71c6..78916090cf 100644 --- a/ts/state/smart/MessageDetail.preload.tsx +++ b/ts/state/smart/MessageDetail.preload.tsx @@ -6,14 +6,17 @@ import { useSelector } from 'react-redux'; import type { Props as MessageDetailProps } from '../../components/conversation/MessageDetail.dom.js'; import { MessageDetail } from '../../components/conversation/MessageDetail.dom.js'; -import { getContactNameColorSelector } from '../selectors/conversations.dom.js'; +import { + getActivePanel, + getContactNameColorSelector, +} from '../selectors/conversations.dom.js'; import { getIntl, getInteractionMode, getTheme, getPlatform, } from '../selectors/user.std.js'; -import { getMessageDetails } from '../selectors/message.preload.js'; +import { getMessageDetailsSelector } from '../selectors/message.preload.js'; import { getPreferredBadgeSelector } from '../selectors/badges.preload.js'; import { renderAudioAttachment } from './renderAudioAttachment.preload.js'; import { useAccountsActions } from '../ducks/accounts.preload.js'; @@ -22,6 +25,8 @@ import { useConversationsActions } from '../ducks/conversations.preload.js'; import { useGlobalModalActions } from '../ducks/globalModals.preload.js'; import { useLightboxActions } from '../ducks/lightbox.preload.js'; import { useStoriesActions } from '../ducks/stories.preload.js'; +import { PanelType } from '../../types/Panels.std.js'; +import { createLogger } from '../../logging/log.std.js'; export type { Contact } from '../../components/conversation/MessageDetail.dom.js'; export type OwnProps = Pick< @@ -29,14 +34,17 @@ export type OwnProps = Pick< 'contacts' | 'errors' | 'message' | 'receivedAt' >; +const log = createLogger('SmartMessageDetail'); + export const SmartMessageDetail = memo( function SmartMessageDetail(): React.JSX.Element | null { + const activePanel = useSelector(getActivePanel); + const getMessageDetails = useSelector(getMessageDetailsSelector); const getContactNameColor = useSelector(getContactNameColorSelector); const getPreferredBadge = useSelector(getPreferredBadgeSelector); const i18n = useSelector(getIntl); const platform = useSelector(getPlatform); const interactionMode = useSelector(getInteractionMode); - const messageDetails = useSelector(getMessageDetails); const theme = useSelector(getTheme); const { checkForAccount } = useAccountsActions(); const { endPoll } = useComposerActions(); @@ -71,6 +79,16 @@ export const SmartMessageDetail = memo( const { showLightbox, showLightboxForViewOnceMedia } = useLightboxActions(); const { viewStory } = useStoriesActions(); + let messageId: string | null = null; + + if (activePanel?.type === PanelType.MessageDetails) { + messageId = activePanel.args.messageId; + } else { + log.error(`Rendering, but current panel is ${activePanel?.type}`); + } + + const messageDetails = messageId ? getMessageDetails(messageId) : undefined; + useEffect(() => { if (!messageDetails) { popPanelForConversation(); @@ -78,6 +96,10 @@ export const SmartMessageDetail = memo( }, [messageDetails, popPanelForConversation]); if (!messageDetails) { + log.error( + `No message details found for message ${messageId}, leaving this screen.` + ); + popPanelForConversation(); return null; } diff --git a/ts/types/Nav.std.ts b/ts/types/Nav.std.ts index d9f74602b4..d9cfa4d9c4 100644 --- a/ts/types/Nav.std.ts +++ b/ts/types/Nav.std.ts @@ -3,6 +3,26 @@ import type { ReadonlyDeep } from 'type-fest'; import type { ChatFolderId, ChatFolderParams } from './ChatFolder.std.js'; +import type { PanelArgsType } from './Panels.std.js'; + +export type Location = ReadonlyDeep< + | { + tab: NavTab.Settings; + details: SettingsLocation; + } + | { tab: Exclude } +>; + +export type ChatDetails = ReadonlyDeep<{ + conversationId?: string; + panels?: { + isAnimating: boolean; + wasAnimated: boolean; + direction: 'push' | 'pop' | undefined; + stack: ReadonlyArray; + watermark: number; + }; +}>; export type SettingsLocation = ReadonlyDeep< | { @@ -29,14 +49,6 @@ export type SettingsLocation = ReadonlyDeep< } >; -export type Location = ReadonlyDeep< - | { - tab: NavTab.Settings; - details: SettingsLocation; - } - | { tab: Exclude } ->; - export enum NavTab { Chats = 'Chats', Calls = 'Calls', diff --git a/ts/types/Panels.std.ts b/ts/types/Panels.std.ts index 5130db882e..e990125c25 100644 --- a/ts/types/Panels.std.ts +++ b/ts/types/Panels.std.ts @@ -3,8 +3,6 @@ import type { ReadonlyDeep } from 'type-fest'; -import type { ReadonlyMessageAttributesType } from '../model-types.d.ts'; - export enum PanelType { AllMedia = 'AllMedia', ChatColorEditor = 'ChatColorEditor', @@ -21,47 +19,15 @@ export enum PanelType { StickerManager = 'StickerManager', } -export type PanelRequestType = ReadonlyDeep< - | { type: PanelType.AllMedia } - | { type: PanelType.ChatColorEditor } - | { - type: PanelType.ContactDetails; - args: { - messageId: string; - }; - } - | { type: PanelType.ConversationDetails } - | { type: PanelType.GroupInvites } - | { type: PanelType.GroupLinkManagement } - | { type: PanelType.GroupPermissions } - | { type: PanelType.GroupV1Members } - | { type: PanelType.GroupMemberLabelEditor } +type PanelsWithArgs = ReadonlyDeep< + | { type: PanelType.ContactDetails; args: { messageId: string } } | { type: PanelType.MessageDetails; args: { messageId: string } } - | { type: PanelType.NotificationSettings } - | { type: PanelType.PinnedMessages } - | { type: PanelType.StickerManager } >; -export type PanelRenderType = ReadonlyDeep< - | { type: PanelType.AllMedia } - | { type: PanelType.ChatColorEditor } - | { - type: PanelType.ContactDetails; - args: { - messageId: string; - }; - } - | { type: PanelType.ConversationDetails } - | { type: PanelType.GroupInvites } - | { type: PanelType.GroupLinkManagement } - | { type: PanelType.GroupPermissions } - | { type: PanelType.GroupV1Members } - | { type: PanelType.GroupMemberLabelEditor } - | { - type: PanelType.MessageDetails; - args: { message: ReadonlyMessageAttributesType }; - } - | { type: PanelType.NotificationSettings } - | { type: PanelType.PinnedMessages } - | { type: PanelType.StickerManager } ->; +type PanelsWithoutArgs = Readonly<{ + type: Exclude; + // To catch mistakes if args are accientally provided + args?: never; +}>; + +export type PanelArgsType = PanelsWithArgs | PanelsWithoutArgs;