From 252c7171e50353ed09d0ec8a8ebcca1a76402e09 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Sun, 18 Sep 2022 20:20:30 +0200 Subject: [PATCH 1/2] Add way to detect notification state type depending on server support --- .../RoomNotificationStateStore.ts | 12 ++- .../ThreadBetaNotificationState.ts | 78 +++++++++++++++++ .../notifications/ThreadNotificationState.ts | 8 +- .../ThreadsBetaRoomNotificationState.ts | 84 +++++++++++++++++++ .../ThreadsRoomNotificationState.ts | 6 +- 5 files changed, 179 insertions(+), 9 deletions(-) create mode 100644 src/stores/notifications/ThreadBetaNotificationState.ts create mode 100644 src/stores/notifications/ThreadsBetaRoomNotificationState.ts diff --git a/src/stores/notifications/RoomNotificationStateStore.ts b/src/stores/notifications/RoomNotificationStateStore.ts index 48aa7e7c20f..fff56160519 100644 --- a/src/stores/notifications/RoomNotificationStateStore.ts +++ b/src/stores/notifications/RoomNotificationStateStore.ts @@ -44,9 +44,17 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient { private roomThreadsMap = new Map(); private listMap = new Map(); private _globalState = new SummarizedNotificationState(); + private ThreadNotificationStateType; private constructor() { super(defaultDispatcher, {}); + this.matrixClient.serverSupportsPrefix("org.matrix.msc3773").then(({ serverSupport }) => { + if (serverSupport) { + this.ThreadNotificationStateType = ThreadsRoomNotificationState; + } else { + this.ThreadNotificationStateType = ThreadsRoomNotificationState; + } + }); } /** @@ -90,7 +98,7 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient { // threads notification at the same time at rooms. // There are multiple entry points, and it's unclear which one gets // called first - const threadState = new ThreadsRoomNotificationState(room); + const threadState = new this.ThreadNotificationStateType(room); this.roomThreadsMap.set(room, threadState); this.roomMap.set(room, new RoomNotificationState(room, threadState)); } @@ -99,7 +107,7 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient { public getThreadsRoomState(room: Room): ThreadsRoomNotificationState { if (!this.roomThreadsMap.has(room)) { - this.roomThreadsMap.set(room, new ThreadsRoomNotificationState(room)); + this.roomThreadsMap.set(room, new this.ThreadNotificationStateType(room)); } return this.roomThreadsMap.get(room); } diff --git a/src/stores/notifications/ThreadBetaNotificationState.ts b/src/stores/notifications/ThreadBetaNotificationState.ts new file mode 100644 index 00000000000..f54a39ed353 --- /dev/null +++ b/src/stores/notifications/ThreadBetaNotificationState.ts @@ -0,0 +1,78 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { Thread, ThreadEvent } from "matrix-js-sdk/src/models/thread"; + +import { NotificationColor } from "./NotificationColor"; +import { MatrixClientPeg } from "../../MatrixClientPeg"; +import { ThreadNotificationState } from "./ThreadNotificationState"; + +export class ThreadBetaNotificationState extends ThreadNotificationState { + protected _symbol = null; + protected _count = 0; + protected _color = NotificationColor.None; + + constructor(public readonly thread: Thread) { + super(thread); + this.thread.on(ThreadEvent.NewReply, this.handleNewThreadReply); + this.thread.on(ThreadEvent.ViewThread, this.resetThreadNotification); + if (this.thread.replyToEvent) { + // Process the current tip event + this.handleNewThreadReply(this.thread, this.thread.replyToEvent); + } + } + + public destroy(): void { + super.destroy(); + this.thread.off(ThreadEvent.NewReply, this.handleNewThreadReply); + this.thread.off(ThreadEvent.ViewThread, this.resetThreadNotification); + } + + protected handleNewThreadReply = (thread: Thread, event: MatrixEvent) => { + const client = MatrixClientPeg.get(); + + const myUserId = client.getUserId(); + + const isOwn = myUserId === event.getSender(); + const readReceipt = this.thread.room.getReadReceiptForUserId(myUserId); + + if (!isOwn && !readReceipt || (readReceipt && event.getTs() >= readReceipt.data.ts)) { + const actions = client.getPushActionsForEvent(event, true); + + if (actions?.tweaks) { + const color = !!actions.tweaks.highlight + ? NotificationColor.Red + : NotificationColor.Grey; + + this.updateNotificationState(color); + } + } + }; + + protected resetThreadNotification = (): void => { + this.updateNotificationState(NotificationColor.None); + }; + + protected updateNotificationState(color: NotificationColor) { + const snapshot = this.snapshot(); + + this._color = color; + + // finally, publish an update if needed + this.emitIfUpdated(snapshot); + } +} diff --git a/src/stores/notifications/ThreadNotificationState.ts b/src/stores/notifications/ThreadNotificationState.ts index 2b2bcf175ce..3e2064f64c2 100644 --- a/src/stores/notifications/ThreadNotificationState.ts +++ b/src/stores/notifications/ThreadNotificationState.ts @@ -1,5 +1,5 @@ /* -Copyright 2021 The Matrix.org Foundation C.I.C. +Copyright 2021 - 2022 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ export class ThreadNotificationState extends NotificationState implements IDestr this.thread.off(ThreadEvent.ViewThread, this.resetThreadNotification); } - private handleNewThreadReply = (thread: Thread, event: MatrixEvent) => { + protected handleNewThreadReply = (thread: Thread, event: MatrixEvent) => { const client = MatrixClientPeg.get(); const myUserId = client.getUserId(); @@ -64,11 +64,11 @@ export class ThreadNotificationState extends NotificationState implements IDestr } }; - private resetThreadNotification = (): void => { + protected resetThreadNotification = (): void => { this.updateNotificationState(NotificationColor.None); }; - private updateNotificationState(color: NotificationColor) { + protected updateNotificationState(color: NotificationColor) { const snapshot = this.snapshot(); this._color = color; diff --git a/src/stores/notifications/ThreadsBetaRoomNotificationState.ts b/src/stores/notifications/ThreadsBetaRoomNotificationState.ts new file mode 100644 index 00000000000..26c18e73f75 --- /dev/null +++ b/src/stores/notifications/ThreadsBetaRoomNotificationState.ts @@ -0,0 +1,84 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { Room } from "matrix-js-sdk/src/models/room"; +import { Thread, ThreadEvent } from "matrix-js-sdk/src/models/thread"; + +import { NotificationStateEvents } from "./NotificationState"; +import { ThreadNotificationState } from "./ThreadNotificationState"; +import { NotificationColor } from "./NotificationColor"; +import { ThreadsRoomNotificationState } from "./ThreadsRoomNotificationState"; +import { ThreadBetaNotificationState } from "./ThreadBetaNotificationState"; + +export class ThreadsBetaRoomNotificationState extends ThreadsRoomNotificationState { + public readonly threadsState = new Map(); + + protected _symbol = null; + protected _count = 0; + protected _color = NotificationColor.None; + + constructor(public readonly room: Room) { + super(room); + for (const thread of this.room.getThreads()) { + this.onNewThread(thread); + } + this.room.on(ThreadEvent.New, this.onNewThread); + } + + public destroy(): void { + super.destroy(); + this.room.off(ThreadEvent.New, this.onNewThread); + for (const [, notificationState] of this.threadsState) { + notificationState.off(NotificationStateEvents.Update, this.onThreadUpdate); + } + } + + public getThreadRoomState(thread: Thread): ThreadNotificationState { + if (!this.threadsState.has(thread)) { + this.threadsState.set(thread, new ThreadBetaNotificationState(thread)); + } + return this.threadsState.get(thread); + } + + protected onNewThread = (thread: Thread): void => { + const notificationState = new ThreadBetaNotificationState(thread); + this.threadsState.set( + thread, + notificationState, + ); + notificationState.on(NotificationStateEvents.Update, this.onThreadUpdate); + }; + + protected onThreadUpdate = (): void => { + let color = NotificationColor.None; + for (const [, notificationState] of this.threadsState) { + if (notificationState.color === NotificationColor.Red) { + color = NotificationColor.Red; + break; + } else if (notificationState.color === NotificationColor.Grey) { + color = NotificationColor.Grey; + } + } + this.updateNotificationState(color); + }; + + protected updateNotificationState(color: NotificationColor): void { + const snapshot = this.snapshot(); + this._color = color; + // finally, publish an update if needed + this.emitIfUpdated(snapshot); + } +} diff --git a/src/stores/notifications/ThreadsRoomNotificationState.ts b/src/stores/notifications/ThreadsRoomNotificationState.ts index e0ec810cec4..c5bc2478431 100644 --- a/src/stores/notifications/ThreadsRoomNotificationState.ts +++ b/src/stores/notifications/ThreadsRoomNotificationState.ts @@ -52,7 +52,7 @@ export class ThreadsRoomNotificationState extends NotificationState implements I return this.threadsState.get(thread); } - private onNewThread = (thread: Thread): void => { + protected onNewThread = (thread: Thread): void => { const notificationState = new ThreadNotificationState(thread); this.threadsState.set( thread, @@ -61,7 +61,7 @@ export class ThreadsRoomNotificationState extends NotificationState implements I notificationState.on(NotificationStateEvents.Update, this.onThreadUpdate); }; - private onThreadUpdate = (): void => { + protected onThreadUpdate = (): void => { let color = NotificationColor.None; for (const [, notificationState] of this.threadsState) { if (notificationState.color === NotificationColor.Red) { @@ -74,7 +74,7 @@ export class ThreadsRoomNotificationState extends NotificationState implements I this.updateNotificationState(color); }; - private updateNotificationState(color: NotificationColor): void { + protected updateNotificationState(color: NotificationColor): void { const snapshot = this.snapshot(); this._color = color; // finally, publish an update if needed From fea83185329b69108ef6d23fb483ede42818b9fa Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Sun, 18 Sep 2022 21:37:54 +0200 Subject: [PATCH 2/2] Move RoomNotificationState logic to parent class --- .../notifications/RoomNotificationState.ts | 97 ++++--------------- .../notifications/ThreadNotificationState.ts | 57 ++++------- .../TimelineNotificationState.ts | 96 ++++++++++++++++++ 3 files changed, 134 insertions(+), 116 deletions(-) create mode 100644 src/stores/notifications/TimelineNotificationState.ts diff --git a/src/stores/notifications/RoomNotificationState.ts b/src/stores/notifications/RoomNotificationState.ts index c4c803483df..510091d9be8 100644 --- a/src/stores/notifications/RoomNotificationState.ts +++ b/src/stores/notifications/RoomNotificationState.ts @@ -14,34 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room"; -import { ClientEvent } from "matrix-js-sdk/src/client"; import { NotificationColor } from "./NotificationColor"; -import { IDestroyable } from "../../utils/IDestroyable"; -import { MatrixClientPeg } from "../../MatrixClientPeg"; import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership"; -import { readReceiptChangeIsFor } from "../../utils/read-receipts"; import * as RoomNotifs from '../../RoomNotifs'; import * as Unread from '../../Unread'; -import { NotificationState, NotificationStateEvents } from "./NotificationState"; import { getUnsentMessages } from "../../components/structures/RoomStatusBar"; -import { ThreadsRoomNotificationState } from "./ThreadsRoomNotificationState"; - -export class RoomNotificationState extends NotificationState implements IDestroyable { - constructor(public readonly room: Room, private readonly threadsState?: ThreadsRoomNotificationState) { - super(); - this.room.on(RoomEvent.Receipt, this.handleReadReceipt); - this.room.on(RoomEvent.Timeline, this.handleRoomEventUpdate); - this.room.on(RoomEvent.Redaction, this.handleRoomEventUpdate); +import { TimelineNotificationState } from "./TimelineNotificationState"; + +export class RoomNotificationState extends TimelineNotificationState { + constructor(public readonly room: Room) { + super(room); this.room.on(RoomEvent.MyMembership, this.handleMembershipUpdate); - this.room.on(RoomEvent.LocalEchoUpdated, this.handleLocalEchoUpdated); - if (threadsState) { - threadsState.on(NotificationStateEvents.Update, this.handleThreadsUpdate); - } - MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.onEventDecrypted); - MatrixClientPeg.get().on(ClientEvent.AccountData, this.handleAccountDataUpdate); this.updateNotificationState(); } @@ -51,57 +37,31 @@ export class RoomNotificationState extends NotificationState implements IDestroy public destroy(): void { super.destroy(); - this.room.removeListener(RoomEvent.Receipt, this.handleReadReceipt); - this.room.removeListener(RoomEvent.Timeline, this.handleRoomEventUpdate); - this.room.removeListener(RoomEvent.Redaction, this.handleRoomEventUpdate); this.room.removeListener(RoomEvent.MyMembership, this.handleMembershipUpdate); - this.room.removeListener(RoomEvent.LocalEchoUpdated, this.handleLocalEchoUpdated); - if (this.threadsState) { - this.threadsState.removeListener(NotificationStateEvents.Update, this.handleThreadsUpdate); - } - if (MatrixClientPeg.get()) { - MatrixClientPeg.get().removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted); - MatrixClientPeg.get().removeListener(ClientEvent.AccountData, this.handleAccountDataUpdate); - } } - private handleThreadsUpdate = () => { - this.updateNotificationState(); - }; - - private handleLocalEchoUpdated = () => { - this.updateNotificationState(); - }; - - private handleReadReceipt = (event: MatrixEvent, room: Room) => { - if (!readReceiptChangeIsFor(event, MatrixClientPeg.get())) return; // not our own - ignore - if (room.roomId !== this.room.roomId) return; // not for us - ignore - this.updateNotificationState(); + protected handleReadReceipt = (event: MatrixEvent): void => { + if (event.getRoomId() !== this.room.roomId) return; // not for us - ignore + super.handleReadReceipt(event); }; - private handleMembershipUpdate = () => { - this.updateNotificationState(); + private handleMembershipUpdate = (): void => { + return this.updateNotificationState(); }; - private onEventDecrypted = (event: MatrixEvent) => { + protected onEventDecrypted = (event: MatrixEvent): void => { if (event.getRoomId() !== this.room.roomId) return; // ignore - not for us or notifications timeline + if (event.threadRootId && !event.isThreadRoot) return; // ignore all threaded events - this.updateNotificationState(); - }; - - private handleRoomEventUpdate = (event: MatrixEvent, room: Room | null) => { - if (room?.roomId !== this.room.roomId) return; // ignore - not for us or notifications timeline - - this.updateNotificationState(); + super.onEventDecrypted(event); }; - private handleAccountDataUpdate = (ev: MatrixEvent) => { - if (ev.getType() === "m.push_rules") { - this.updateNotificationState(); - } + protected handleRoomEventUpdate = (event: MatrixEvent): void => { + if (event.getRoomId() !== this.room.roomId) return; // ignore - not for us or notifications timeline + super.handleRoomEventUpdate(event); }; - private updateNotificationState() { + protected updateNotificationState(): void { const snapshot = this.snapshot(); if (getUnsentMessages(this.room).length > 0) { @@ -119,27 +79,12 @@ export class RoomNotificationState extends NotificationState implements IDestroy this._symbol = "!"; this._count = 1; // not used, technically } else { + super.updateNotificationState(); + const redNotifs = RoomNotifs.getUnreadNotificationCount(this.room, NotificationCountType.Highlight); const greyNotifs = RoomNotifs.getUnreadNotificationCount(this.room, NotificationCountType.Total); - // For a 'true count' we pick the grey notifications first because they include the - // red notifications. If we don't have a grey count for some reason we use the red - // count. If that count is broken for some reason, assume zero. This avoids us showing - // a badge for 'NaN' (which formats as 'NaNB' for NaN Billion). - const trueCount = greyNotifs ? greyNotifs : (redNotifs ? redNotifs : 0); - - // Note: we only set the symbol if we have an actual count. We don't want to show - // zero on badges. - - if (redNotifs > 0) { - this._color = NotificationColor.Red; - this._count = trueCount; - this._symbol = null; // symbol calculated by component - } else if (greyNotifs > 0) { - this._color = NotificationColor.Grey; - this._count = trueCount; - this._symbol = null; // symbol calculated by component - } else { + if (redNotifs === 0 && greyNotifs === 0) { // We don't have any notified messages, but we might have unread messages. Let's // find out. const hasUnread = Unread.doesRoomHaveUnreadMessages(this.room); diff --git a/src/stores/notifications/ThreadNotificationState.ts b/src/stores/notifications/ThreadNotificationState.ts index 3e2064f64c2..ba40ab824c4 100644 --- a/src/stores/notifications/ThreadNotificationState.ts +++ b/src/stores/notifications/ThreadNotificationState.ts @@ -15,63 +15,40 @@ limitations under the License. */ import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { Thread, ThreadEvent } from "matrix-js-sdk/src/models/thread"; +import { Room } from "matrix-js-sdk/src/models/room"; import { NotificationColor } from "./NotificationColor"; -import { IDestroyable } from "../../utils/IDestroyable"; -import { MatrixClientPeg } from "../../MatrixClientPeg"; -import { NotificationState } from "./NotificationState"; +import { TimelineNotificationState } from "./TimelineNotificationState"; -export class ThreadNotificationState extends NotificationState implements IDestroyable { +export class ThreadNotificationState extends TimelineNotificationState { protected _symbol = null; protected _count = 0; protected _color = NotificationColor.None; - constructor(public readonly thread: Thread) { - super(); - this.thread.on(ThreadEvent.NewReply, this.handleNewThreadReply); - this.thread.on(ThreadEvent.ViewThread, this.resetThreadNotification); - if (this.thread.replyToEvent) { - // Process the current tip event - this.handleNewThreadReply(this.thread, this.thread.replyToEvent); - } + constructor(public readonly room: Room, public readonly threadId: string) { + super(room); } - public destroy(): void { - super.destroy(); - this.thread.off(ThreadEvent.NewReply, this.handleNewThreadReply); - this.thread.off(ThreadEvent.ViewThread, this.resetThreadNotification); - } - - protected handleNewThreadReply = (thread: Thread, event: MatrixEvent) => { - const client = MatrixClientPeg.get(); - - const myUserId = client.getUserId(); - - const isOwn = myUserId === event.getSender(); - const readReceipt = this.thread.room.getReadReceiptForUserId(myUserId); - - if (!isOwn && !readReceipt || (readReceipt && event.getTs() >= readReceipt.data.ts)) { - const actions = client.getPushActionsForEvent(event, true); + protected handleReadReceipt = (event: MatrixEvent): void => { + if (event.threadRootId !== this.threadId) return; // not for us - ignore + super.handleReadReceipt(event); + }; - if (actions?.tweaks) { - const color = !!actions.tweaks.highlight - ? NotificationColor.Red - : NotificationColor.Grey; + protected onEventDecrypted = (event: MatrixEvent): void => { + if (event.threadRootId !== this.threadId) return; // ignore - not for us or notifications timeline - this.updateNotificationState(color); - } - } + super.onEventDecrypted(event); }; - protected resetThreadNotification = (): void => { - this.updateNotificationState(NotificationColor.None); + protected handleRoomEventUpdate = (event: MatrixEvent): void => { + if (event.threadRootId !== this.threadId) return; // ignore - not for us or notifications timeline + super.handleRoomEventUpdate(event); }; - protected updateNotificationState(color: NotificationColor) { + protected updateNotificationState(): void { const snapshot = this.snapshot(); - this._color = color; + super.updateNotificationState(); // finally, publish an update if needed this.emitIfUpdated(snapshot); diff --git a/src/stores/notifications/TimelineNotificationState.ts b/src/stores/notifications/TimelineNotificationState.ts new file mode 100644 index 00000000000..f722e9e34d7 --- /dev/null +++ b/src/stores/notifications/TimelineNotificationState.ts @@ -0,0 +1,96 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event"; +import { NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room"; +import { ClientEvent } from "matrix-js-sdk/src/client"; + +import { NotificationColor } from "./NotificationColor"; +import { IDestroyable } from "../../utils/IDestroyable"; +import { MatrixClientPeg } from "../../MatrixClientPeg"; +import { readReceiptChangeIsFor } from "../../utils/read-receipts"; +import * as RoomNotifs from '../../RoomNotifs'; +import { NotificationState } from "./NotificationState"; + +export class TimelineNotificationState extends NotificationState implements IDestroyable { + constructor(public readonly room: Room) { + super(); + this.room.on(RoomEvent.Receipt, this.handleReadReceipt); + this.room.on(RoomEvent.Timeline, this.handleRoomEventUpdate); + this.room.on(RoomEvent.Redaction, this.handleRoomEventUpdate); + this.room.on(RoomEvent.LocalEchoUpdated, this.handleLocalEchoUpdated); + MatrixClientPeg.get()?.on(MatrixEventEvent.Decrypted, this.onEventDecrypted); + MatrixClientPeg.get()?.on(ClientEvent.AccountData, this.handleAccountDataUpdate); + this.updateNotificationState(); + } + + public destroy(): void { + super.destroy(); + this.room.removeListener(RoomEvent.Receipt, this.handleReadReceipt); + this.room.removeListener(RoomEvent.Timeline, this.handleRoomEventUpdate); + this.room.removeListener(RoomEvent.Redaction, this.handleRoomEventUpdate); + this.room.removeListener(RoomEvent.LocalEchoUpdated, this.handleLocalEchoUpdated); + MatrixClientPeg.get()?.removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted); + MatrixClientPeg.get()?.removeListener(ClientEvent.AccountData, this.handleAccountDataUpdate); + } + + protected handleLocalEchoUpdated = (): void => { + this.updateNotificationState(); + }; + + protected handleReadReceipt = (event: MatrixEvent): void => { + if (!readReceiptChangeIsFor(event, MatrixClientPeg.get())) return; // not our own - ignore + this.updateNotificationState(); + }; f; + + protected onEventDecrypted = (event: MatrixEvent): void => { + this.updateNotificationState(); + }; + + protected handleRoomEventUpdate = (event: MatrixEvent): void => { + this.updateNotificationState(); + }; + + protected handleAccountDataUpdate = (ev: MatrixEvent): void => { + if (ev.getType() === "m.push_rules") { + this.updateNotificationState(); + } + }; + + protected updateNotificationState(): void { + const redNotifs = RoomNotifs.getUnreadNotificationCount(this.room, NotificationCountType.Highlight); + const greyNotifs = RoomNotifs.getUnreadNotificationCount(this.room, NotificationCountType.Total); + + // For a 'true count' we pick the grey notifications first because they include the + // red notifications. If we don't have a grey count for some reason we use the red + // count. If that count is broken for some reason, assume zero. This avoids us showing + // a badge for 'NaN' (which formats as 'NaNB' for NaN Billion). + const trueCount = greyNotifs ? greyNotifs : (redNotifs ? redNotifs : 0); + + // Note: we only set the symbol if we have an actual count. We don't want to show + // zero on badges. + + if (redNotifs > 0) { + this._color = NotificationColor.Red; + this._count = trueCount; + this._symbol = null; // symbol calculated by component + } else if (greyNotifs > 0) { + this._color = NotificationColor.Grey; + this._count = trueCount; + this._symbol = null; // symbol calculated by component + } + } +}