From 329bc8a89e409e342364ff2bc9f3dc52797b7732 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 9 Sep 2021 13:14:05 +0100
Subject: [PATCH] Move unsent event badge handling into RoomNotificationState

---
 src/components/views/rooms/RoomTile.tsx       | 49 ++++---------------
 .../notifications/RoomNotificationState.ts    | 14 +++++-
 2 files changed, 23 insertions(+), 40 deletions(-)

diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx
index 4d6de10e1f..8ed2ca85e9 100644
--- a/src/components/views/rooms/RoomTile.tsx
+++ b/src/components/views/rooms/RoomTile.tsx
@@ -17,7 +17,6 @@ limitations under the License.
 
 import React, { createRef } from "react";
 import { Room } from "matrix-js-sdk/src/models/room";
-import { MatrixEvent } from "matrix-js-sdk/src/models/event";
 import classNames from "classnames";
 import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
 import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
@@ -51,8 +50,6 @@ import IconizedContextMenu, {
 } from "../context_menus/IconizedContextMenu";
 import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/CommunityPrototypeStore";
 import { replaceableComponent } from "../../../utils/replaceableComponent";
-import { getUnsentMessages } from "../../structures/RoomStatusBar";
-import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
 
 interface IProps {
     room: Room;
@@ -68,7 +65,6 @@ interface IState {
     notificationsMenuPosition: PartialDOMRect;
     generalMenuPosition: PartialDOMRect;
     messagePreview?: string;
-    hasUnsentEvents: boolean;
 }
 
 const messagePreviewId = (roomId: string) => `mx_RoomTile_messagePreview_${roomId}`;
@@ -95,7 +91,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
             selected: ActiveRoomObserver.activeRoomId === this.props.room.roomId,
             notificationsMenuPosition: null,
             generalMenuPosition: null,
-            hasUnsentEvents: this.countUnsentEvents() > 0,
 
             // generatePreview() will return nothing if the user has previews disabled
             messagePreview: "",
@@ -106,10 +101,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
         this.roomProps = EchoChamber.forRoom(this.props.room);
     }
 
-    private countUnsentEvents(): number {
-        return getUnsentMessages(this.props.room).length;
-    }
-
     private onRoomNameUpdate = (room) => {
         this.forceUpdate();
     };
@@ -118,11 +109,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
         this.forceUpdate(); // notification state changed - update
     };
 
-    private onLocalEchoUpdated = (ev: MatrixEvent, room: Room) => {
-        if (room?.roomId !== this.props.room.roomId) return;
-        this.setState({ hasUnsentEvents: this.countUnsentEvents() > 0 });
-    };
-
     private onRoomPropertyUpdate = (property: CachedRoomKey) => {
         if (property === CachedRoomKey.NotificationVolume) this.onNotificationUpdate();
         // else ignore - not important for this tile
@@ -183,7 +169,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
             CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId),
             this.onCommunityUpdate,
         );
-        MatrixClientPeg.get().on("Room.localEchoUpdated", this.onLocalEchoUpdated);
     }
 
     public componentWillUnmount() {
@@ -208,7 +193,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
             CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId),
             this.onCommunityUpdate,
         );
-        MatrixClientPeg.get()?.removeListener("Room.localEchoUpdated", this.onLocalEchoUpdated);
     }
 
     private onAction = (payload: ActionPayload) => {
@@ -587,30 +571,17 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
         />;
 
         let badge: React.ReactNode;
-        if (!this.props.isMinimized) {
+        if (!this.props.isMinimized && this.notificationState) {
             // aria-hidden because we summarise the unread count/highlight status in a manual aria-label below
-            if (this.state.hasUnsentEvents) {
-                // hardcode the badge to a danger state when there's unsent messages
-                badge = (
-                    <div className="mx_RoomTile_badgeContainer" aria-hidden="true">
-                        <NotificationBadge
-                            notification={StaticNotificationState.RED_EXCLAMATION}
-                            forceCount={false}
-                            roomId={this.props.room.roomId}
-                        />
-                    </div>
-                );
-            } else if (this.notificationState) {
-                badge = (
-                    <div className="mx_RoomTile_badgeContainer" aria-hidden="true">
-                        <NotificationBadge
-                            notification={this.notificationState}
-                            forceCount={false}
-                            roomId={this.props.room.roomId}
-                        />
-                    </div>
-                );
-            }
+            badge = (
+                <div className="mx_RoomTile_badgeContainer" aria-hidden="true">
+                    <NotificationBadge
+                        notification={this.notificationState}
+                        forceCount={false}
+                        roomId={this.props.room.roomId}
+                    />
+                </div>
+            );
         }
 
         let messagePreview = null;
diff --git a/src/stores/notifications/RoomNotificationState.ts b/src/stores/notifications/RoomNotificationState.ts
index 3fadbe7d7a..0a2e801620 100644
--- a/src/stores/notifications/RoomNotificationState.ts
+++ b/src/stores/notifications/RoomNotificationState.ts
@@ -24,6 +24,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
 import * as RoomNotifs from '../../RoomNotifs';
 import * as Unread from '../../Unread';
 import { NotificationState } from "./NotificationState";
+import { getUnsentMessages } from "../../components/structures/RoomStatusBar";
 
 export class RoomNotificationState extends NotificationState implements IDestroyable {
     constructor(public readonly room: Room) {
@@ -32,6 +33,7 @@ export class RoomNotificationState extends NotificationState implements IDestroy
         this.room.on("Room.timeline", this.handleRoomEventUpdate);
         this.room.on("Room.redaction", this.handleRoomEventUpdate);
         this.room.on("Room.myMembership", this.handleMembershipUpdate);
+        this.room.on("Room.localEchoUpdated", this.handleLocalEchoUpdated);
         MatrixClientPeg.get().on("Event.decrypted", this.handleRoomEventUpdate);
         MatrixClientPeg.get().on("accountData", this.handleAccountDataUpdate);
         this.updateNotificationState();
@@ -47,12 +49,17 @@ export class RoomNotificationState extends NotificationState implements IDestroy
         this.room.removeListener("Room.timeline", this.handleRoomEventUpdate);
         this.room.removeListener("Room.redaction", this.handleRoomEventUpdate);
         this.room.removeListener("Room.myMembership", this.handleMembershipUpdate);
+        this.room.removeListener("Room.localEchoUpdated", this.handleLocalEchoUpdated);
         if (MatrixClientPeg.get()) {
             MatrixClientPeg.get().removeListener("Event.decrypted", this.handleRoomEventUpdate);
             MatrixClientPeg.get().removeListener("accountData", this.handleAccountDataUpdate);
         }
     }
 
+    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
@@ -79,7 +86,12 @@ export class RoomNotificationState extends NotificationState implements IDestroy
     private updateNotificationState() {
         const snapshot = this.snapshot();
 
-        if (RoomNotifs.getRoomNotifsState(this.room.roomId) === RoomNotifs.MUTE) {
+        if (getUnsentMessages(this.room).length > 0) {
+            // When there are unsent messages we show a red `!`
+            this._color = NotificationColor.Red;
+            this._symbol = "!";
+            this._count = 1; // not used, technically
+        } else if (RoomNotifs.getRoomNotifsState(this.room.roomId) === RoomNotifs.MUTE) {
             // When muted we suppress all notification states, even if we have context on them.
             this._color = NotificationColor.None;
             this._symbol = null;