Refactor conversation panels types, and remove obsolete event

Co-authored-by: Jamie <113370520+jamiebuilds-signal@users.noreply.github.com>
This commit is contained in:
Scott Nonnenberg
2026-02-12 06:10:23 +10:00
parent 41b2d4728c
commit 0ba17756d5
12 changed files with 228 additions and 378 deletions

View File

@@ -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) {

View File

@@ -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');
})
);

View File

@@ -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<PanelRenderType>;
stack: ReadonlyArray<PanelArgsType>;
watermark: number;
};
targetedMessageForDetails?: ReadonlyMessageAttributesType;
lastSelectedMessage: MessageTimestamps | undefined;
selectedMessageIds: ReadonlyArray<string> | 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<void, RootStateType, unknown, PushPanelActionType> {
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<void, RootStateType, unknown, never> {
@@ -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,

View File

@@ -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
);

View File

@@ -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

View File

@@ -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),

View File

@@ -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(

View File

@@ -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<string>;
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<SmartMessageDetailContact> =
conversationIds.map(id => {
const errorsForContact = getOwn(errorsGroupedById, id);
const isOutgoingKeyError = Boolean(
errorsForContact?.some(error => error.name === OUTGOING_KEY_ERROR)
let conversationIds: Array<string>;
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<SmartMessageDetailContact> =
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),
};
}
);

View File

@@ -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<PanelRenderType | null>(null);
useState<PanelArgsType | null>(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 <SmartStickerManager />;
}
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:

View File

@@ -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;
}

View File

@@ -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<NavTab, NavTab.Settings> }
>;
export type ChatDetails = ReadonlyDeep<{
conversationId?: string;
panels?: {
isAnimating: boolean;
wasAnimated: boolean;
direction: 'push' | 'pop' | undefined;
stack: ReadonlyArray<PanelArgsType>;
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<NavTab, NavTab.Settings> }
>;
export enum NavTab {
Chats = 'Chats',
Calls = 'Calls',

View File

@@ -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<PanelType, PanelsWithArgs['type']>;
// To catch mistakes if args are accientally provided
args?: never;
}>;
export type PanelArgsType = PanelsWithArgs | PanelsWithoutArgs;