From b317093a72e30a24ac2503e2daeb8aacc3012aa2 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 3 Jan 2024 14:53:33 +0100 Subject: [PATCH 01/11] Add `Thread Activity centre` labs flag --- src/i18n/strings/en_EN.json | 2 ++ src/settings/Settings.tsx | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f96bddfc281..d1b3e8f977a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1417,6 +1417,7 @@ "group_rooms": "Rooms", "group_spaces": "Spaces", "group_themes": "Themes", + "group_threads": "Threads", "group_voip": "Voice & Video", "group_widgets": "Widgets", "hidebold": "Hide notification dot (only display counters badges)", @@ -1457,6 +1458,7 @@ "sliding_sync_server_no_support": "Your server lacks native support", "sliding_sync_server_specify_proxy": "Your server lacks native support, you must specify a proxy", "sliding_sync_server_support": "Your server has native support", + "thread_activity_centre": "Thread activity centre", "under_active_development": "Under active development.", "unrealiable_e2e": "Unreliable in encrypted rooms", "video_rooms": "Video rooms", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 45cbc9341c3..e495bb8780c 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -81,6 +81,7 @@ export enum LabGroup { Spaces, Widgets, Rooms, + Threads, VoiceAndVideo, Moderation, Analytics, @@ -103,6 +104,7 @@ export const labGroupNames: Record = { [LabGroup.Spaces]: _td("labs|group_spaces"), [LabGroup.Widgets]: _td("labs|group_widgets"), [LabGroup.Rooms]: _td("labs|group_rooms"), + [LabGroup.Threads]: _td("labs|group_threads"), [LabGroup.VoiceAndVideo]: _td("labs|group_voip"), [LabGroup.Moderation]: _td("labs|group_moderation"), [LabGroup.Analytics]: _td("common|analytics"), @@ -1138,6 +1140,13 @@ export const SETTINGS: { [setting: string]: ISetting } = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + "threadActivityCentre": { + supportedLevels: LEVELS_FEATURE, + isFeature: true, + labsGroup: LabGroup.Threads, + displayName: _td("labs|thread_activity_centre"), + default: false, + }, // Electron-specific settings, they are stored by Electron and set/read over an IPC. // We store them over there are they are necessary to know before the renderer process launches. From a07196b97196d183b0a1dc6533e0dfa4fad5dcc4 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 4 Jan 2024 15:35:31 +0100 Subject: [PATCH 02/11] Rename translation string --- src/i18n/strings/en_EN.json | 4 +++- src/settings/Settings.tsx | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d1b3e8f977a..3fa7be88463 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1458,7 +1458,6 @@ "sliding_sync_server_no_support": "Your server lacks native support", "sliding_sync_server_specify_proxy": "Your server lacks native support, you must specify a proxy", "sliding_sync_server_support": "Your server has native support", - "thread_activity_centre": "Thread activity centre", "under_active_development": "Under active development.", "unrealiable_e2e": "Unreliable in encrypted rooms", "video_rooms": "Video rooms", @@ -3136,6 +3135,9 @@ "light_high_contrast": "Light high contrast", "match_system": "Match system" }, + "thread_activity_centre": { + "title": "Thread activity centre" + }, "thread_view_back_action_label": "Back to thread", "threads": { "all_threads": "All threads", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index e495bb8780c..23836d7a651 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -1144,7 +1144,7 @@ export const SETTINGS: { [setting: string]: ISetting } = { supportedLevels: LEVELS_FEATURE, isFeature: true, labsGroup: LabGroup.Threads, - displayName: _td("labs|thread_activity_centre"), + displayName: _td("thread_activity_centre|title"), default: false, }, From 007c4f5e3f16eabe1a4f513dcf2bc36c47f67a13 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 12 Jan 2024 09:57:35 +0100 Subject: [PATCH 03/11] Update supportedLevels --- src/settings/Settings.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 2b47ff4d38d..7b58533b021 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -1106,6 +1106,13 @@ export const SETTINGS: { [setting: string]: ISetting } = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, default: [], }, + "threadActivityCentre": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + labsGroup: LabGroup.Threads, + displayName: _td("thread_activity_centre|title"), + default: false, + isFeature: true, + }, [UIFeature.RoomHistorySettings]: { supportedLevels: LEVELS_UI_FEATURE, default: true, @@ -1176,13 +1183,6 @@ export const SETTINGS: { [setting: string]: ISetting } = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, - "threadActivityCentre": { - supportedLevels: LEVELS_FEATURE, - isFeature: true, - labsGroup: LabGroup.Threads, - displayName: _td("thread_activity_centre|title"), - default: false, - }, // Electron-specific settings, they are stored by Electron and set/read over an IPC. // We store them over there are they are necessary to know before the renderer process launches. From c83559b6fdd98e1df7416f55f216eb623341c12f Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 12 Jan 2024 15:08:47 +0100 Subject: [PATCH 04/11] Fix labs subsection test --- .../views/settings/tabs/user/LabsUserSettingsTab-test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx b/test/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx index ac832b88b21..040f5e8b659 100644 --- a/test/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx +++ b/test/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx @@ -58,6 +58,6 @@ describe("", () => { // non-beta labs section expect(screen.getByText("Early previews")).toBeInTheDocument(); const labsSections = container.getElementsByClassName("mx_SettingsSubsection"); - expect(labsSections).toHaveLength(9); + expect(labsSections).toHaveLength(10); }); }); From b226a08742d56772e20b77812a4177219c3f1ae0 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 15 Jan 2024 15:35:17 +0100 Subject: [PATCH 05/11] Update Threads Activity Centre label --- src/i18n/strings/en_EN.json | 4 +--- src/settings/Settings.tsx | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5d5a34c98c5..e97fd8f601e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1460,6 +1460,7 @@ "sliding_sync_server_no_support": "Your server lacks native support", "sliding_sync_server_specify_proxy": "Your server lacks native support, you must specify a proxy", "sliding_sync_server_support": "Your server has native support", + "threads_activity_centre": "Threads Activity Centre (in development). Currently this just removes thread notification counts from the count total in the room list", "under_active_development": "Under active development.", "unrealiable_e2e": "Unreliable in encrypted rooms", "video_rooms": "Video rooms", @@ -3138,9 +3139,6 @@ "light_high_contrast": "Light high contrast", "match_system": "Match system" }, - "thread_activity_centre": { - "title": "Thread activity centre" - }, "thread_view_back_action_label": "Back to thread", "threads": { "all_threads": "All threads", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 7b58533b021..202d3498c6d 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -1106,10 +1106,10 @@ export const SETTINGS: { [setting: string]: ISetting } = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, default: [], }, - "threadActivityCentre": { + "threadsActivityCentre": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, labsGroup: LabGroup.Threads, - displayName: _td("thread_activity_centre|title"), + displayName: _td("labs|threads_activity_centre"), default: false, isFeature: true, }, From 5b854dcc6a7aacecb03b706a1e93ea134004329d Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Jan 2024 16:43:24 +0000 Subject: [PATCH 06/11] Make threads activity centre labs flag split out unread counts Just shows notif & unread counts for main thread if the TAC is enabled. --- src/RoomNotifs.ts | 9 ++++++--- src/Unread.ts | 7 ++++++- src/settings/Settings.tsx | 1 + 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/RoomNotifs.ts b/src/RoomNotifs.ts index aa696f6e29c..1380f4abddd 100644 --- a/src/RoomNotifs.ts +++ b/src/RoomNotifs.ts @@ -81,9 +81,12 @@ export function setRoomNotifsState(client: MatrixClient, roomId: string, newStat } export function getUnreadNotificationCount(room: Room, type: NotificationCountType, threadId?: string): number { - let notificationCount = !!threadId - ? room.getThreadUnreadNotificationCount(threadId, type) - : room.getUnreadNotificationCount(type); + const countShownForRoom = (): number => + SettingsStore.getValue("threadsActivityCentre") + ? room.getRoomUnreadNotificationCount(type) + : room.getUnreadNotificationCount(type); + + let notificationCount = !!threadId ? room.getThreadUnreadNotificationCount(threadId, type) : countShownForRoom(); // Check notification counts in the old room just in case there's some lost // there. We only go one level down to avoid performance issues, and theory diff --git a/src/Unread.ts b/src/Unread.ts index 7b2da855dd7..daf6bc54551 100644 --- a/src/Unread.ts +++ b/src/Unread.ts @@ -57,7 +57,12 @@ export function doesRoomHaveUnreadMessages(room: Room): boolean { return false; } - for (const withTimeline of [room, ...room.getThreads()]) { + const toCheck: Array = [room]; + if (!SettingsStore.getValue("threadsActivityCentre")) { + toCheck.push(...room.getThreads()); + } + + for (const withTimeline of toCheck) { if (doesTimelineHaveUnreadMessages(room, withTimeline.timeline)) { // We found an unread, so the room is unread return true; diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 202d3498c6d..84a40396faa 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -1109,6 +1109,7 @@ export const SETTINGS: { [setting: string]: ISetting } = { "threadsActivityCentre": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, labsGroup: LabGroup.Threads, + controller: new ReloadOnChangeController(), displayName: _td("labs|threads_activity_centre"), default: false, isFeature: true, From ba9ad0763dc247ac3e31da5d47b1440697ea0463 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Jan 2024 17:16:20 +0000 Subject: [PATCH 07/11] Fix tests --- .../views/rooms/LegacyRoomHeader-test.tsx | 13 +++++++++++-- test/stores/RoomNotificationStateStore-test.ts | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/test/components/views/rooms/LegacyRoomHeader-test.tsx b/test/components/views/rooms/LegacyRoomHeader-test.tsx index ed82f74630c..8e0768cec56 100644 --- a/test/components/views/rooms/LegacyRoomHeader-test.tsx +++ b/test/components/views/rooms/LegacyRoomHeader-test.tsx @@ -792,8 +792,17 @@ function createRoom(info: IRoomCreationInfo) { const userId = client.getUserId()!; if (info.isDm) { client.getAccountData = (eventType) => { - expect(eventType).toEqual("m.direct"); - return mkDirectEvent(roomId, userId, info.userIds); + if (eventType === "m.direct") { + return mkDirectEvent(roomId, userId, info.userIds); + } else if (eventType === "im.vector.web.settings") { + return mkEvent({ + event: true, + type: "im.vector.web.settings", + room: roomId, + user: userId, + content: {}, + }); + } }; } diff --git a/test/stores/RoomNotificationStateStore-test.ts b/test/stores/RoomNotificationStateStore-test.ts index acb6a593471..46b57425d6b 100644 --- a/test/stores/RoomNotificationStateStore-test.ts +++ b/test/stores/RoomNotificationStateStore-test.ts @@ -125,6 +125,7 @@ describe("RoomNotificationStateStore", function () { ret.getPendingEvents = jest.fn().mockReturnValue([]); ret.isSpaceRoom = jest.fn().mockReturnValue(false); ret.getUnreadNotificationCount = jest.fn().mockReturnValue(numUnreads); + ret.getRoomUnreadNotificationCount = jest.fn().mockReturnValue(numUnreads); return ret; } }); From f2dc6f9f15164340b203416aa494a6da62ef5a0c Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Jan 2024 17:21:33 +0000 Subject: [PATCH 08/11] Simpler fix --- test/components/views/rooms/LegacyRoomHeader-test.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/test/components/views/rooms/LegacyRoomHeader-test.tsx b/test/components/views/rooms/LegacyRoomHeader-test.tsx index 8e0768cec56..7dadf14dbee 100644 --- a/test/components/views/rooms/LegacyRoomHeader-test.tsx +++ b/test/components/views/rooms/LegacyRoomHeader-test.tsx @@ -794,14 +794,8 @@ function createRoom(info: IRoomCreationInfo) { client.getAccountData = (eventType) => { if (eventType === "m.direct") { return mkDirectEvent(roomId, userId, info.userIds); - } else if (eventType === "im.vector.web.settings") { - return mkEvent({ - event: true, - type: "im.vector.web.settings", - room: roomId, - user: userId, - content: {}, - }); + } else { + return undefined; } }; } From c6683d2fdf783cdb236e24b4cb5f1c065a2c8bf0 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Jan 2024 18:12:30 +0000 Subject: [PATCH 09/11] Pass in & cache the status of the TAC labs flag --- src/RoomNotifs.ts | 38 ++++++++++++++----- .../dialogs/devtools/RoomNotifications.tsx | 7 +++- src/hooks/useUnreadNotifications.ts | 9 +++-- .../notifications/RoomNotificationState.ts | 7 +++- .../RoomNotificationStateStore.ts | 4 +- test/RoomNotifs-test.ts | 36 +++++++++--------- .../RoomHeader/VideoRoomChatButton-test.tsx | 2 +- .../RoomNotificationState-test.ts | 16 ++++---- 8 files changed, 75 insertions(+), 44 deletions(-) diff --git a/src/RoomNotifs.ts b/src/RoomNotifs.ts index d94053ec4be..be3516e1d59 100644 --- a/src/RoomNotifs.ts +++ b/src/RoomNotifs.ts @@ -80,13 +80,19 @@ export function setRoomNotifsState(client: MatrixClient, roomId: string, newStat } } -export function getUnreadNotificationCount(room: Room, type: NotificationCountType, threadId?: string): number { - const countShownForRoom = (): number => - SettingsStore.getValue("threadsActivityCentre") - ? room.getRoomUnreadNotificationCount(type) - : room.getUnreadNotificationCount(type); +export function getUnreadNotificationCount( + room: Room, + type: NotificationCountType, + includeThreads: boolean, + threadId?: string, +): number { + const getCountShownForRoom = (r: Room, type: NotificationCountType): number => { + return includeThreads ? r.getUnreadNotificationCount(type) : r.getRoomUnreadNotificationCount(type); + }; - let notificationCount = !!threadId ? room.getThreadUnreadNotificationCount(threadId, type) : countShownForRoom(); + let notificationCount = !!threadId + ? room.getThreadUnreadNotificationCount(threadId, type) + : getCountShownForRoom(room, type); // Check notification counts in the old room just in case there's some lost // there. We only go one level down to avoid performance issues, and theory @@ -102,7 +108,7 @@ export function getUnreadNotificationCount(room: Room, type: NotificationCountTy // notifying the user for unread messages because they would have extreme // difficulty changing their notification preferences away from "All Messages" // and "Noisy". - notificationCount += oldRoom.getUnreadNotificationCount(NotificationCountType.Highlight); + notificationCount += getCountShownForRoom(oldRoom, NotificationCountType.Highlight); } } @@ -227,9 +233,18 @@ function isMuteRule(rule: IPushRule): boolean { ); } +/** + * Returns an object giving information about the unread state of a room or thread + * @param room The room to query, or the room the thread is in + * @param threadId The thread to check the unread state of, or undefined to query the main thread + * @param includeThreads If threadId is undefined, true to include threads other than the main thread, or + * false to exclude them. Ignored if threadId is specified. + * @returns + */ export function determineUnreadState( room?: Room, threadId?: string, + includeThreads?: boolean, ): { level: NotificationLevel; symbol: string | null; count: number } { if (!room) { return { symbol: null, count: 0, level: NotificationLevel.None }; @@ -251,8 +266,13 @@ export function determineUnreadState( return { symbol: null, count: 0, level: NotificationLevel.None }; } - const redNotifs = getUnreadNotificationCount(room, NotificationCountType.Highlight, threadId); - const greyNotifs = getUnreadNotificationCount(room, NotificationCountType.Total, threadId); + const redNotifs = getUnreadNotificationCount( + room, + NotificationCountType.Highlight, + includeThreads ?? false, + threadId, + ); + const greyNotifs = getUnreadNotificationCount(room, NotificationCountType.Total, includeThreads ?? false, threadId); const trueCount = greyNotifs || redNotifs; if (redNotifs > 0) { diff --git a/src/components/views/dialogs/devtools/RoomNotifications.tsx b/src/components/views/dialogs/devtools/RoomNotifications.tsx index 5a43eeb935e..19f3ced8bc5 100644 --- a/src/components/views/dialogs/devtools/RoomNotifications.tsx +++ b/src/components/views/dialogs/devtools/RoomNotifications.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import { NotificationCountType, Room, Thread, ReceiptType } from "matrix-js-sdk/src/matrix"; -import React, { useContext } from "react"; +import React, { useContext, useMemo } from "react"; import { ReadReceipt } from "matrix-js-sdk/src/models/read-receipt"; import MatrixClientContext from "../../../../contexts/MatrixClientContext"; @@ -25,6 +25,7 @@ import { determineUnreadState } from "../../../../RoomNotifs"; import { humanReadableNotificationLevel } from "../../../../stores/notifications/NotificationLevel"; import { doesRoomOrThreadHaveUnreadMessages } from "../../../../Unread"; import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool"; +import SettingsStore from "../../../../settings/SettingsStore"; function UserReadUpTo({ target }: { target: ReadReceipt }): JSX.Element { const cli = useContext(MatrixClientContext); @@ -65,10 +66,12 @@ function UserReadUpTo({ target }: { target: ReadReceipt }): JSX.Elemen } export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Element { + const tacEnabled = useMemo(() => SettingsStore.getValue("threadsActivityCentre"), []); + const { room } = useContext(DevtoolsContext); const cli = useContext(MatrixClientContext); - const { level, count } = determineUnreadState(room); + const { level, count } = determineUnreadState(room, undefined, !tacEnabled); const [notificationState] = useNotificationState(room); return ( diff --git a/src/hooks/useUnreadNotifications.ts b/src/hooks/useUnreadNotifications.ts index fd4bab9edfe..e0a2f1eeff2 100644 --- a/src/hooks/useUnreadNotifications.ts +++ b/src/hooks/useUnreadNotifications.ts @@ -15,12 +15,13 @@ limitations under the License. */ import { RoomEvent } from "matrix-js-sdk/src/matrix"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import type { NotificationCount, Room } from "matrix-js-sdk/src/matrix"; import { determineUnreadState } from "../RoomNotifs"; import { NotificationLevel } from "../stores/notifications/NotificationLevel"; import { useEventEmitter } from "./useEventEmitter"; +import SettingsStore from "../settings/SettingsStore"; export const useUnreadNotifications = ( room?: Room, @@ -30,6 +31,8 @@ export const useUnreadNotifications = ( count: number; level: NotificationLevel; } => { + const tacEnabled = useMemo(() => SettingsStore.getValue("threadsActivityCentre"), []); + const [symbol, setSymbol] = useState(null); const [count, setCount] = useState(0); const [level, setLevel] = useState(NotificationLevel.None); @@ -50,11 +53,11 @@ export const useUnreadNotifications = ( useEventEmitter(room, RoomEvent.MyMembership, () => updateNotificationState()); const updateNotificationState = useCallback(() => { - const { symbol, count, level } = determineUnreadState(room, threadId); + const { symbol, count, level } = determineUnreadState(room, threadId, !tacEnabled); setSymbol(symbol); setCount(count); setLevel(level); - }, [room, threadId]); + }, [room, threadId, tacEnabled]); useEffect(() => { updateNotificationState(); diff --git a/src/stores/notifications/RoomNotificationState.ts b/src/stores/notifications/RoomNotificationState.ts index be5d5918342..0503485584d 100644 --- a/src/stores/notifications/RoomNotificationState.ts +++ b/src/stores/notifications/RoomNotificationState.ts @@ -25,7 +25,10 @@ import { NotificationState } from "./NotificationState"; import SettingsStore from "../../settings/SettingsStore"; export class RoomNotificationState extends NotificationState implements IDestroyable { - public constructor(public readonly room: Room) { + public constructor( + public readonly room: Room, + private includeThreads: boolean, + ) { super(); const cli = this.room.client; this.room.on(RoomEvent.Receipt, this.handleReadReceipt); @@ -90,7 +93,7 @@ export class RoomNotificationState extends NotificationState implements IDestroy private updateNotificationState(): void { const snapshot = this.snapshot(); - const { level, symbol, count } = RoomNotifs.determineUnreadState(this.room); + const { level, symbol, count } = RoomNotifs.determineUnreadState(this.room, undefined, this.includeThreads); const muted = RoomNotifs.getRoomNotifsState(this.room.client, this.room.roomId) === RoomNotifs.RoomNotifState.Mute; const knocked = SettingsStore.getValue("feature_ask_to_join") && this.room.getMyMembership() === "knock"; diff --git a/src/stores/notifications/RoomNotificationStateStore.ts b/src/stores/notifications/RoomNotificationStateStore.ts index 5c8161a3383..f2d10ac4fb1 100644 --- a/src/stores/notifications/RoomNotificationStateStore.ts +++ b/src/stores/notifications/RoomNotificationStateStore.ts @@ -42,6 +42,8 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient { private listMap = new Map(); private _globalState = new SummarizedNotificationState(); + private tacEnabled = SettingsStore.getValue("threadsActivityCentre"); + private constructor(dispatcher = defaultDispatcher) { super(dispatcher, {}); SettingsStore.watchSetting("feature_dynamic_room_predecessors", null, () => { @@ -97,7 +99,7 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient { */ public getRoomState(room: Room): RoomNotificationState { if (!this.roomMap.has(room)) { - this.roomMap.set(room, new RoomNotificationState(room)); + this.roomMap.set(room, new RoomNotificationState(room, !this.tacEnabled)); } return this.roomMap.get(room)!; } diff --git a/test/RoomNotifs-test.ts b/test/RoomNotifs-test.ts index f558ccc68ae..371ffea9104 100644 --- a/test/RoomNotifs-test.ts +++ b/test/RoomNotifs-test.ts @@ -109,16 +109,16 @@ describe("RoomNotifs test", () => { }); it("counts room notification type", () => { - expect(getUnreadNotificationCount(room, NotificationCountType.Total)).toBe(0); - expect(getUnreadNotificationCount(room, NotificationCountType.Highlight)).toBe(0); + expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(0); + expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(0); }); it("counts notifications type", () => { room.setUnreadNotificationCount(NotificationCountType.Total, 2); room.setUnreadNotificationCount(NotificationCountType.Highlight, 1); - expect(getUnreadNotificationCount(room, NotificationCountType.Total)).toBe(2); - expect(getUnreadNotificationCount(room, NotificationCountType.Highlight)).toBe(1); + expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(2); + expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(1); }); describe("when there is a room predecessor", () => { @@ -156,8 +156,8 @@ describe("RoomNotifs test", () => { it("and there is a predecessor in the create event, it should count predecessor highlight", () => { room.addLiveEvents([mkCreateEvent(OLD_ROOM_ID)]); - expect(getUnreadNotificationCount(room, NotificationCountType.Total)).toBe(8); - expect(getUnreadNotificationCount(room, NotificationCountType.Highlight)).toBe(7); + expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(8); + expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(7); }); }; @@ -167,8 +167,8 @@ describe("RoomNotifs test", () => { room.addLiveEvents([mkCreateEvent(OLD_ROOM_ID)]); upsertRoomStateEvents(room, [mkPredecessorEvent(OLD_ROOM_ID)]); - expect(getUnreadNotificationCount(room, NotificationCountType.Total)).toBe(8); - expect(getUnreadNotificationCount(room, NotificationCountType.Highlight)).toBe(7); + expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(8); + expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(7); }); }; @@ -195,8 +195,8 @@ describe("RoomNotifs test", () => { room.addLiveEvents([mkCreateEvent()]); upsertRoomStateEvents(room, [mkPredecessorEvent(OLD_ROOM_ID)]); - expect(getUnreadNotificationCount(room, NotificationCountType.Total)).toBe(2); - expect(getUnreadNotificationCount(room, NotificationCountType.Highlight)).toBe(1); + expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(2); + expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(1); }); }); @@ -214,31 +214,31 @@ describe("RoomNotifs test", () => { room.addLiveEvents([mkCreateEvent()]); upsertRoomStateEvents(room, [mkPredecessorEvent(OLD_ROOM_ID)]); - expect(getUnreadNotificationCount(room, NotificationCountType.Total)).toBe(8); - expect(getUnreadNotificationCount(room, NotificationCountType.Highlight)).toBe(7); + expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(8); + expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(7); }); it("and there is an unknown room in the predecessor event, it should not count predecessor highlight", () => { room.addLiveEvents([mkCreateEvent()]); upsertRoomStateEvents(room, [mkPredecessorEvent("!unknon:example.com")]); - expect(getUnreadNotificationCount(room, NotificationCountType.Total)).toBe(2); - expect(getUnreadNotificationCount(room, NotificationCountType.Highlight)).toBe(1); + expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(2); + expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(1); }); }); }); it("counts thread notification type", () => { - expect(getUnreadNotificationCount(room, NotificationCountType.Total, THREAD_ID)).toBe(0); - expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, THREAD_ID)).toBe(0); + expect(getUnreadNotificationCount(room, NotificationCountType.Total, false, THREAD_ID)).toBe(0); + expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false, THREAD_ID)).toBe(0); }); it("counts thread notifications type", () => { room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 2); room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 1); - expect(getUnreadNotificationCount(room, NotificationCountType.Total, THREAD_ID)).toBe(2); - expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, THREAD_ID)).toBe(1); + expect(getUnreadNotificationCount(room, NotificationCountType.Total, false, THREAD_ID)).toBe(2); + expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false, THREAD_ID)).toBe(1); }); }); diff --git a/test/components/views/rooms/RoomHeader/VideoRoomChatButton-test.tsx b/test/components/views/rooms/RoomHeader/VideoRoomChatButton-test.tsx index bc9eb10b560..7981a1b03dc 100644 --- a/test/components/views/rooms/RoomHeader/VideoRoomChatButton-test.tsx +++ b/test/components/views/rooms/RoomHeader/VideoRoomChatButton-test.tsx @@ -47,7 +47,7 @@ describe("", () => { }; const mockRoomNotificationState = (room: Room, level: NotificationLevel): RoomNotificationState => { - const roomNotificationState = new RoomNotificationState(room); + const roomNotificationState = new RoomNotificationState(room, false); // @ts-ignore ugly mocking roomNotificationState._level = level; diff --git a/test/stores/notifications/RoomNotificationState-test.ts b/test/stores/notifications/RoomNotificationState-test.ts index f556afb6dfc..ac7eabe4e1d 100644 --- a/test/stores/notifications/RoomNotificationState-test.ts +++ b/test/stores/notifications/RoomNotificationState-test.ts @@ -81,7 +81,7 @@ describe("RoomNotificationState", () => { } it("Updates on event decryption", () => { - const roomNotifState = new RoomNotificationState(room); + const roomNotifState = new RoomNotificationState(room, false); const listener = jest.fn(); roomNotifState.addListener(NotificationStateEvents.Update, listener); const testEvent = { @@ -93,12 +93,12 @@ describe("RoomNotificationState", () => { }); it("removes listeners", () => { - const roomNotifState = new RoomNotificationState(room); + const roomNotifState = new RoomNotificationState(room, false); expect(() => roomNotifState.destroy()).not.toThrow(); }); it("suggests an 'unread' ! if there are unsent messages", () => { - const roomNotifState = new RoomNotificationState(room); + const roomNotifState = new RoomNotificationState(room, false); const event = mkEvent({ event: true, @@ -115,7 +115,7 @@ describe("RoomNotificationState", () => { }); it("suggests nothing if the room is muted", () => { - const roomNotifState = new RoomNotificationState(room); + const roomNotifState = new RoomNotificationState(room, false); muteRoom(room); setUnreads(room, 1234, 0); @@ -127,7 +127,7 @@ describe("RoomNotificationState", () => { }); it("suggests a red ! if the user has been invited to a room", () => { - const roomNotifState = new RoomNotificationState(room); + const roomNotifState = new RoomNotificationState(room, false); room.updateMyMembership("invite"); // emit @@ -137,7 +137,7 @@ describe("RoomNotificationState", () => { }); it("returns a proper count and color for regular unreads", () => { - const roomNotifState = new RoomNotificationState(room); + const roomNotifState = new RoomNotificationState(room, false); setUnreads(room, 4321, 0); room.updateMyMembership("join"); // emit @@ -148,7 +148,7 @@ describe("RoomNotificationState", () => { }); it("returns a proper count and color for highlights", () => { - const roomNotifState = new RoomNotificationState(room); + const roomNotifState = new RoomNotificationState(room, false); setUnreads(room, 0, 69); room.updateMyMembership("join"); // emit @@ -159,7 +159,7 @@ describe("RoomNotificationState", () => { }); it("includes threads", async () => { - const roomNotifState = new RoomNotificationState(room); + const roomNotifState = new RoomNotificationState(room, false); room.timeline.push( new MatrixEvent({ From 67550c124829dc8852de84311bf428d073169c89 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Jan 2024 18:19:40 +0000 Subject: [PATCH 10/11] Pass includeThreads as setting to doesRoomHaveUnreadMessages too --- src/RoomNotifs.ts | 2 +- src/Unread.ts | 4 ++-- test/Unread-test.ts | 20 ++++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/RoomNotifs.ts b/src/RoomNotifs.ts index be3516e1d59..66be248f4f9 100644 --- a/src/RoomNotifs.ts +++ b/src/RoomNotifs.ts @@ -292,7 +292,7 @@ export function determineUnreadState( } // If the thread does not exist, assume it contains no unreads } else { - hasUnread = doesRoomHaveUnreadMessages(room); + hasUnread = doesRoomHaveUnreadMessages(room, includeThreads ?? false); } return { diff --git a/src/Unread.ts b/src/Unread.ts index daf6bc54551..3a177fedaec 100644 --- a/src/Unread.ts +++ b/src/Unread.ts @@ -50,7 +50,7 @@ export function eventTriggersUnreadCount(client: MatrixClient, ev: MatrixEvent): return haveRendererForEvent(ev, client, false /* hidden messages should never trigger unread counts anyways */); } -export function doesRoomHaveUnreadMessages(room: Room): boolean { +export function doesRoomHaveUnreadMessages(room: Room, includeThreads: boolean): boolean { if (SettingsStore.getValue("feature_sliding_sync")) { // TODO: https://github.com/vector-im/element-web/issues/23207 // Sliding Sync doesn't support unread indicator dots (yet...) @@ -58,7 +58,7 @@ export function doesRoomHaveUnreadMessages(room: Room): boolean { } const toCheck: Array = [room]; - if (!SettingsStore.getValue("threadsActivityCentre")) { + if (includeThreads) { toCheck.push(...room.getThreads()); } diff --git a/test/Unread-test.ts b/test/Unread-test.ts index 2ad0982a18e..843490b4256 100644 --- a/test/Unread-test.ts +++ b/test/Unread-test.ts @@ -152,7 +152,7 @@ describe("Unread", () => { }); it("returns true for a room with no receipts", () => { - expect(doesRoomHaveUnreadMessages(room)).toBe(true); + expect(doesRoomHaveUnreadMessages(room, false)).toBe(true); }); it("returns false for a room when the latest event was sent by the current user", () => { @@ -166,7 +166,7 @@ describe("Unread", () => { // Only for timeline events. room.addLiveEvents([event]); - expect(doesRoomHaveUnreadMessages(room)).toBe(false); + expect(doesRoomHaveUnreadMessages(room, false)).toBe(false); }); it("returns false for a room when the read receipt is at the latest event", () => { @@ -183,7 +183,7 @@ describe("Unread", () => { }); room.addReceipt(receipt); - expect(doesRoomHaveUnreadMessages(room)).toBe(false); + expect(doesRoomHaveUnreadMessages(room, false)).toBe(false); }); it("returns true for a room when the read receipt is earlier than the latest event", () => { @@ -210,7 +210,7 @@ describe("Unread", () => { // Only for timeline events. room.addLiveEvents([event2]); - expect(doesRoomHaveUnreadMessages(room)).toBe(true); + expect(doesRoomHaveUnreadMessages(room, false)).toBe(true); }); it("returns true for a room with an unread message in a thread", async () => { @@ -247,7 +247,7 @@ describe("Unread", () => { // Create a thread as a different user. await populateThread({ room, client, authorId: myId, participantUserIds: [aliceId] }); - expect(doesRoomHaveUnreadMessages(room)).toBe(true); + expect(doesRoomHaveUnreadMessages(room, true)).toBe(true); }); it("returns false for a room when the latest thread event was sent by the current user", async () => { @@ -268,7 +268,7 @@ describe("Unread", () => { // Create a thread as the current user. await populateThread({ room, client, authorId: myId, participantUserIds: [myId] }); - expect(doesRoomHaveUnreadMessages(room)).toBe(false); + expect(doesRoomHaveUnreadMessages(room, true)).toBe(false); }); it("returns false for a room with read thread messages", async () => { @@ -308,7 +308,7 @@ describe("Unread", () => { }); room.addReceipt(receipt); - expect(doesRoomHaveUnreadMessages(room)).toBe(false); + expect(doesRoomHaveUnreadMessages(room, true)).toBe(false); }); it("returns true for a room when read receipt is not on the latest thread messages", async () => { @@ -348,7 +348,7 @@ describe("Unread", () => { }); room.addReceipt(receipt); - expect(doesRoomHaveUnreadMessages(room)).toBe(true); + expect(doesRoomHaveUnreadMessages(room, true)).toBe(true); }); it("returns true when the event for a thread receipt can't be found", async () => { @@ -394,7 +394,7 @@ describe("Unread", () => { }); room.addReceipt(receipt); - expect(doesRoomHaveUnreadMessages(room)).toBe(true); + expect(doesRoomHaveUnreadMessages(room, true)).toBe(true); }); }); @@ -412,7 +412,7 @@ describe("Unread", () => { // Only for timeline events. room.addLiveEvents([redactedEvent]); - expect(doesRoomHaveUnreadMessages(room)).toBe(true); + expect(doesRoomHaveUnreadMessages(room, true)).toBe(true); expect(logger.warn).toHaveBeenCalledWith( "Falling back to unread room because of no read receipt or counting message found", { From c9eccdcd1684056cd8ae075c5062f4ddbe97a6e1 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Jan 2024 18:33:52 +0000 Subject: [PATCH 11/11] Fix tests --- test/stores/notifications/RoomNotificationState-test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/stores/notifications/RoomNotificationState-test.ts b/test/stores/notifications/RoomNotificationState-test.ts index ac7eabe4e1d..1e124d15272 100644 --- a/test/stores/notifications/RoomNotificationState-test.ts +++ b/test/stores/notifications/RoomNotificationState-test.ts @@ -81,7 +81,7 @@ describe("RoomNotificationState", () => { } it("Updates on event decryption", () => { - const roomNotifState = new RoomNotificationState(room, false); + const roomNotifState = new RoomNotificationState(room, true); const listener = jest.fn(); roomNotifState.addListener(NotificationStateEvents.Update, listener); const testEvent = { @@ -159,7 +159,7 @@ describe("RoomNotificationState", () => { }); it("includes threads", async () => { - const roomNotifState = new RoomNotificationState(room, false); + const roomNotifState = new RoomNotificationState(room, true); room.timeline.push( new MatrixEvent({