mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-02-15 07:28:59 +00:00
Refactor conversation panels types, and remove obsolete event
Co-authored-by: Jamie <113370520+jamiebuilds-signal@users.noreply.github.com>
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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');
|
||||
})
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user