From de614109d06ee168411a3db5f77022695f4d1e5a Mon Sep 17 00:00:00 2001 From: William Grant Date: Tue, 13 Jun 2023 11:47:38 +1000 Subject: [PATCH] feat: added lastMessageId to convo model to uniquely identify a lastMessage --- .../conversation-list-item/InteractionItem.tsx | 18 ++++++++++-------- ts/interactions/conversationInteractions.ts | 1 + ts/models/conversation.ts | 12 ++++++++++-- ts/models/conversationAttributes.ts | 6 ++++-- ts/node/database_utility.ts | 6 ++++++ ts/node/migration/sessionMigrations.ts | 2 ++ ts/node/sql.ts | 6 ++++-- ts/receiver/queuedJob.ts | 1 + ts/state/ducks/conversations.ts | 1 + 9 files changed, 39 insertions(+), 14 deletions(-) diff --git a/ts/components/leftpane/conversation-list-item/InteractionItem.tsx b/ts/components/leftpane/conversation-list-item/InteractionItem.tsx index 1f6ed752a..68eb848dc 100644 --- a/ts/components/leftpane/conversation-list-item/InteractionItem.tsx +++ b/ts/components/leftpane/conversation-list-item/InteractionItem.tsx @@ -27,7 +27,8 @@ export const InteractionItem = (props: InteractionItemProps) => { const isGroup = !useIsPrivate(conversationId); const isCommunity = useIsPublic(conversationId); - const [storedLastMessage, setStoredLastMessage] = useState(lastMessage?.text); + const [storedLastMessageId, setStoredLastMessageId] = useState(lastMessage?.id); + const [storedLastMessageText, setStoredLastMessageText] = useState(lastMessage?.text); // NOTE we want to reset the interaction state when the last message changes useEffect(() => { @@ -35,23 +36,24 @@ export const InteractionItem = (props: InteractionItemProps) => { const convo = getConversationController().get(conversationId); window.log.debug( - `WIP: storedLastMessage "${storedLastMessage}" convo.get('lastMessage') "${convo.get( - 'lastMessage' - )}'` + `WIP: storedLastMessageId "${storedLastMessageId}" convo.get('lastMessageId') "${convo.get( + 'lastMessageId' + )}' lastMessageId ${JSON.stringify(lastMessage)}` ); - if (storedLastMessage !== convo.get('lastMessage')) { - setStoredLastMessage(convo.get('lastMessage')); + if (storedLastMessageId !== convo.get('lastMessageId')) { + setStoredLastMessageId(convo.get('lastMessageId')); + setStoredLastMessageText(convo.get('lastMessage')); void clearConversationInteractionState({ conversationId }); } } - }, [conversationId, interactionStatus, lastMessage?.text]); + }, [conversationId, interactionStatus, lastMessage?.id]); if (isEmpty(conversationId) || isEmpty(interactionType) || isEmpty(interactionStatus)) { return null; } - let text = storedLastMessage || ''; + let text = storedLastMessageText || ''; switch (interactionType) { case ConversationInteractionType.Leave: const failText = isCommunity diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index fbe59a872..00585bb77 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -426,6 +426,7 @@ export async function deleteAllMessagesByConvoIdNoConfirmation(conversationId: s // conversation still appears on the conversation list but is empty conversation.set({ lastMessage: null, + lastMessageId: null, }); await conversation.commit(); diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 9f152371a..4880903c2 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -389,9 +389,11 @@ export class ConversationModel extends Backbone.Model { // -- Handle the last message status, if present -- const lastMessageText = this.get('lastMessage'); if (lastMessageText && lastMessageText.length) { + const lastMessageId = this.get('lastMessageId'); const lastMessageStatus = this.get('lastMessageStatus'); toRet.lastMessage = { + id: lastMessageId, status: lastMessageStatus, text: lastMessageText, }; @@ -753,6 +755,7 @@ export class ConversationModel extends Backbone.Model { this.set({ lastMessage: messageModel.getNotificationText(), + lastMessageId: messageModel.get('id'), lastMessageStatus: 'sending', active_at: networkTimestamp, }); @@ -1853,6 +1856,7 @@ export class ConversationModel extends Backbone.Model { return; } const lastMessageModel = messages.at(0); + const lastMessageId = lastMessageModel.get('id'); const lastMessageStatus = lastMessageModel.getMessagePropStatus() || undefined; const lastMessageNotificationText = lastMessageModel.getNotificationText() || undefined; // we just want to set the `status` to `undefined` if there are no `lastMessageNotificationText` @@ -1860,17 +1864,21 @@ export class ConversationModel extends Backbone.Model { !!lastMessageNotificationText && !isEmpty(lastMessageNotificationText) ? { lastMessage: lastMessageNotificationText || '', + lastMessageId, lastMessageStatus, } - : { lastMessage: '', lastMessageStatus: undefined }; + : { lastMessage: '', lastMessageId: '', lastMessageStatus: undefined }; const existingLastMessageAttribute = this.get('lastMessage'); + const existingLastMessageId = this.get('lastMessageId'); const existingLastMessageStatus = this.get('lastMessageStatus'); if ( lastMessageUpdate.lastMessage !== existingLastMessageAttribute || - lastMessageUpdate.lastMessageStatus !== existingLastMessageStatus + lastMessageUpdate.lastMessageStatus !== existingLastMessageStatus || + lastMessageUpdate.lastMessageId !== existingLastMessageId ) { if ( + lastMessageUpdate.lastMessageId === existingLastMessageId && lastMessageUpdate.lastMessageStatus === existingLastMessageStatus && lastMessageUpdate.lastMessage && lastMessageUpdate.lastMessage.length > 40 && diff --git a/ts/models/conversationAttributes.ts b/ts/models/conversationAttributes.ts index 85aa7d9f9..928832b92 100644 --- a/ts/models/conversationAttributes.ts +++ b/ts/models/conversationAttributes.ts @@ -59,7 +59,6 @@ export interface ConversationAttributes { // 0 means inactive (undefined and null too but we try to get rid of them and only have 0 = inactive) active_at: number; // this field is the one used to sort conversations in the left pane from most recent - lastMessageStatus: LastMessageStatusType; /** * lastMessage is actually just a preview of the last message text, shortened to 60 chars. * This is to avoid filling the redux store with a huge last message when it's only used in the @@ -67,6 +66,8 @@ export interface ConversationAttributes { * The shortening is made in sql.ts directly. */ lastMessage: string | null; + lastMessageId: string | null; + lastMessageStatus: LastMessageStatusType; avatarImageId?: number; // avatar imageID is currently used only for sogs. It's the fileID of the image uploaded and set as the sogs avatar (not only sogs I think, but our profile too?) @@ -129,8 +130,9 @@ export const fillConvoAttributesWithDefaults = ( expireTimer: 0, active_at: 0, - lastMessageStatus: undefined, lastMessage: null, + lastMessageId: null, + lastMessageStatus: undefined, triggerNotificationsFor: 'all', // if the settings is not set in the db, this is the default diff --git a/ts/node/database_utility.ts b/ts/node/database_utility.ts index 58c43e45d..151452d89 100644 --- a/ts/node/database_utility.ts +++ b/ts/node/database_utility.ts @@ -58,6 +58,7 @@ const allowedKeysFormatRowOfConversation = [ 'isKickedFromGroup', 'left', 'lastMessage', + 'lastMessageId', 'lastMessageStatus', 'triggerNotificationsFor', 'unreadCount', @@ -138,6 +139,10 @@ export function formatRowOfConversation( convo.lastMessage = null; } + if (!convo.lastMessageId) { + convo.lastMessageId = null; + } + if (!convo.lastMessageStatus) { convo.lastMessageStatus = undefined; } @@ -178,6 +183,7 @@ const allowedKeysOfConversationAttributes = [ 'isKickedFromGroup', 'left', 'lastMessage', + 'lastMessageId', 'lastMessageStatus', 'triggerNotificationsFor', 'lastJoinedTimestamp', diff --git a/ts/node/migration/sessionMigrations.ts b/ts/node/migration/sessionMigrations.ts index 08665a66c..5eedbe78e 100644 --- a/ts/node/migration/sessionMigrations.ts +++ b/ts/node/migration/sessionMigrations.ts @@ -1846,6 +1846,8 @@ function updateToSessionSchemaVersion32(currentVersion: number, db: BetterSqlite db.prepare(`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN interactionStatus TEXT;`).run(); + db.prepare(`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN lastMessageId TEXT;`).run(); + writeSessionSchemaVersion(targetVersion, db); })(); diff --git a/ts/node/sql.ts b/ts/node/sql.ts index 3b04c3970..ccf7210d4 100644 --- a/ts/node/sql.ts +++ b/ts/node/sql.ts @@ -427,8 +427,9 @@ function saveConversation(data: ConversationAttributes): SaveConversationReturn zombies, left, expireTimer, - lastMessageStatus, lastMessage, + lastMessageId, + lastMessageStatus, lastJoinedTimestamp, groupAdmins, isKickedFromGroup, @@ -477,8 +478,9 @@ function saveConversation(data: ConversationAttributes): SaveConversationReturn zombies: zombies && zombies.length ? arrayStrToJson(zombies) : '[]', left: toSqliteBoolean(left), expireTimer, - lastMessageStatus, lastMessage: shortenedLastMessage, + lastMessageId, + lastMessageStatus, lastJoinedTimestamp, groupAdmins: groupAdmins && groupAdmins.length ? arrayStrToJson(groupAdmins) : '[]', diff --git a/ts/receiver/queuedJob.ts b/ts/receiver/queuedJob.ts index 27a07cdb7..f5f8854f8 100644 --- a/ts/receiver/queuedJob.ts +++ b/ts/receiver/queuedJob.ts @@ -276,6 +276,7 @@ async function handleRegularMessage( conversation.set({ active_at: message.get('sent_at'), lastMessage: message.getNotificationText(), + lastMessageId: message.get('id'), }); // a new message was received for that conversation. If it was not it should not be hidden anymore await conversation.unhideIfNeeded(false); diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 3c8e80171..d147daf92 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -217,6 +217,7 @@ export type PropsForMessageWithConvoProps = PropsForMessageWithoutConvoProps & { }; export type LastMessageType = { + id: string | null; status: LastMessageStatusType; text: string | null; };