diff --git a/ts/data/data.ts b/ts/data/data.ts index 9a33e41f6..82017155e 100644 --- a/ts/data/data.ts +++ b/ts/data/data.ts @@ -493,9 +493,13 @@ async function getUnreadByConversation(conversationId: string): Promise> { - const messagesIds = await channels.markAllAsReadByConversationNoExpiration(conversationId); + const messagesIds = await channels.markAllAsReadByConversationNoExpiration( + conversationId, + returnMessagesUpdated + ); return messagesIds; } diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index bb7059e56..6c13eafa9 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -254,11 +254,7 @@ export function showLeaveGroupByConvoId(conversationId: string) { export function showInviteContactByConvoId(conversationId: string) { window.inboxStore?.dispatch(updateInviteContactModal({ conversationId })); } -export async function onMarkAllReadByConvoId(conversationId: string) { - const conversation = getConversationController().get(conversationId); - await conversation.markReadBouncy(Date.now()); -} export function showAddModeratorsByConvoId(conversationId: string) { window.inboxStore?.dispatch(updateAddModeratorsModal({ conversationId })); diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index fe3942186..266629ee6 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -32,6 +32,7 @@ import { actions as conversationActions, conversationChanged, conversationsChanged, + markConversationFullyRead, MessageModelPropsWithoutConvoProps, ReduxConversationType, } from '../state/ducks/conversations'; @@ -1231,27 +1232,38 @@ export class ConversationModel extends Backbone.Model { * Send read receipt if needed. */ public async markAllAsRead() { - if (this.isOpenGroupV2()) { + /** + * when marking all as read, there is a bunch of things we need to do. + * - we need to update all the messages in the DB not read yet for that conversation + * - we need to send the read receipts if there is one needed for those messages + * - we need to trigger a change on the redux store, so those messages are read AND mark the whole convo as read. + * - we need to remove any notifications related to this conversation ID. + * + * + * (if there is an expireTimer, we do it the slow way, handling each message separately) + */ + const expireTimerSet = !!this.get('expireTimer'); + if (this.isOpenGroupV2() || !expireTimerSet) { // for opengroups, we batch everything as there is no expiration timer to take care (and potentially a lot of messages) - await Data.markAllAsReadByConversationNoExpiration(this.id); + const isOpenGroup = this.isOpenGroupV2(); + // if this is an opengroup there is no need to send read receipt, and so no need to fetch messages updated. + const allReadMessages = await Data.markAllAsReadByConversationNoExpiration( + this.id, + !isOpenGroup + ); this.set({ mentionedUs: false, unreadCount: 0 }); await this.commit(); - return; - } - - // if the conversation has no expiration timer, we can also batch everything, but we also need to send read receipts potentially - // so we grab them from the db - if (!this.get('expireTimer')) { - const allReadMessages = await Data.markAllAsReadByConversationNoExpiration(this.id); - this.set({ mentionedUs: false, unreadCount: 0 }); - await this.commit(); - if (allReadMessages.length) { + if (!this.isOpenGroupV2() && allReadMessages.length) { await this.sendReadReceiptsIfNeeded(uniq(allReadMessages)); } + Notifications.clearByConversationID(this.id); + window.inboxStore?.dispatch(markConversationFullyRead(this.id)); + return; } + // otherwise, do it the slow way await this.markReadBouncy(Date.now()); } diff --git a/ts/node/sql.ts b/ts/node/sql.ts index f7270e7f7..33ae61980 100644 --- a/ts/node/sql.ts +++ b/ts/node/sql.ts @@ -1117,18 +1117,23 @@ function getUnreadByConversation(conversationId: string) { * Warning: This does not start expiration timer */ function markAllAsReadByConversationNoExpiration( - conversationId: string -): Array<{ id: string; timestamp: number }> { - const messagesUnreadBefore = assertGlobalInstance() - .prepare( - `SELECT json FROM ${MESSAGES_TABLE} WHERE - unread = $unread AND - conversationId = $conversationId;` - ) - .all({ - unread: 1, - conversationId, - }); + conversationId: string, + returnMessagesUpdated: boolean +): Array { + let toReturn: Array = []; + if (returnMessagesUpdated) { + const messagesUnreadBefore = assertGlobalInstance() + .prepare( + `SELECT json FROM ${MESSAGES_TABLE} WHERE + unread = $unread AND + conversationId = $conversationId;` + ) + .all({ + unread: 1, + conversationId, + }); + toReturn = compact(messagesUnreadBefore.map(row => jsonToObject(row.json).sent_at)); + } assertGlobalInstance() .prepare( @@ -1142,7 +1147,7 @@ function markAllAsReadByConversationNoExpiration( conversationId, }); - return compact(messagesUnreadBefore.map(row => jsonToObject(row.json).sent_at)); + return toReturn; } function getUnreadCountByConversation(conversationId: string) { diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 3d4614d4b..6c2dbd28c 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -699,11 +699,23 @@ const conversationsSlice = createSlice({ return state; } + let updatedMessages = state.messages; + + // if some are unread, mark them as read + if (state.messages.some(m => m.propsForMessage.isUnread)) { + updatedMessages = state.messages.map(m => ({ + ...m, + propsForMessage: { ...m.propsForMessage, isUnread: false }, + })); + } + // keep the unread visible just like in other apps. It will be shown until the user changes convo return { ...state, shouldHighlightMessage: false, firstUnreadMessageId: undefined, + + messages: updatedMessages, }; }, /**