Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 44e0732

Browse files
author
Kerry
authored
Sort muted rooms to the bottom of their section of the room list (#10592)
* muted-to-the-bottom POC * split muted rooms in natural algorithm * add previous event to account data dispatch * add muted to notification state * sort muted rooms to the bottom * only split muted rooms when sorting is RECENT * remove debugs * use RoomNotifState better * add default notifications test util * test getChangedOverrideRoomPushRules * remove file * test roomudpate in roomliststore * unit test ImportanceAlgorithm * strict fixes * test recent x importance with muted rooms * unit test NaturalAlgorithm * test naturalalgorithm with muted rooms * strict fixes * comments * add push rules test utility * strict fixes * more strict * tidy comment * document previousevent on account data dispatch event * simplify (?) room mute rule utilities, comments * remove debug
1 parent 3ca957b commit 44e0732

File tree

15 files changed

+765
-27
lines changed

15 files changed

+765
-27
lines changed

src/RoomNotifs.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,19 +182,44 @@ function findOverrideMuteRule(roomId: string): IPushRule | null {
182182
return null;
183183
}
184184
for (const rule of cli.pushRules.global.override) {
185-
if (rule.enabled && isRuleForRoom(roomId, rule) && isMuteRule(rule)) {
185+
if (rule.enabled && isRuleRoomMuteRuleForRoomId(roomId, rule)) {
186186
return rule;
187187
}
188188
}
189189
return null;
190190
}
191191

192-
function isRuleForRoom(roomId: string, rule: IPushRule): boolean {
193-
if (rule.conditions?.length !== 1) {
192+
/**
193+
* Checks if a given rule is a room mute rule as implemented by EW
194+
* - matches every event in one room (one condition that is an event match on roomId)
195+
* - silences notifications (one action that is `DontNotify`)
196+
* @param rule - push rule
197+
* @returns {boolean} - true when rule mutes a room
198+
*/
199+
export function isRuleMaybeRoomMuteRule(rule: IPushRule): boolean {
200+
return (
201+
// matches every event in one room
202+
rule.conditions?.length === 1 &&
203+
rule.conditions[0].kind === ConditionKind.EventMatch &&
204+
rule.conditions[0].key === "room_id" &&
205+
// silences notifications
206+
isMuteRule(rule)
207+
);
208+
}
209+
210+
/**
211+
* Checks if a given rule is a room mute rule as implemented by EW
212+
* @param roomId - id of room to match
213+
* @param rule - push rule
214+
* @returns {boolean} true when rule mutes the given room
215+
*/
216+
function isRuleRoomMuteRuleForRoomId(roomId: string, rule: IPushRule): boolean {
217+
if (!isRuleMaybeRoomMuteRule(rule)) {
194218
return false;
195219
}
196-
const cond = rule.conditions[0];
197-
return cond.kind === ConditionKind.EventMatch && cond.key === "room_id" && cond.pattern === roomId;
220+
// isRuleMaybeRoomMuteRule checks this condition exists
221+
const cond = rule.conditions![0]!;
222+
return cond.pattern === roomId;
198223
}
199224

200225
function isMuteRule(rule: IPushRule): boolean {

src/actions/MatrixActionCreators.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ function createSyncAction(matrixClient: MatrixClient, state: string, prevState:
4848
* @property {MatrixEvent} event the MatrixEvent that triggered the dispatch.
4949
* @property {string} event_type the type of the MatrixEvent, e.g. "m.direct".
5050
* @property {Object} event_content the content of the MatrixEvent.
51+
* @property {MatrixEvent} previousEvent the previous account data event of the same type, if present
5152
*/
5253

5354
/**
@@ -56,14 +57,20 @@ function createSyncAction(matrixClient: MatrixClient, state: string, prevState:
5657
*
5758
* @param {MatrixClient} matrixClient the matrix client.
5859
* @param {MatrixEvent} accountDataEvent the account data event.
60+
* @param {MatrixEvent | undefined} previousAccountDataEvent the previous account data event of the same type, if present
5961
* @returns {AccountDataAction} an action of type MatrixActions.accountData.
6062
*/
61-
function createAccountDataAction(matrixClient: MatrixClient, accountDataEvent: MatrixEvent): ActionPayload {
63+
function createAccountDataAction(
64+
matrixClient: MatrixClient,
65+
accountDataEvent: MatrixEvent,
66+
previousAccountDataEvent?: MatrixEvent,
67+
): ActionPayload {
6268
return {
6369
action: "MatrixActions.accountData",
6470
event: accountDataEvent,
6571
event_type: accountDataEvent.getType(),
6672
event_content: accountDataEvent.getContent(),
73+
previousEvent: previousAccountDataEvent,
6774
};
6875
}
6976

src/stores/notifications/NotificationColor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
import { _t } from "../../languageHandler";
1818

1919
export enum NotificationColor {
20+
Muted,
2021
// Inverted (None -> Red) because we do integer comparisons on this
2122
None, // nothing special
2223
// TODO: Remove bold with notifications: https://github.com/vector-im/element-web/issues/14227

src/stores/notifications/NotificationState.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface INotificationStateSnapshotParams {
2424
symbol: string | null;
2525
count: number;
2626
color: NotificationColor;
27+
muted: boolean;
2728
}
2829

2930
export enum NotificationStateEvents {
@@ -42,6 +43,7 @@ export abstract class NotificationState
4243
protected _symbol: string | null = null;
4344
protected _count = 0;
4445
protected _color: NotificationColor = NotificationColor.None;
46+
protected _muted = false;
4547

4648
private watcherReferences: string[] = [];
4749

@@ -66,6 +68,10 @@ export abstract class NotificationState
6668
return this._color;
6769
}
6870

71+
public get muted(): boolean {
72+
return this._muted;
73+
}
74+
6975
public get isIdle(): boolean {
7076
return this.color <= NotificationColor.None;
7177
}
@@ -110,16 +116,18 @@ export class NotificationStateSnapshot {
110116
private readonly symbol: string | null;
111117
private readonly count: number;
112118
private readonly color: NotificationColor;
119+
private readonly muted: boolean;
113120

114121
public constructor(state: INotificationStateSnapshotParams) {
115122
this.symbol = state.symbol;
116123
this.count = state.count;
117124
this.color = state.color;
125+
this.muted = state.muted;
118126
}
119127

120128
public isDifferentFrom(other: INotificationStateSnapshotParams): boolean {
121-
const before = { count: this.count, symbol: this.symbol, color: this.color };
122-
const after = { count: other.count, symbol: other.symbol, color: other.color };
129+
const before = { count: this.count, symbol: this.symbol, color: this.color, muted: this.muted };
130+
const after = { count: other.count, symbol: other.symbol, color: other.color, muted: other.muted };
123131
return JSON.stringify(before) !== JSON.stringify(after);
124132
}
125133
}

src/stores/notifications/RoomNotificationState.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,12 @@ export class RoomNotificationState extends NotificationState implements IDestroy
9393
const snapshot = this.snapshot();
9494

9595
const { color, symbol, count } = RoomNotifs.determineUnreadState(this.room);
96+
const muted =
97+
RoomNotifs.getRoomNotifsState(this.room.client, this.room.roomId) === RoomNotifs.RoomNotifState.Mute;
9698
this._color = color;
9799
this._symbol = symbol;
98100
this._count = count;
101+
this._muted = muted;
99102

100103
// finally, publish an update if needed
101104
this.emitIfUpdated(snapshot);

src/stores/room-list/RoomListStore.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { RoomListStore as Interface, RoomListStoreEvent } from "./Interface";
4040
import { SlidingRoomListStoreClass } from "./SlidingRoomListStore";
4141
import { UPDATE_EVENT } from "../AsyncStore";
4242
import { SdkContextClass } from "../../contexts/SDKContext";
43+
import { getChangedOverrideRoomMutePushRules } from "./utils/roomMute";
4344

4445
interface IState {
4546
// state is tracked in underlying classes
@@ -289,6 +290,17 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
289290
this.onDispatchMyMembership(<any>payload);
290291
return;
291292
}
293+
294+
const possibleMuteChangeRoomIds = getChangedOverrideRoomMutePushRules(payload);
295+
if (possibleMuteChangeRoomIds) {
296+
for (const roomId of possibleMuteChangeRoomIds) {
297+
const room = roomId && this.matrixClient.getRoom(roomId);
298+
if (room) {
299+
await this.handleRoomUpdate(room, RoomUpdateCause.PossibleMuteChange);
300+
}
301+
}
302+
this.updateFn.trigger();
303+
}
292304
}
293305

294306
/**

src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const CATEGORY_ORDER = [
4242
NotificationColor.Grey,
4343
NotificationColor.Bold,
4444
NotificationColor.None, // idle
45+
NotificationColor.Muted,
4546
];
4647

4748
/**
@@ -81,6 +82,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
8182
[NotificationColor.Grey]: [],
8283
[NotificationColor.Bold]: [],
8384
[NotificationColor.None]: [],
85+
[NotificationColor.Muted]: [],
8486
};
8587
for (const room of rooms) {
8688
const category = this.getRoomCategory(room);
@@ -94,7 +96,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
9496
// It's fine for us to call this a lot because it's cached, and we shouldn't be
9597
// wasting anything by doing so as the store holds single references
9698
const state = RoomNotificationStateStore.instance.getRoomState(room);
97-
return state.color;
99+
return this.isMutedToBottom && state.muted ? NotificationColor.Muted : state.color;
98100
}
99101

100102
public setRooms(rooms: Room[]): void {
@@ -164,15 +166,25 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
164166
return this.handleSplice(room, cause);
165167
}
166168

167-
if (cause !== RoomUpdateCause.Timeline && cause !== RoomUpdateCause.ReadReceipt) {
169+
if (
170+
cause !== RoomUpdateCause.Timeline &&
171+
cause !== RoomUpdateCause.ReadReceipt &&
172+
cause !== RoomUpdateCause.PossibleMuteChange
173+
) {
168174
throw new Error(`Unsupported update cause: ${cause}`);
169175
}
170176

171-
const category = this.getRoomCategory(room);
177+
// don't react to mute changes when we are not sorting by mute
178+
if (cause === RoomUpdateCause.PossibleMuteChange && !this.isMutedToBottom) {
179+
return false;
180+
}
181+
172182
if (this.sortingAlgorithm === SortAlgorithm.Manual) {
173183
return false; // Nothing to do here.
174184
}
175185

186+
const category = this.getRoomCategory(room);
187+
176188
const roomIdx = this.getRoomIndex(room);
177189
if (roomIdx === -1) {
178190
throw new Error(`Room ${room.roomId} has no index in ${this.tagId}`);

0 commit comments

Comments
 (0)