Room list: add activity marker to sections (#33024)
* feat: add unread status to section view * feat: add unread tracking in room list section * feat: populate rooms into section header vm * test: add units for unread in section view model * test(e2e): add unread tests
This commit is contained in:
@@ -5,12 +5,17 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
import {
|
||||
BaseViewModel,
|
||||
type RoomListSectionHeaderActions,
|
||||
type RoomListSectionHeaderViewSnapshot,
|
||||
} from "@element-hq/web-shared-components";
|
||||
|
||||
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
|
||||
import { NotificationStateEvents } from "../../stores/notifications/NotificationState";
|
||||
import { type RoomNotificationState } from "../../stores/notifications/RoomNotificationState";
|
||||
|
||||
interface RoomListSectionHeaderViewModelProps {
|
||||
tag: string;
|
||||
title: string;
|
||||
@@ -21,8 +26,13 @@ export class RoomListSectionHeaderViewModel
|
||||
extends BaseViewModel<RoomListSectionHeaderViewSnapshot, RoomListSectionHeaderViewModelProps>
|
||||
implements RoomListSectionHeaderActions
|
||||
{
|
||||
/**
|
||||
* The notification states of the rooms currently in this section, used to compute the unread state.
|
||||
*/
|
||||
private roomNotificationStates = new Set<RoomNotificationState>();
|
||||
|
||||
public constructor(props: RoomListSectionHeaderViewModelProps) {
|
||||
super(props, { id: props.tag, title: props.title, isExpanded: true });
|
||||
super(props, { id: props.tag, title: props.title, isExpanded: true, isUnread: false });
|
||||
}
|
||||
|
||||
public onClick = (): void => {
|
||||
@@ -37,4 +47,47 @@ export class RoomListSectionHeaderViewModel
|
||||
public get isExpanded(): boolean {
|
||||
return this.snapshot.current.isExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the rooms tracked by this section header for unread state computation.
|
||||
* Only subscribes to new rooms and unsubscribes from rooms no longer in the section.
|
||||
* @param rooms - The rooms currently in this section
|
||||
*/
|
||||
public setRooms(rooms: Room[]): void {
|
||||
const newStates = new Set(rooms.map((room) => RoomNotificationStateStore.instance.getRoomState(room)));
|
||||
|
||||
// Unsubscribe from rooms no longer in the section
|
||||
for (const state of this.roomNotificationStates) {
|
||||
if (!newStates.has(state)) {
|
||||
state.off(NotificationStateEvents.Update, this.updateUnreadState);
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to newly added rooms
|
||||
for (const state of newStates) {
|
||||
if (!this.roomNotificationStates.has(state)) {
|
||||
// We don't use trackListener because we don't want to grow the disposables indefinitely as rooms are added and removed from the section
|
||||
state.on(NotificationStateEvents.Update, this.updateUnreadState);
|
||||
}
|
||||
}
|
||||
|
||||
this.roomNotificationStates = newStates;
|
||||
this.updateUnreadState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the unread state of the section header based on the notification states of the tracked rooms.
|
||||
*/
|
||||
private updateUnreadState = (): void => {
|
||||
const isUnread = [...this.roomNotificationStates].some((state) => state.hasAnyNotificationOrActivity);
|
||||
this.snapshot.merge({ isUnread });
|
||||
};
|
||||
|
||||
public dispose(): void {
|
||||
for (const state of this.roomNotificationStates) {
|
||||
state.off(NotificationStateEvents.Update, this.updateUnreadState);
|
||||
}
|
||||
this.roomNotificationStates.clear();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,6 +491,11 @@ export class RoomListViewModel
|
||||
// Track the current active room position for future sticky calculations
|
||||
this.lastActiveRoomPosition = roomId ? this.findRoomPosition(this.roomsResult.sections, roomId) : undefined;
|
||||
|
||||
// Update section header view models with current rooms for unread state tracking
|
||||
for (const section of this.roomsResult.sections) {
|
||||
this.getSectionHeaderViewModel(section.tag).setRooms(section.rooms);
|
||||
}
|
||||
|
||||
// Build the complete state atomically to ensure consistency
|
||||
const { sections, isFlatList } = computeSections(
|
||||
this.roomsResult,
|
||||
|
||||
Reference in New Issue
Block a user