From b75dbe8d5c9641c7c660a75cfa1382a9b57c9034 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 29 Mar 2024 23:23:28 +0100 Subject: [PATCH] Rework roommanager for improved stability Fixes #645 - Active space handling is moved from QML to RoomManager - Active tab in SpaceDrawer (space / no space / DM) is unified in a single variable - RoomList & RoomPage loading is simplified: We're always pushing a RoomPage now; if there is no room, a placeholder is shown - SpaceHomePage is moved into RoomPage; This replaces the entire push/replace room/spacehome logic - If the current room is a space, the space home is shown, otherwise the timeline - The concept of "previous room" is removed entirely. If we're leaving the active room, the placeholder room page is shown - When clicking on a space in the list, the space room list is switched and the space home page is shown In short, these changes should (after some initial regressions) lead to a less crashy NeoChat :) --- src/qml/RoomListPage.qml | 40 +++--- src/qml/RoomPage.qml | 32 +++-- src/qml/SpaceDrawer.qml | 47 ++----- src/qml/SpaceHomePage.qml | 255 +++++++++++++++++++------------------- src/qml/TimelineView.qml | 4 + src/qml/main.qml | 61 ++------- src/roommanager.cpp | 214 ++++++++++++++------------------ src/roommanager.h | 92 ++++---------- 8 files changed, 315 insertions(+), 430 deletions(-) diff --git a/src/qml/RoomListPage.qml b/src/qml/RoomListPage.qml index 70dcf8789..005b9a070 100644 --- a/src/qml/RoomListPage.qml +++ b/src/qml/RoomListPage.qml @@ -32,10 +32,13 @@ Kirigami.Page { readonly property RoomTreeModel roomTreeModel: RoomTreeModel { connection: root.connection } - property bool spaceChanging: false readonly property bool collapsed: Config.collapsed + onCurrentWidthChanged: pageStack.defaultColumnWidth = root.currentWidth + Component.onCompleted: pageStack.defaultColumnWidth = root.currentWidth + + onCollapsedChanged: { if (collapsed) { sortFilterRoomTreeModel.filterText = ""; @@ -87,6 +90,13 @@ Kirigami.Page { padding: 0 + Connections { + target: RoomManager + function onCurrentSpaceChanged() { + treeView.expandRecursively(); + } + } + RowLayout { anchors.fill: parent spacing: 0 @@ -98,7 +108,6 @@ Kirigami.Page { connection: root.connection - onSelectionChanged: root.spaceChanging = true onSpacesUpdated: sortFilterRoomTreeModel.invalidate() } @@ -127,31 +136,14 @@ Kirigami.Page { clip: true reuseItems: false - onLayoutChanged: { - treeView.expandRecursively(); - if (sortFilterRoomTreeModel.filterTextJustChanged) { - sortFilterRoomTreeModel.filterTextJustChanged = false; - } - if (root.spaceChanging) { - if (spaceDrawer.showDirectChats || spaceDrawer.selectedSpaceId.length < 1) { - const item = treeView.itemAtIndex(treeView.index(1, 0)) - if (!item) { - return; - } - RoomManager.resolveResource(item.currentRoom.id); - } - root.spaceChanging = false; - } - } - model: SortFilterRoomTreeModel { id: sortFilterRoomTreeModel - property bool filterTextJustChanged: false - sourceModel: root.roomTreeModel - activeSpaceId: spaceDrawer.selectedSpaceId - mode: spaceDrawer.showDirectChats ? SortFilterRoomTreeModel.DirectChats : SortFilterRoomTreeModel.Rooms + activeSpaceId: RoomManager.currentSpace + mode: RoomManager.currentSpace === "DM" ? SortFilterRoomTreeModel.DirectChats : SortFilterRoomTreeModel.Rooms + onRowsInserted: (index, first, last) => treeView.expandTo(index) + onDataChanged: treeView.expandRecursively() } selectionModel: ItemSelectionModel {} @@ -334,7 +326,7 @@ Kirigami.Page { onTextChanged: newText => { sortFilterRoomTreeModel.filterText = newText; - sortFilterRoomTreeModel.filterTextJustChanged = true; + treeView.expandRecursively(); } } } diff --git a/src/qml/RoomPage.qml b/src/qml/RoomPage.qml index 3f5843719..e625d6af3 100644 --- a/src/qml/RoomPage.qml +++ b/src/qml/RoomPage.qml @@ -72,7 +72,7 @@ Kirigami.Page { /// Disable cancel shortcut. Used by the separate window since it provides its own cancel implementation. property bool disableCancelShortcut: false - title: root.currentRoom.displayName + title: root.currentRoom ? root.currentRoom.displayName : "" focus: true padding: 0 @@ -116,7 +116,7 @@ Kirigami.Page { Loader { id: timelineViewLoader anchors.fill: parent - active: root.currentRoom && !root.currentRoom.isInvite && !root.loading + active: root.currentRoom && !root.currentRoom.isInvite && !root.loading && !root.currentRoom.isSpace sourceComponent: TimelineView { id: timelineView currentRoom: root.currentRoom @@ -143,7 +143,23 @@ Kirigami.Page { } Loader { - active: root.loading && !invitationLoader.active + id: spaceLoader + active: root.currentRoom && root.currentRoom.isSpace + anchors.fill: parent + sourceComponent: SpaceHomePage {} + } + + Loader { + active: !RoomManager.currentRoom + anchors.centerIn: parent + sourceComponent: Kirigami.PlaceholderMessage { + icon.name: "org.kde.neochat" + text: i18n("Welcome to NeoChat") + } + } + + Loader { + active: root.loading && !invitationLoader.active && RoomManager.currentRoom && !spaceLoader.active anchors.centerIn: parent sourceComponent: Kirigami.LoadingPlaceholder { anchors.centerIn: parent @@ -176,11 +192,7 @@ Kirigami.Page { Connections { target: RoomManager function onCurrentRoomChanged() { - if (!RoomManager.currentRoom) { - if (pageStack.lastItem === root) { - pageStack.pop(); - } - } else if (root.currentRoom.isInvite) { + if (root.currentRoom && root.currentRoom.isInvite) { root.currentRoom.clearInvitationNotification(); } } @@ -188,6 +200,10 @@ Kirigami.Page { function onWarning(title, message) { root.warning(title, message); } + + function onGoToEvent(eventId) { + (timelineViewLoader.item as TimelineView).goToEvent(eventId); + } } Shortcut { diff --git a/src/qml/SpaceDrawer.qml b/src/qml/SpaceDrawer.qml index f1b8750ac..05b126f54 100644 --- a/src/qml/SpaceDrawer.qml +++ b/src/qml/SpaceDrawer.qml @@ -21,18 +21,6 @@ QQC2.Control { topPadding: 0 bottomPadding: 0 - property string selectedSpaceId: RoomManager.lastSpaceId - Connections { - target: RoomManager - function onConnectionChanged() { - // We need to rebind as any previous change will have been overwritten. - selectedSpaceId = RoomManager.lastSpaceId; - } - } - - property bool showDirectChats: RoomManager.directChatsActive - - signal selectionChanged signal spacesUpdated contentItem: Loader { @@ -100,12 +88,9 @@ QQC2.Control { source: "user-home-symbolic" } - checked: root.selectedSpaceId === "" && root.showDirectChats === false + checked: RoomManager.currentSpace.length === 0 onClicked: { - root.showDirectChats = false; - RoomManager.directChatsActive = false; - root.selectedSpaceId = ""; - RoomManager.lastSpaceId = ""; + RoomManager.currentSpace = ""; root.selectionChanged(); } @@ -119,7 +104,7 @@ QQC2.Control { height: Kirigami.Units.iconSizes.smallMedium text: root.connection.homeNotifications > 0 ? root.connection.homeNotifications : "" - visible: root.connection.homeNotifications > 0 && (root.selectedSpaceId !== "" || root.showDirectChats === true) + visible: root.connection.homeNotifications > 0 && (RoomManager.currentSpace.length > 0 || root.showDirectChats === true) color: Kirigami.Theme.textColor horizontalAlignment: Text.AlignHCenter background: Rectangle { @@ -149,12 +134,9 @@ QQC2.Control { source: "system-users" } - checked: root.showDirectChats === true + checked: RoomManager.currentSpace === "DM" onClicked: { - root.showDirectChats = true; - RoomManager.directChatsActive = true; - root.selectedSpaceId = ""; - RoomManager.lastSpaceId = ""; + RoomManager.currentSpace = "DM"; root.selectionChanged(); } @@ -193,11 +175,6 @@ QQC2.Control { } onLayoutChanged: root.spacesUpdated() } - onCountChanged: { - if (!root.connection.room(root.selectedSpaceId)) { - root.selectedSpaceId = ""; - } - } delegate: AvatarTabButton { id: spaceDelegate @@ -215,17 +192,11 @@ QQC2.Control { source: avatar ? ("image://mxc/" + avatar) : "" onSelected: { - root.showDirectChats = false; - RoomManager.directChatsActive = false; - if (!SpaceHierarchyCache.isSpaceChild(roomId, RoomManager.currentRoom.id) || root.selectedSpaceId == roomId) { - RoomManager.resolveResource(currentRoom.id); - } else { - RoomManager.lastSpaceId = currentRoom.id; - } - root.selectedSpaceId = roomId; + RoomManager.resolveResource(spaceDelegate.roomId); + RoomManager.currentSpace = spaceDelegate.roomId; root.selectionChanged(); } - checked: root.selectedSpaceId === roomId + checked: RoomManager.currentSpace === roomId onContextMenuRequested: root.createContextMenu(currentRoom) QQC2.Label { @@ -238,7 +209,7 @@ QQC2.Control { height: Kirigami.Units.iconSizes.smallMedium text: spaceDelegate.currentRoom.childrenNotificationCount > 0 ? spaceDelegate.currentRoom.childrenNotificationCount : "" - visible: spaceDelegate.currentRoom.childrenNotificationCount > 0 && root.selectedSpaceId != spaceDelegate.roomId + visible: spaceDelegate.currentRoom.childrenNotificationCount > 0 && RoomManager.currentSpace != spaceDelegate.roomId color: Kirigami.Theme.textColor horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter diff --git a/src/qml/SpaceHomePage.qml b/src/qml/SpaceHomePage.qml index f5ff81ff6..f43c3c9a1 100644 --- a/src/qml/SpaceHomePage.qml +++ b/src/qml/SpaceHomePage.qml @@ -10,152 +10,153 @@ import org.kde.kirigami as Kirigami import org.kde.neochat import org.kde.neochat.settings -Kirigami.Page { +ColumnLayout { id: root readonly property NeoChatRoom currentRoom: RoomManager.currentRoom - padding: 0 + anchors.fill: parent - ColumnLayout { - id: columnLayout - anchors.fill: parent - spacing: 0 + spacing: 0 - Item { - id: headerItem - Layout.fillWidth: true - Layout.topMargin: Kirigami.Units.smallSpacing - implicitHeight: headerColumn.implicitHeight + QQC2.Control { + id: headerItem + Layout.fillWidth: true + Layout.topMargin: Kirigami.Units.smallSpacing + implicitHeight: headerColumn.implicitHeight - ColumnLayout { - id: headerColumn - anchors.centerIn: headerItem - width: sizeHelper.currentWidth - spacing: Kirigami.Units.largeSpacing + background: Rectangle { + color: Kirigami.Theme.backgroundColor + Kirigami.Theme.colorSet: Kirigami.Theme.View + Kirigami.Theme.inherit: false + } - GroupChatDrawerHeader { - id: header - Layout.fillWidth: true - room: root.currentRoom + ColumnLayout { + id: headerColumn + anchors.centerIn: headerItem + width: sizeHelper.currentWidth + spacing: Kirigami.Units.largeSpacing + + GroupChatDrawerHeader { + id: header + Layout.fillWidth: true + room: root.currentRoom + } + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: Kirigami.Units.largeSpacing + Layout.rightMargin: Kirigami.Units.largeSpacing + QQC2.Button { + visible: root.currentRoom.canSendState("invite") + text: i18nc("@button", "Invite user to space") + icon.name: "list-add-user" + onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'InviteUserPage.qml'), { + room: root.currentRoom + }, { + title: i18nc("@title", "Invite a User") + }) } - RowLayout { - Layout.fillWidth: true - Layout.leftMargin: Kirigami.Units.largeSpacing - Layout.rightMargin: Kirigami.Units.largeSpacing - QQC2.Button { - visible: root.currentRoom.canSendState("invite") - text: i18nc("@button", "Invite user to space") - icon.name: "list-add-user" - onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'InviteUserPage.qml'), { - room: root.currentRoom - }, { - title: i18nc("@title", "Invite a User") - }) - } - QQC2.Button { - visible: root.currentRoom.canSendState("m.space.child") - text: i18nc("@button", "Add new room") - icon.name: "list-add" - onClicked: _private.createRoom(root.currentRoom.id) - } - QQC2.Button { - text: i18nc("@button", "Leave the space") - icon.name: "go-previous" - onClicked: RoomManager.leaveRoom(root.currentRoom) - } - Item { - Layout.fillWidth: true - } - QQC2.Button { - text: i18nc("@button", "Space settings") - icon.name: "settings-configure" - display: QQC2.AbstractButton.IconOnly - onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'RoomSettings.qml'), { - room: root.currentRoom, - connection: root.currentRoom.connection - }, { - title: i18n("Room Settings") - }) - - QQC2.ToolTip.text: text - QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - QQC2.ToolTip.visible: hovered - } + QQC2.Button { + visible: root.currentRoom.canSendState("m.space.child") + text: i18nc("@button", "Add new room") + icon.name: "list-add" + onClicked: _private.createRoom(root.currentRoom.id) } - Kirigami.SearchField { + QQC2.Button { + text: i18nc("@button", "Leave the space") + icon.name: "go-previous" + onClicked: RoomManager.leaveRoom(root.currentRoom) + } + Item { Layout.fillWidth: true - Layout.leftMargin: Kirigami.Units.largeSpacing - Layout.rightMargin: Kirigami.Units.largeSpacing - Layout.bottomMargin: Kirigami.Units.largeSpacing - onTextChanged: spaceChildSortFilterModel.filterText = text + } + QQC2.Button { + text: i18nc("@button", "Space settings") + icon.name: "settings-configure" + display: QQC2.AbstractButton.IconOnly + onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'RoomSettings.qml'), { + room: root.currentRoom, + connection: root.currentRoom.connection + }, { + title: i18n("Room Settings") + }) + + QQC2.ToolTip.text: text + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + QQC2.ToolTip.visible: hovered } } - DelegateSizeHelper { - id: sizeHelper - startBreakpoint: Kirigami.Units.gridUnit * 46 - endBreakpoint: Kirigami.Units.gridUnit * 66 - startPercentWidth: 100 - endPercentWidth: 85 - maxWidth: Kirigami.Units.gridUnit * 60 - - parentWidth: columnLayout.width + Kirigami.SearchField { + Layout.fillWidth: true + Layout.leftMargin: Kirigami.Units.largeSpacing + Layout.rightMargin: Kirigami.Units.largeSpacing + Layout.bottomMargin: Kirigami.Units.largeSpacing + onTextChanged: spaceChildSortFilterModel.filterText = text } } - Kirigami.Separator { - Layout.fillWidth: true - } - QQC2.ScrollView { - id: hierarchyScrollView - Layout.fillWidth: true - Layout.fillHeight: true - visible: !spaceChildrenModel.loading + DelegateSizeHelper { + id: sizeHelper + startBreakpoint: Kirigami.Units.gridUnit * 46 + endBreakpoint: Kirigami.Units.gridUnit * 66 + startPercentWidth: 100 + endPercentWidth: 85 + maxWidth: Kirigami.Units.gridUnit * 60 - TreeView { - id: spaceTree - columnWidthProvider: function (column) { - return spaceTree.width; - } - - clip: true - - model: SpaceChildSortFilterModel { - id: spaceChildSortFilterModel - sourceModel: SpaceChildrenModel { - id: spaceChildrenModel - space: root.currentRoom - } - } - - delegate: SpaceHierarchyDelegate { - onCreateRoom: _private.createRoom(roomId) - } - } - - background: Rectangle { - color: Kirigami.Theme.backgroundColor - Kirigami.Theme.colorSet: Kirigami.Theme.View - } - } - Item { - Layout.fillWidth: true - Layout.fillHeight: true - visible: spaceChildrenModel.loading - - Loader { - active: spaceChildrenModel.loading - anchors.centerIn: parent - sourceComponent: Kirigami.LoadingPlaceholder {} - } + parentWidth: root.width } } - - background: Rectangle { - color: Kirigami.Theme.backgroundColor - Kirigami.Theme.inherit: false - Kirigami.Theme.colorSet: Kirigami.Theme.View + Kirigami.Separator { + Layout.fillWidth: true } + QQC2.ScrollView { + id: hierarchyScrollView + Layout.fillWidth: true + Layout.fillHeight: true + visible: !spaceChildrenModel.loading + TreeView { + id: spaceTree + columnWidthProvider: function (column) { + return spaceTree.width; + } + + clip: true + + model: SpaceChildSortFilterModel { + id: spaceChildSortFilterModel + sourceModel: SpaceChildrenModel { + id: spaceChildrenModel + space: root.currentRoom + } + } + + delegate: SpaceHierarchyDelegate { + onCreateRoom: _private.createRoom(roomId) + } + } + + background: Rectangle { + color: Kirigami.Theme.backgroundColor + Kirigami.Theme.colorSet: Kirigami.Theme.View + } + } + QQC2.Control { + Layout.fillWidth: true + Layout.fillHeight: true + visible: spaceChildrenModel.loading + + background: Rectangle { + color: Kirigami.Theme.backgroundColor + Kirigami.Theme.colorSet: Kirigami.Theme.View + Kirigami.Theme.inherit: false + } + + Loader { + active: spaceChildrenModel.loading + anchors.centerIn: parent + sourceComponent: Kirigami.LoadingPlaceholder {} + } + } QtObject { id: _private function createRoom(parentId) { @@ -182,3 +183,5 @@ Kirigami.Page { } } } + + diff --git a/src/qml/TimelineView.qml b/src/qml/TimelineView.qml index af6897dcd..09dcb86fa 100644 --- a/src/qml/TimelineView.qml +++ b/src/qml/TimelineView.qml @@ -422,4 +422,8 @@ QQC2.ScrollView { function positionViewAtBeginning() { messageListView.positionViewAtBeginning(); } + + function goToEvent(eventId) { + messageListView.goToEvent(eventId); + } } diff --git a/src/qml/main.qml b/src/qml/main.qml index 10d56b281..df82fb5f3 100644 --- a/src/qml/main.qml +++ b/src/qml/main.qml @@ -15,32 +15,20 @@ import org.kde.neochat.accounts Kirigami.ApplicationWindow { id: root - property int columnWidth: Kirigami.Units.gridUnit * 13 - - property RoomListPage roomListPage - - property RoomPage roomPage - property SpaceHomePage spaceHomePage - property NeoChatConnection connection: Controller.activeConnection minimumWidth: Kirigami.Units.gridUnit * 20 minimumHeight: Kirigami.Units.gridUnit * 15 visible: false // Will be overridden in Component.onCompleted - wideScreen: width > columnWidth * 5 + wideScreen: width > Kirigami.Units.gridUnit * 65 pageStack { initialPage: WelcomePage { showExisting: true - onConnectionChosen: { - pageStack.replace(roomListComponent); - roomListPage = pageStack.currentItem; - RoomManager.loadInitialRoom(); - } + onConnectionChosen: root.load() } globalToolBar.canContainHandles: true - defaultColumnWidth: roomListPage ? roomListPage.currentWidth : 0 globalToolBar { style: Kirigami.ApplicationHeaderStyle.ToolBar showNavigationButtons: pageStack.currentIndex > 0 || pageStack.layers.depth > 1 ? Kirigami.ApplicationHeaderStyle.ShowBackButton : 0 @@ -59,9 +47,7 @@ Kirigami.ApplicationWindow { Connections { target: LoginHelper function onLoaded() { - pageStack.replace(roomListComponent); - roomListPage = pageStack.currentItem; - RoomManager.loadInitialRoom(); + root.load(); } } @@ -122,16 +108,6 @@ Kirigami.ApplicationWindow { Connections { target: RoomManager - function onPushRoom(room, event) { - root.roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage.qml'), { - connection: root.connection - }); - root.roomPage.forceActiveFocus(); - if (event.length > 0) { - roomPage.goToEvent(event); - } - } - function onAskJoinRoom(room) { joinRoomDialog.createObject(applicationWindow(), { room: room, @@ -143,27 +119,6 @@ Kirigami.ApplicationWindow { root.showUserDetail(user); } - function onPushSpaceHome(room) { - root.spaceHomePage = pageStack.push(Qt.createComponent('org.kde.neochat', 'SpaceHomePage.qml')); - root.spaceHomePage.forceActiveFocus(); - } - - function onReplaceRoom(room, event) { - if (root.roomPage) { - pageStack.currentIndex = pageStack.depth - 1; - } else { - pageStack.pop(); - root.roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage.qml'), { - connection: root.connection - }); - root.spaceHomePage = null; - } - root.roomPage.forceActiveFocus(); - if (event.length > 0) { - root.roomPage.goToEvent(event); - } - } - function onReplaceSpaceHome(room) { if (root.spaceHomePage) { pageStack.currentIndex = pageStack.depth - 1; @@ -314,7 +269,6 @@ Kirigami.ApplicationWindow { target: AccountRegistry function onRowsRemoved() { if (AccountRegistry.rowCount() === 0) { - RoomManager.reset(); pageStack.clear(); pageStack.push(Qt.createComponent('org.kde.neochat', '.qml')); } @@ -490,6 +444,15 @@ Kirigami.ApplicationWindow { }).open(); } + function load() { + pageStack.replace(roomListComponent); + RoomManager.loadInitialRoom(); + let roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage.qml'), { + connection: root.connection + }); + roomPage.forceActiveFocus(); + } + Component { id: userDetailDialog UserDetailDialog {} diff --git a/src/roommanager.cpp b/src/roommanager.cpp index daab29821..396b728ad 100644 --- a/src/roommanager.cpp +++ b/src/roommanager.cpp @@ -7,11 +7,10 @@ #include "chatbarcache.h" #include "controller.h" #include "eventhandler.h" -#include "messagecomponenttype.h" #include "models/timelinemodel.h" -#include "neochatconfig.h" #include "neochatconnection.h" #include "neochatroom.h" +#include "spacehierarchycache.h" #include #include @@ -29,8 +28,6 @@ RoomManager::RoomManager(QObject *parent) : QObject(parent) - , m_currentRoom(nullptr) - , m_lastCurrentRoom(nullptr) , m_config(KSharedConfig::openStateConfig()) , m_timelineModel(new TimelineModel(this)) , m_messageFilterModel(new MessageFilterModel(this, m_timelineModel)) @@ -104,7 +101,7 @@ void RoomManager::resolveResource(const QString &idOrUri, const QString &action) const auto result = visitResource(m_connection, uri); if (result == Quotient::CouldNotResolve) { - if (uri.type() == Uri::RoomAlias || uri.type() == Uri::RoomId) { + if ((uri.type() == Uri::RoomAlias || uri.type() == Uri::RoomId) && action != "no_join"_ls) { Q_EMIT askJoinRoom(uri.primaryId()); } } else { // Invalid cases should have been eliminated earlier @@ -187,72 +184,17 @@ void RoomManager::loadInitialRoom() connect(this, &RoomManager::connectionChanged, this, &RoomManager::openRoomForActiveConnection); } -QString RoomManager::lastSpaceId() const -{ - if (!m_connection) { - return {}; - } - return m_lastSpaceConfig.readEntry(m_connection->userId(), QString()); -} - -void RoomManager::setLastSpaceId(const QString &lastSpaceId) -{ - if (!m_connection) { - return; - } - - const auto currentLastSpaceId = m_lastSpaceConfig.readEntry(m_connection->userId(), QString()); - if (lastSpaceId == currentLastSpaceId) { - return; - } - m_lastSpaceConfig.writeEntry(m_connection->userId(), lastSpaceId); - Q_EMIT lastSpaceIdChanged(); -} - -bool RoomManager::directChatsActive() const -{ - if (!m_connection) { - return {}; - } - return m_directChatsConfig.readEntry(m_connection->userId(), bool()); -} - -void RoomManager::setDirectChatsActive(bool directChatsActive) -{ - if (!m_connection) { - return; - } - - const auto currentDirectChatsActive = m_directChatsConfig.readEntry(m_connection->userId(), bool()); - if (directChatsActive == currentDirectChatsActive) { - return; - } - m_directChatsConfig.writeEntry(m_connection->userId(), directChatsActive); - Q_EMIT directChatsActiveChanged(); -} - void RoomManager::openRoomForActiveConnection() { if (!m_connection) { - return; + m_currentRoom = nullptr; } - // Read from last open room - QString roomId = m_lastRoomConfig.readEntry(m_connection->userId(), QString()); - - // TODO remove legacy check at some point. - if (roomId.isEmpty()) { - roomId = NeoChatConfig::self()->openRoom(); - } - - if (!roomId.isEmpty()) { - // Here we can cast because the controller has been configured to - // return NeoChatRoom instead of simple Quotient::Room - const auto room = qobject_cast(m_connection->room(roomId)); - - if (room) { - resolveResource(room->id()); - } + if (m_lastRoomConfig.readEntry(m_connection->userId(), QString()).isEmpty()) { + setCurrentRoom({}); + } else { + resolveResource(m_lastRoomConfig.readEntry(m_connection->userId(), QString())); } + setCurrentSpace(m_lastSpaceConfig.readEntry(m_connection->userId(), QString()), false); } UriResolveResult RoomManager::visitUser(User *user, const QString &action) @@ -273,10 +215,9 @@ UriResolveResult RoomManager::visitUser(User *user, const QString &action) return Quotient::UriResolved; } -void RoomManager::visitRoom(Room *room, const QString &eventId) +void RoomManager::visitRoom(Room *r, const QString &eventId) { - auto neoChatRoom = qobject_cast(room); - Q_ASSERT(neoChatRoom != nullptr); + auto room = qobject_cast(r); if (m_currentRoom && !m_currentRoom->editCache()->editId().isEmpty()) { m_currentRoom->editCache()->setEditId({}); @@ -285,41 +226,26 @@ void RoomManager::visitRoom(Room *room, const QString &eventId) // We're doing these things here because it is critical that they are switched at the same time if (m_chatDocumentHandler->document()) { m_currentRoom->mainCache()->setSavedText(m_chatDocumentHandler->document()->textDocument()->toPlainText()); - m_chatDocumentHandler->setRoom(neoChatRoom); - m_chatDocumentHandler->document()->textDocument()->setPlainText(neoChatRoom->mainCache()->savedText()); - neoChatRoom->mainCache()->setText(neoChatRoom->mainCache()->savedText()); - } else { - m_chatDocumentHandler->setRoom(neoChatRoom); - } - } - - if (m_currentRoom) { - if (m_currentRoom->id() == room->id()) { - Q_EMIT goToEvent(eventId); - } else { - m_lastCurrentRoom = std::exchange(m_currentRoom, neoChatRoom); - Q_EMIT currentRoomChanged(); - - if (neoChatRoom->isSpace()) { - m_lastSpaceConfig.writeEntry(m_connection->userId(), room->id()); - Q_EMIT replaceSpaceHome(neoChatRoom); - } else { - Q_EMIT replaceRoom(neoChatRoom, eventId); + m_chatDocumentHandler->setRoom(room); + if (room) { + m_chatDocumentHandler->document()->textDocument()->setPlainText(room->mainCache()->savedText()); + room->mainCache()->setText(room->mainCache()->savedText()); } - } - } else { - m_lastCurrentRoom = std::exchange(m_currentRoom, neoChatRoom); - Q_EMIT currentRoomChanged(); - if (neoChatRoom->isSpace()) { - m_lastSpaceConfig.writeEntry(m_connection->userId(), room->id()); - Q_EMIT pushSpaceHome(neoChatRoom); } else { - Q_EMIT pushRoom(neoChatRoom, eventId); + m_chatDocumentHandler->setRoom(room); } } - // Save last open room - m_lastRoomConfig.writeEntry(m_connection->userId(), room->id()); + if (!room) { + setCurrentRoom({}); + return; + } + + if (m_currentRoom && m_currentRoom->id() == room->id()) { + Q_EMIT goToEvent(eventId); + } else { + setCurrentRoom(room->id()); + } } void RoomManager::joinRoom(Quotient::Connection *account, const QString &roomAliasOrId, const QStringList &viaServers) @@ -360,31 +286,18 @@ bool RoomManager::visitNonMatrix(const QUrl &url) return true; } -void RoomManager::reset() -{ - m_arg = QString(); - m_currentRoom = nullptr; - m_lastCurrentRoom = nullptr; - Q_EMIT currentRoomChanged(); -} - void RoomManager::leaveRoom(NeoChatRoom *room) { if (!room) { return; } - if (m_lastCurrentRoom && room->id() == m_lastCurrentRoom->id()) { - m_lastCurrentRoom = nullptr; - } + if (m_currentRoom && m_currentRoom->id() == room->id()) { - m_currentRoom = m_lastCurrentRoom; - m_lastCurrentRoom = nullptr; - Q_EMIT currentRoomChanged(); - if (m_currentRoom->isSpace()) { - Q_EMIT replaceSpaceHome(m_currentRoom); - } else { - Q_EMIT replaceRoom(m_currentRoom, {}); - } + setCurrentRoom({}); + } + + if (m_currentSpaceId == room->id()) { + setCurrentSpace({}); } room->forget(); @@ -411,4 +324,69 @@ void RoomManager::setConnection(NeoChatConnection *connection) Q_EMIT connectionChanged(); } +void RoomManager::setCurrentSpace(const QString &spaceId, bool setRoom) +{ + m_currentSpaceId = spaceId; + Q_EMIT currentSpaceChanged(); + m_lastSpaceConfig.writeEntry(m_connection->userId(), spaceId); + + if (!setRoom) { + return; + } + if (spaceId.length() > 3) { + resolveResource(spaceId, "no_join"_ls); + } else { + visitRoom({}, {}); + } +} + +void RoomManager::setCurrentRoom(const QString &roomId) +{ + if (roomId.isEmpty()) { + m_currentRoom = nullptr; + } else { + m_currentRoom = dynamic_cast(m_connection->room(roomId)); + } + Q_EMIT currentRoomChanged(); + if (m_connection) { + m_lastRoomConfig.writeEntry(m_connection->userId(), roomId); + } + if (roomId.isEmpty()) { + return; + } + if (m_currentRoom->isSpace()) { + return; + } + if (m_currentRoom->isDirectChat() && m_currentSpaceId != "DM"_ls) { + setCurrentSpace("DM"_ls, false); + return; + } + const auto &parentSpaces = SpaceHierarchyCache::instance().parentSpaces(roomId); + if (parentSpaces.contains(m_currentSpaceId)) { + return; + } + if (const auto &parent = m_connection->room(m_currentRoom->canonicalParent())) { + for (const auto &parentParent : SpaceHierarchyCache::instance().parentSpaces(parent->id())) { + if (SpaceHierarchyCache::instance().parentSpaces(parentParent).isEmpty()) { + setCurrentSpace(parentParent, false); + return; + } + } + setCurrentSpace(parent->id(), false); + return; + } + for (const auto &space : parentSpaces) { + if (SpaceHierarchyCache::instance().parentSpaces(space).isEmpty()) { + setCurrentSpace(space, false); + return; + } + } + setCurrentSpace({}, false); +} + +QString RoomManager::currentSpace() const +{ + return m_currentSpaceId; +} + #include "moc_roommanager.cpp" diff --git a/src/roommanager.h b/src/roommanager.h index 26046a956..789bb2c51 100644 --- a/src/roommanager.h +++ b/src/roommanager.h @@ -9,7 +9,6 @@ #include #include #include -#include #include "chatdocumenthandler.h" #include "enums/messagecomponenttype.h" @@ -21,12 +20,6 @@ class NeoChatRoom; class NeoChatConnection; -namespace Quotient -{ -class Room; -class User; -} - using namespace Quotient; /** @@ -52,6 +45,14 @@ class RoomManager : public QObject, public UriResolverBase */ Q_PROPERTY(NeoChatRoom *currentRoom READ currentRoom NOTIFY currentRoomChanged) + /** + * @brief The id of the space currently opened in the space drawer. + * + * If this is an empty string, the uncategorized rooms are shown. + * If it is the string "DM", the DMs are shown. + */ + Q_PROPERTY(QString currentSpace READ currentSpace WRITE setCurrentSpace NOTIFY currentSpaceChanged) + /** * @brief The TimelineModel that should be used for room message visualisation. * @@ -87,16 +88,6 @@ class RoomManager : public QObject, public UriResolverBase */ Q_PROPERTY(bool hasOpenRoom READ hasOpenRoom NOTIFY currentRoomChanged) - /** - * @brief The room ID of the last space entered. - */ - Q_PROPERTY(QString lastSpaceId READ lastSpaceId WRITE setLastSpaceId NOTIFY directChatsActiveChanged) - - /** - * @brief Whether the last SpaceDrawer category selected was direct chats. - */ - Q_PROPERTY(bool directChatsActive READ directChatsActive WRITE setDirectChatsActive NOTIFY directChatsActiveChanged) - /** * @brief The ChatDocumentHandler for the open room. * @@ -183,11 +174,6 @@ public: */ Q_INVOKABLE void viewEventMenu(const QString &eventId, NeoChatRoom *room, const QString &selectedText = {}); - /** - * @brief Call this when the current used connection is dropped. - */ - Q_INVOKABLE void reset(); - ChatDocumentHandler *chatDocumentHandler() const; void setChatDocumentHandler(ChatDocumentHandler *handler); @@ -196,8 +182,7 @@ public: */ void setUrlArgument(const QString &arg); - QString lastSpaceId() const; - void setLastSpaceId(const QString &lastSpaceId); + QString currentSpace() const; bool directChatsActive() const; void setDirectChatsActive(bool directChatsActive); @@ -213,47 +198,6 @@ Q_SIGNALS: void currentRoomChanged(); - /** - * @brief Push a new room page. - * - * Signal triggered when the main window pageStack should push a new page with - * the message list for the given room. - * - * @param room the room to be shown on the new page. - * @param event the event to got to if available. - */ - void pushRoom(NeoChatRoom *room, const QString &event); - - /** - * @brief Replace the existing room. - * - * Signal triggered when the room displayed by the message list should be changed. - * - * @param room the room to be shown on the new page. - * @param event the event to got to if available. - */ - void replaceRoom(NeoChatRoom *room, const QString &event); - - /** - * @brief Push a new space home page. - * - * Signal triggered when the main window pageStack should push a new page with - * the space home for the given space room. - * - * @param spaceRoom the space room to be shown on the new page. - */ - void pushSpaceHome(NeoChatRoom *spaceRoom); - - /** - * @brief Replace the existing space home. - * - * Signal triggered when the currently displayed room page should be changed - * to the space home for the given space room. - * - * @param spaceRoom the space room to be shown on the new page. - */ - void replaceSpaceHome(NeoChatRoom *spaceRoom); - /** * @brief Go to the specified event in the current room. */ @@ -324,15 +268,24 @@ Q_SIGNALS: void connectionChanged(); void directChatsActiveChanged(); - void lastSpaceIdChanged(); void externalUrl(const QUrl &url); + void currentSpaceChanged(); + private: void openRoomForActiveConnection(); + /** The room currently being shown in the main view (RoomPage.qml). This can be null, if there is no room. + * If this is a space, the space home page is shown. + */ QPointer m_currentRoom; - NeoChatRoom *m_lastCurrentRoom; + + /** The id of the space currently opened in the space drawer. If this is empty, the uncategorized rooms are shown. + * If it is "DM", the direct messages are shown. Otherwise it's the id of a toplevel space. + */ + QString m_currentSpaceId; + QString m_arg; KSharedConfig::Ptr m_config; KConfigGroup m_lastRoomConfig; @@ -345,6 +298,11 @@ private: MediaMessageFilterModel *m_mediaMessageFilterModel; NeoChatConnection *m_connection; + void setCurrentRoom(const QString &roomId); + + // Space ID, "DM", or empty string + void setCurrentSpace(const QString &spaceId, bool setRoom = true); + /** * @brief Resolve a user URI. *