From 6bc6cc64c43ad75ea3d50f61da9dd7a78226ca93 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Mon, 25 Mar 2024 12:21:14 -0700 Subject: [PATCH] Sort left pane via receivedAt/receivedAtMs, even via edits --- ts/model-types.d.ts | 9 ++++++++- ts/models/conversations.ts | 25 ++++++++++++++++-------- ts/state/ducks/conversations.ts | 2 ++ ts/state/selectors/conversations.ts | 18 +++++++++++++++-- ts/test-electron/MessageReceipts_test.ts | 4 ++++ ts/util/getConversation.ts | 2 ++ ts/util/handleEditMessage.ts | 6 ++++++ 7 files changed, 55 insertions(+), 11 deletions(-) diff --git a/ts/model-types.d.ts b/ts/model-types.d.ts index ca8c202401bb..16cfb43a5a38 100644 --- a/ts/model-types.d.ts +++ b/ts/model-types.d.ts @@ -116,7 +116,8 @@ export type MessageReactionType = { }; // Note: when adding to the set of things that can change via edits, sendNormalMessage.ts -// needs more usage of get/setPropForTimestamp. +// needs more usage of get/setPropForTimestamp. Also, these fields must match the fields +// in MessageAttributesType. export type EditHistoryType = { attachments?: Array; body?: string; @@ -126,6 +127,8 @@ export type EditHistoryType = { quote?: QuotedMessageType; sendStateByConversationId?: SendStateByConversationId; timestamp: number; + received_at: number; + received_at_ms?: number; }; export type MessageAttributesType = { @@ -152,6 +155,8 @@ export type MessageAttributesType = { isViewOnce?: boolean; editHistory?: Array; editMessageTimestamp?: number; + editMessageReceivedAt?: number; + editMessageReceivedAtMs?: number; key_changed?: string; local?: boolean; logger?: unknown; @@ -334,6 +339,8 @@ export type ConversationAttributesType = { lastMessagePrefix?: string; lastMessageAuthor?: string | null; lastMessageStatus?: LastMessageStatus | null; + lastMessageReceivedAt?: number; + lastMessageReceivedAtMs?: number; markedUnread?: boolean; messageCount?: number; messageCountBeforeMessageRequests?: number | null; diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index 1f21687d341a..23884016b74d 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -4173,16 +4173,23 @@ export class ConversationModel extends window.Backbone return; } - const currentTimestamp = this.get('timestamp') || null; - - let timestamp = currentTimestamp; + let timestamp = this.get('timestamp') || null; + let lastMessageReceivedAt = this.get('lastMessageReceivedAt'); + let lastMessageReceivedAtMs = this.get('lastMessageReceivedAtMs'); if (activityMessage) { - const receivedAt = activityMessage.get('received_at_ms'); - timestamp = receivedAt - ? Math.min(activityMessage.get('sent_at'), receivedAt) - : activityMessage.get('sent_at'); + timestamp = + activityMessage.get('editMessageTimestamp') || + activityMessage.get('sent_at') || + timestamp; + lastMessageReceivedAt = + activityMessage.get('editMessageReceivedAt') || + activityMessage.get('received_at') || + lastMessageReceivedAt; + lastMessageReceivedAtMs = + activityMessage.get('editMessageReceivedAtMs') || + activityMessage.get('received_at_ms') || + lastMessageReceivedAtMs; } - timestamp = timestamp || currentTimestamp; const notificationData = previewMessage?.getNotificationData(); @@ -4196,6 +4203,8 @@ export class ConversationModel extends window.Backbone (previewMessage ? getMessagePropStatus(previewMessage.attributes, ourConversationId) : null) || null, + lastMessageReceivedAt, + lastMessageReceivedAtMs, timestamp, lastMessageDeletedForEveryone: previewMessage ? previewMessage.get('deletedForEveryone') diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index a342d56399a3..bbd85996ebcc 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -282,6 +282,8 @@ export type ConversationType = ReadonlyDeep< isVerified?: boolean; activeAt?: number; timestamp?: number; + lastMessageReceivedAt?: number; + lastMessageReceivedAtMs?: number; inboxPosition?: number; left?: boolean; lastMessage?: LastMessageType; diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 84028d4f98ba..45e2e697b39e 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -303,8 +303,9 @@ const collator = new Intl.Collator(); // phone numbers and contacts from scratch here again. export const _getConversationComparator = () => { return (left: ConversationType, right: ConversationType): number => { - const leftTimestamp = left.timestamp; - const rightTimestamp = right.timestamp; + // These two fields can be sorted with each other; they are timestamps + const leftTimestamp = left.lastMessageReceivedAtMs || left.timestamp; + const rightTimestamp = right.lastMessageReceivedAtMs || right.timestamp; if (leftTimestamp && !rightTimestamp) { return -1; } @@ -315,6 +316,19 @@ export const _getConversationComparator = () => { return rightTimestamp - leftTimestamp; } + // This field looks like a timestamp, but is actually a counter + const leftCounter = left.lastMessageReceivedAt; + const rightCounter = right.lastMessageReceivedAt; + if (leftCounter && !rightCounter) { + return -1; + } + if (rightCounter && !leftCounter) { + return 1; + } + if (leftCounter && rightCounter && leftCounter !== rightCounter) { + return rightCounter - leftCounter; + } + if ( typeof left.inboxPosition === 'number' && typeof right.inboxPosition === 'number' diff --git a/ts/test-electron/MessageReceipts_test.ts b/ts/test-electron/MessageReceipts_test.ts index 98537048ec73..fff7841926a2 100644 --- a/ts/test-electron/MessageReceipts_test.ts +++ b/ts/test-electron/MessageReceipts_test.ts @@ -129,10 +129,14 @@ describe('MessageReceipts', () => { { sendStateByConversationId: defaultSendState, timestamp: editedSentAt, + received_at: 2, + received_at_ms: Date.now(), }, { sendStateByConversationId: defaultSendState, timestamp: sentAt, + received_at: 1, + received_at_ms: Date.now(), }, ], }; diff --git a/ts/util/getConversation.ts b/ts/util/getConversation.ts index 30d688ac6599..9e06b70142ad 100644 --- a/ts/util/getConversation.ts +++ b/ts/util/getConversation.ts @@ -186,6 +186,8 @@ export function getConversation(model: ConversationModel): ConversationType { isVerified: model.isVerified(), isFetchingUUID: model.isFetchingUUID, lastMessage: getLastMessage(attributes), + lastMessageReceivedAt: attributes.lastMessageReceivedAt, + lastMessageReceivedAtMs: attributes.lastMessageReceivedAtMs, lastUpdated: dropNull(timestamp), left: Boolean(attributes.left), markedUnread: attributes.markedUnread, diff --git a/ts/util/handleEditMessage.ts b/ts/util/handleEditMessage.ts index b4d61c287a9f..78d83abf593b 100644 --- a/ts/util/handleEditMessage.ts +++ b/ts/util/handleEditMessage.ts @@ -121,6 +121,8 @@ export async function handleEditMessage( quote: mainMessage.quote, sendStateByConversationId: { ...mainMessage.sendStateByConversationId }, timestamp: mainMessage.timestamp, + received_at: mainMessage.received_at, + received_at_ms: mainMessage.received_at_ms, }, ]; @@ -253,6 +255,8 @@ export async function handleEditMessage( sendStateByConversationId: upgradedEditedMessageData.sendStateByConversationId, timestamp: upgradedEditedMessageData.timestamp, + received_at: upgradedEditedMessageData.received_at, + received_at_ms: upgradedEditedMessageData.received_at_ms, quote: nextEditedMessageQuote, }; @@ -268,6 +272,8 @@ export async function handleEditMessage( bodyRanges: editedMessage.bodyRanges, editHistory, editMessageTimestamp: upgradedEditedMessageData.timestamp, + editMessageReceivedAt: upgradedEditedMessageData.received_at, + editMessageReceivedAtMs: upgradedEditedMessageData.received_at_ms, preview: editedMessage.preview, quote: editedMessage.quote, });