From bdfe28c9c30139e45ea71d7969a9cb11db525413 Mon Sep 17 00:00:00 2001
From: SpiritCroc <dev@spiritcroc.de>
Date: Sun, 5 May 2024 10:12:17 +0200
Subject: Bring back unified room list

Remaining TODO:
- Hook up setting to UI again (better with own SC-specific screen?)

Co-authored-by: su-ex <codeworks@supercable.onl>
---
 src/components/views/rooms/RoomList.tsx      | 39 +++++++++++++++++++-
 src/settings/Settings.tsx                    |  8 ++++
 src/stores/room-list/RoomListStore.ts        | 19 +++++++++-
 src/stores/room-list/algorithms/Algorithm.ts | 20 ++++++++--
 src/stores/room-list/models.ts               |  2 +
 5 files changed, 83 insertions(+), 5 deletions(-)

diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx
index e27b2ca03f..311a54d9d8 100644
--- a/src/components/views/rooms/RoomList.tsx
+++ b/src/components/views/rooms/RoomList.tsx
@@ -72,11 +72,13 @@ interface IState {
     sublists: ITagMap;
     currentRoomId?: string;
     suggestedRooms: ISuggestedRoom[];
+    unifiedRoomList: boolean;
 }
 
 export const TAG_ORDER: TagID[] = [
     DefaultTagID.Invite,
     DefaultTagID.Favourite,
+    DefaultTagID.Unified,
     DefaultTagID.DM,
     DefaultTagID.Untagged,
     DefaultTagID.Conference,
@@ -89,6 +91,7 @@ export const TAG_ORDER: TagID[] = [
     // but we'd have to make sure that rooms you weren't in were hidden.
 ];
 const ALWAYS_VISIBLE_TAGS: TagID[] = [DefaultTagID.DM, DefaultTagID.Untagged];
+const ALWAYS_VISIBLE_UNIFIED_TAGS: TagID[] = [DefaultTagID.Unified];
 
 interface ITagAesthetics {
     sectionLabel: TranslationKey;
@@ -366,6 +369,17 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
     return null;
 };
 
+const UnifiedAuxButton: React.FC<IAuxButtonProps> = (iAuxButtonProps: IAuxButtonProps) => {
+    return (
+        <>
+            {/* eslint-disable-next-line new-cap */}
+            {DmAuxButton(iAuxButtonProps)}
+            {/* eslint-disable-next-line new-cap */}
+            {UntaggedAuxButton(iAuxButtonProps)}
+        </>
+    );
+};
+
 const TAG_AESTHETICS: TagAestheticsMap = {
     [DefaultTagID.Invite]: {
         sectionLabel: _td("action|invites_list"),
@@ -383,6 +397,12 @@ const TAG_AESTHETICS: TagAestheticsMap = {
         defaultHidden: false,
         AuxButtonComponent: DmAuxButton,
     },
+    [DefaultTagID.Unified]: {
+        sectionLabel: _td("Normal priority"),
+        isInvite: false,
+        defaultHidden: false,
+        AuxButtonComponent: UnifiedAuxButton,
+    },
     [DefaultTagID.Conference]: {
         sectionLabel: _td("voip|metaspace_video_rooms|conference_room_section"),
         isInvite: false,
@@ -421,6 +441,7 @@ const TAG_AESTHETICS: TagAestheticsMap = {
 
 export default class RoomList extends React.PureComponent<IProps, IState> {
     private dispatcherRef?: string;
+    private readonly unifiedRoomListWatcherRef: string;
     private treeRef = createRef<HTMLDivElement>();
 
     public static contextType = MatrixClientContext;
@@ -432,7 +453,14 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
         this.state = {
             sublists: {},
             suggestedRooms: SpaceStore.instance.suggestedRooms,
+            unifiedRoomList: SettingsStore.getValue("unifiedRoomList"),
         };
+
+        this.unifiedRoomListWatcherRef = SettingsStore.watchSetting(
+            "unifiedRoomList",
+            null,
+            this.onUnifiedRoomListChange,
+        );
     }
 
     public componentDidMount(): void {
@@ -447,9 +475,16 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
         SpaceStore.instance.off(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms);
         RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists);
         if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef);
+        SettingsStore.unwatchSetting(this.unifiedRoomListWatcherRef);
         SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
     }
 
+    private onUnifiedRoomListChange = (): void => {
+        this.setState({
+            unifiedRoomList: SettingsStore.getValue("unifiedRoomList"),
+        });
+    };
+
     private onRoomViewStoreUpdate = (): void => {
         this.setState({
             currentRoomId: SdkContextClass.instance.roomViewStore.getRoomId() ?? undefined,
@@ -590,7 +625,9 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
             const aesthetics = TAG_AESTHETICS[orderedTagId];
             if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`);
 
-            let alwaysVisible = ALWAYS_VISIBLE_TAGS.includes(orderedTagId);
+            let alwaysVisible = (
+                this.state.unifiedRoomList ? ALWAYS_VISIBLE_UNIFIED_TAGS : ALWAYS_VISIBLE_TAGS
+            ).includes(orderedTagId);
             if (
                 (this.props.activeSpace === MetaSpace.Favourites && orderedTagId !== DefaultTagID.Favourite) ||
                 (this.props.activeSpace === MetaSpace.People && orderedTagId !== DefaultTagID.DM) ||
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index 4f788ee224..2e475871fa 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -192,6 +192,14 @@ export interface IFeature extends Omit<IBaseSetting<boolean>, "isFeature"> {
 export type ISetting = IBaseSetting | IFeature;
 
 export const SETTINGS: { [setting: string]: ISetting } = {
+    // SC settings start
+    "unifiedRoomList": {
+        supportedLevels: LEVELS_ACCOUNT_SETTINGS,
+        displayName: _td("Show people and rooms in a combined list"),
+        default: true,
+        controller: new ReloadOnChangeController(),
+    },
+    // SC settings end
     "feature_video_rooms": {
         isFeature: true,
         labsGroup: LabGroup.VoiceAndVideo,
diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts
index 53377e0a01..381187b85c 100644
--- a/src/stores/room-list/RoomListStore.ts
+++ b/src/stores/room-list/RoomListStore.ts
@@ -500,6 +500,9 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
                 this.setAndPersistListOrder(tag, listOrder);
             }
         }
+
+        // SC: Unified list for DMs and groups
+        this.algorithm.setUnifiedRoomList(SettingsStore.getValue("unifiedRoomList"));
     }
 
     private onAlgorithmListUpdated = (forceUpdate: boolean): void => {
@@ -613,7 +616,21 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
      */
     public getTagsForRoom(room: Room): TagID[] {
         const algorithmTags = this.algorithm.getTagsForRoom(room);
-        if (!algorithmTags) return [DefaultTagID.Untagged];
+        if (!algorithmTags) {
+            if (SettingsStore.getValue("unifiedRoomList")) {
+                return [DefaultTagID.Unified];
+            } else {
+                return [DefaultTagID.Untagged];
+            }
+        }
+        const dmTagIndex = algorithmTags.indexOf(DefaultTagID.DM);
+        if (dmTagIndex !== -1) {
+            algorithmTags[dmTagIndex] = DefaultTagID.Unified;
+        }
+        const untaggedTagIndex = algorithmTags.indexOf(DefaultTagID.Untagged);
+        if (untaggedTagIndex !== -1) {
+            algorithmTags[untaggedTagIndex] = DefaultTagID.Unified;
+        }
         return algorithmTags;
     }
 
diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts
index e296676149..06147137b3 100644
--- a/src/stores/room-list/algorithms/Algorithm.ts
+++ b/src/stores/room-list/algorithms/Algorithm.ts
@@ -75,6 +75,7 @@ export class Algorithm extends EventEmitter {
      * Set to true to suspend emissions of algorithm updates.
      */
     public updatesInhibited = false;
+    private unifiedRoomList: boolean = true;
 
     public start(): void {
         CallStore.instance.on(CallStoreEvent.ConnectedCalls, this.onConnectedCalls);
@@ -106,6 +107,10 @@ export class Algorithm extends EventEmitter {
         return this._cachedRooms;
     }
 
+    public setUnifiedRoomList(unifiedRoomList: boolean): void {
+        this.unifiedRoomList = unifiedRoomList;
+    }
+
     /**
      * Awaitable version of the sticky room setter.
      * @param val The new room to sticky.
@@ -513,7 +518,10 @@ export class Algorithm extends EventEmitter {
             }
 
             if (!inTag) {
-                if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) {
+                if (this.unifiedRoomList) {
+                    // SC: Unified room list for DMs and groups
+                    newTags[DefaultTagID.Unified].push(room);
+                } else if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) {
                     newTags[DefaultTagID.DM].push(room);
                 } else {
                     newTags[DefaultTagID.Untagged].push(room);
@@ -557,7 +565,13 @@ export class Algorithm extends EventEmitter {
             tags.push(...this.getTagsOfJoinedRoom(room));
         }
 
-        if (!tags.length) tags.push(DefaultTagID.Untagged);
+        if (!tags.length) {
+            if (this.unifiedRoomList) {
+                tags.push(DefaultTagID.Unified);
+            } else {
+                tags.push(DefaultTagID.Untagged);
+            }
+        }
 
         return tags;
     }
@@ -567,7 +581,7 @@ export class Algorithm extends EventEmitter {
 
         if (tags.length === 0) {
             // Check to see if it's a DM if it isn't anything else
-            if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) {
+            if (DMRoomMap.shared().getUserIdForRoomId(room.roomId) && !this.unifiedRoomList) {
                 tags = [DefaultTagID.DM];
             }
         }
diff --git a/src/stores/room-list/models.ts b/src/stores/room-list/models.ts
index 50cecda665..8efc2d040d 100644
--- a/src/stores/room-list/models.ts
+++ b/src/stores/room-list/models.ts
@@ -13,6 +13,7 @@ export enum DefaultTagID {
     LowPriority = "m.lowpriority",
     Favourite = "m.favourite",
     DM = "im.vector.fake.direct",
+    Unified = "chat.schildi.fake.unified",
     Conference = "im.vector.fake.conferences",
     ServerNotice = "m.server_notice",
     Suggested = "im.vector.fake.suggested",
@@ -21,6 +22,7 @@ export enum DefaultTagID {
 export const OrderedDefaultTagIDs = [
     DefaultTagID.Invite,
     DefaultTagID.Favourite,
+    DefaultTagID.Unified,
     DefaultTagID.DM,
     DefaultTagID.Conference,
     DefaultTagID.Untagged,
-- 
2.47.0