Unify unread notification state determination (#9941)

* Add tests for unread notification facilities

Add some tests to guarantee some consistency in `useUnreadNotifications` and
`RoomNotificationState`.

Signed-off-by: Clark Fischer <clark.fischer@gmail.com>

* Add RoomNotifs#determineUnreadState

Intended as a singular replacement for the divergent implementations before.

Signed-off-by: Clark Fischer <clark.fischer@gmail.com>

* Unify room unread state determination

Have both the class-based facility and the hook use the new unified logic in
`RoomNotifs#determineUnreadState`.

Addresses https://github.com/vector-im/element-web/issues/24229

Signed-off-by: Clark Fischer <clark.fischer@gmail.com>

---------

Signed-off-by: Clark Fischer <clark.fischer@gmail.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Clark Fischer 2023-01-31 09:58:17 +00:00 committed by GitHub
parent 53a9b6447b
commit 431afaafc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 499 additions and 231 deletions

View file

@ -1,5 +1,5 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2020, 2023 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.
@ -14,21 +14,19 @@ 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 { MatrixEventEvent } from "matrix-js-sdk/src/models/event";
import { RoomEvent } from "matrix-js-sdk/src/models/room";
import { ClientEvent } from "matrix-js-sdk/src/client";
import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
import { NotificationColor } from "./NotificationColor";
import { IDestroyable } from "../../utils/IDestroyable";
import type { Room } from "matrix-js-sdk/src/models/room";
import type { MatrixEvent } from "matrix-js-sdk/src/models/event";
import type { 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";
import type { ThreadsRoomNotificationState } from "./ThreadsRoomNotificationState";
export class RoomNotificationState extends NotificationState implements IDestroyable {
public constructor(public readonly room: Room, private readonly threadsState?: ThreadsRoomNotificationState) {
@ -49,10 +47,6 @@ export class RoomNotificationState extends NotificationState implements IDestroy
this.updateNotificationState();
}
private get roomIsInvite(): boolean {
return getEffectiveMembership(this.room.getMyMembership()) === EffectiveMembership.Invite;
}
public destroy(): void {
super.destroy();
const cli = this.room.client;
@ -112,58 +106,10 @@ export class RoomNotificationState extends NotificationState implements IDestroy
private updateNotificationState(): void {
const snapshot = this.snapshot();
if (getUnsentMessages(this.room).length > 0) {
// When there are unsent messages we show a red `!`
this._color = NotificationColor.Unsent;
this._symbol = "!";
this._count = 1; // not used, technically
} else if (
RoomNotifs.getRoomNotifsState(this.room.client, this.room.roomId) === RoomNotifs.RoomNotifState.Mute
) {
// When muted we suppress all notification states, even if we have context on them.
this._color = NotificationColor.None;
this._symbol = null;
this._count = 0;
} else if (this.roomIsInvite) {
this._color = NotificationColor.Red;
this._symbol = "!";
this._count = 1; // not used, technically
} else {
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 {
// We don't have any notified messages, but we might have unread messages. Let's
// find out.
const hasUnread = Unread.doesRoomHaveUnreadMessages(this.room);
if (hasUnread) {
this._color = NotificationColor.Bold;
} else {
this._color = NotificationColor.None;
}
// no symbol or count for this state
this._count = 0;
this._symbol = null;
}
}
const { color, symbol, count } = RoomNotifs.determineUnreadState(this.room);
this._color = color;
this._symbol = symbol;
this._count = count;
// finally, publish an update if needed
this.emitIfUpdated(snapshot);