diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index 7e495d0222..6eaf279cba 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -223,7 +223,7 @@ export class ConversationModel extends window.Backbone contactCollection?: Backbone.Collection; - debouncedUpdateLastMessage?: (() => void) & { flush(): void }; + debouncedUpdateLastMessage: (() => void) & { flush(): void }; initialPromise?: Promise; @@ -1400,9 +1400,7 @@ export class ConversationModel extends window.Backbone ): Promise { await this.beforeAddSingleMessage(message); this.doAddSingleMessage(message, { isJustSent }); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.debouncedUpdateLastMessage!(); + this.debouncedUpdateLastMessage(); } private async beforeAddSingleMessage(message: MessageModel): Promise { @@ -5221,7 +5219,7 @@ export class ConversationModel extends window.Backbone async flushDebouncedUpdates(): Promise { try { - await this.debouncedUpdateLastMessage?.flush(); + this.debouncedUpdateLastMessage.flush(); } catch (error) { const logId = this.idForLogging(); log.error( diff --git a/ts/models/messages.ts b/ts/models/messages.ts index 5097b886e3..4cbf148ab1 100644 --- a/ts/models/messages.ts +++ b/ts/models/messages.ts @@ -1148,7 +1148,7 @@ export class MessageModel extends window.Backbone.Model { sticker: undefined, ...additionalProperties, }); - this.getConversation()?.debouncedUpdateLastMessage?.(); + this.getConversation()?.debouncedUpdateLastMessage(); if (shouldPersist) { await window.Signal.Data.saveMessage(this.attributes, { @@ -1485,7 +1485,7 @@ export class MessageModel extends window.Backbone.Model { saveErrors?: (errors: Array) => void ): Promise { const updateLeftPane = - this.getConversation()?.debouncedUpdateLastMessage || noop; + this.getConversation()?.debouncedUpdateLastMessage ?? noop; updateLeftPane(); diff --git a/ts/sql/Interface.ts b/ts/sql/Interface.ts index 62ba98c068..5819730eca 100644 --- a/ts/sql/Interface.ts +++ b/ts/sql/Interface.ts @@ -637,6 +637,7 @@ export type DataInterface = { clearCallHistory: (beforeTimestamp: number) => Promise>; getCallHistoryUnreadCount(): Promise; markCallHistoryRead(callId: string): Promise; + markAllCallHistoryRead(): Promise>; getCallHistoryMessageByCallId(options: { conversationId: string; callId: string; diff --git a/ts/sql/Server.ts b/ts/sql/Server.ts index ab032ba01f..fddd225d9d 100644 --- a/ts/sql/Server.ts +++ b/ts/sql/Server.ts @@ -308,6 +308,7 @@ const dataInterface: ServerInterface = { clearCallHistory, getCallHistoryUnreadCount, markCallHistoryRead, + markAllCallHistoryRead, getCallHistoryMessageByCallId, getCallHistory, getCallHistoryGroupsCount, @@ -3369,6 +3370,35 @@ async function markCallHistoryRead(callId: string): Promise { db.prepare(query).run(params); } +async function markAllCallHistoryRead(): Promise> { + const db = getInstance(); + + return db.transaction(() => { + const where = sqlFragment` + WHERE messages.type IS 'call-history' + AND messages.readStatus IS ${READ_STATUS_UNREAD} + `; + + const [selectQuery, selectParams] = sql` + SELECT DISTINCT conversationId + FROM messages + ${where}; + `; + + const conversationIds = db.prepare(selectQuery).pluck().all(selectParams); + + const [updateQuery, updateParams] = sql` + UPDATE messages + SET readStatus = ${READ_STATUS_READ} + ${where}; + `; + + db.prepare(updateQuery).run(updateParams); + + return conversationIds; + })(); +} + function getCallHistoryGroupDataSync( db: Database, isCount: boolean, @@ -5587,14 +5617,14 @@ async function eraseStorageServiceState(): Promise { storageVersion = null, storageUnknownFields = null, storageNeedsSync = 0; - + UPDATE uninstalled_sticker_packs SET storageID = null, storageVersion = null, storageUnknownFields = null, storageNeedsSync = 0; - + -- Story Distribution Lists UPDATE storyDistributions SET diff --git a/ts/state/ducks/callHistory.ts b/ts/state/ducks/callHistory.ts index c573ed2121..99703b6be6 100644 --- a/ts/state/ducks/callHistory.ts +++ b/ts/state/ducks/callHistory.ts @@ -13,6 +13,7 @@ import { ToastType } from '../../types/Toast'; import type { CallHistoryDetails } from '../../types/CallDisposition'; import * as log from '../../logging/log'; import * as Errors from '../../types/errors'; +import { drop } from '../../util/drop'; export type CallHistoryState = ReadonlyDeep<{ // This informs the app that underlying call history data has changed. @@ -77,9 +78,35 @@ function markCallHistoryRead( return async dispatch => { try { await window.Signal.Data.markCallHistoryRead(callId); - await window.ConversationController.get(conversationId)?.updateUnread(); + drop(window.ConversationController.get(conversationId)?.updateUnread()); } catch (error) { - log.error('Error marking call history read', Errors.toLogFormat(error)); + log.error( + 'markCallHistoryRead: Error marking call history read', + Errors.toLogFormat(error) + ); + } finally { + dispatch(updateCallHistoryUnreadCount()); + } + }; +} + +function markCallsTabViewed(): ThunkAction< + void, + RootStateType, + unknown, + CallHistoryUpdateUnread +> { + return async dispatch => { + try { + const conversationIds = await window.Signal.Data.markAllCallHistoryRead(); + for (const conversationId of conversationIds) { + drop(window.ConversationController.get(conversationId)?.updateUnread()); + } + } catch (error) { + log.error( + 'markCallsTabViewed: Error marking all call history read', + Errors.toLogFormat(error) + ); } finally { dispatch(updateCallHistoryUnreadCount()); } @@ -118,6 +145,7 @@ export const actions = { clearAllCallHistory, updateCallHistoryUnreadCount, markCallHistoryRead, + markCallsTabViewed, }; export const useCallHistoryActions = (): BoundActionCreatorsMapObject< diff --git a/ts/state/smart/CallsTab.tsx b/ts/state/smart/CallsTab.tsx index a8e9a50ca7..9f017b5b8e 100644 --- a/ts/state/smart/CallsTab.tsx +++ b/ts/state/smart/CallsTab.tsx @@ -1,7 +1,7 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { useSelector } from 'react-redux'; import { useItemsActions } from '../ducks/items'; import { @@ -102,8 +102,11 @@ export function SmartCallsTab(): JSX.Element { onOutgoingAudioCallInConversation, onOutgoingVideoCallInConversation, } = useCallingActions(); - const { clearAllCallHistory: clearCallHistory, markCallHistoryRead } = - useCallHistoryActions(); + const { + clearAllCallHistory: clearCallHistory, + markCallHistoryRead, + markCallsTabViewed, + } = useCallHistoryActions(); const getCallHistoryGroupsCount = useCallback( async (options: CallHistoryFilterOptions) => { @@ -149,6 +152,10 @@ export function SmartCallsTab(): JSX.Element { [allConversations, regionCode, callHistoryEdition] ); + useEffect(() => { + markCallsTabViewed(); + }, [markCallsTabViewed]); + return ( { messageId, message.get('conversationId') ); - drop(conversation.updateLastMessage()); + conversation.debouncedUpdateLastMessage(); window.MessageController.unregister(messageId); }); diff --git a/ts/util/cleanup.ts b/ts/util/cleanup.ts index a24e7169e0..d571e1eed9 100644 --- a/ts/util/cleanup.ts +++ b/ts/util/cleanup.ts @@ -15,7 +15,7 @@ export async function cleanupMessage( window.reduxActions?.conversations.messageDeleted(id, conversationId); const parentConversation = window.ConversationController.get(conversationId); - parentConversation?.debouncedUpdateLastMessage?.(); + parentConversation?.debouncedUpdateLastMessage(); window.MessageController.unregister(id);