summary refs log tree commit diff
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2022-08-10 00:20:44 +0200
committerNicolas Werner <nicolas.werner@hotmail.de>2022-08-10 00:20:44 +0200
commit376612e4eb39b1a5260745b901a3366d393511ee (patch)
treee86bed96026080c0cd7298a2a69f8daedb68e2db
parentChange preview title for spaces (diff)
downloadnheko-376612e4eb39b1a5260745b901a3366d393511ee.tar.xz
Support editing space children
-rw-r--r--CMakeLists.txt10
-rw-r--r--cmake/Hunter/config.cmake6
-rw-r--r--io.github.NhekoReborn.Nheko.yaml2
-rw-r--r--resources/qml/RoomList.qml78
-rw-r--r--resources/qml/components/SpaceMenuLevel.qml84
-rw-r--r--resources/res.qrc13
-rw-r--r--src/timeline/CommunitiesModel.cpp164
-rw-r--r--src/timeline/CommunitiesModel.h49
-rw-r--r--src/timeline/RoomlistModel.cpp7
9 files changed, 365 insertions, 48 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 136505ab..214aad13 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -26,10 +26,10 @@ set(CMAKE_AUTOMOC ON)
 option(HUNTER_ENABLED "Enable Hunter package manager" OFF)
 include("cmake/HunterGate.cmake")
 HunterGate(
-	URL "https://github.com/cpp-pm/hunter/archive/v0.23.305.tar.gz"
-	SHA1 "fc8d7a6dac2fa23681847b3872d88d3839b657b0"
-	LOCAL
-	)
+    URL "https://github.com/cpp-pm/hunter/archive/v0.24.3.tar.gz"
+    SHA1 "10738b59e539818a01090e64c2d09896247530c7"
+		LOCAL
+)
 
 macro(hunter_add_package_safe)
 	set(pkg_temp_backup_libdir "$ENV{PKG_CONFIG_LIBDIR}")
@@ -583,7 +583,7 @@ if(USE_BUNDLED_MTXCLIENT)
 	FetchContent_Declare(
 		MatrixClient
 		GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
-		GIT_TAG        b706492de042455630063c847574bbc5ed5d4641
+		GIT_TAG        eb747bb7723c11e38ed21543d05c42cc883c9d06
 		)
 	set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
 	set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
diff --git a/cmake/Hunter/config.cmake b/cmake/Hunter/config.cmake
index d7030d41..3f5f8ce4 100644
--- a/cmake/Hunter/config.cmake
+++ b/cmake/Hunter/config.cmake
@@ -1,9 +1,3 @@
-
-hunter_config(
-    spdlog
-    VERSION 1.8.0-p1
-)
-
 hunter_config(
     lmdb
     VERSION 0.9.21-p2
diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml
index d6d877d7..4984ef1f 100644
--- a/io.github.NhekoReborn.Nheko.yaml
+++ b/io.github.NhekoReborn.Nheko.yaml
@@ -203,7 +203,7 @@ modules:
     buildsystem: cmake-ninja
     name: mtxclient
     sources:
-      - commit: b706492de042455630063c847574bbc5ed5d4641
+      - commit: eb747bb7723c11e38ed21543d05c42cc883c9d06
         #tag: v0.8.0
         type: git
         url: https://github.com/Nheko-Reborn/mtxclient.git
diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml
index 1e61b68b..fe61b8d5 100644
--- a/resources/qml/RoomList.qml
+++ b/resources/qml/RoomList.qml
@@ -110,6 +110,17 @@ Page {
 
         }
 
+
+        Component {
+            id: nestedSpaceMenuLevel
+
+            SpaceMenuLevel {
+                roomid: roomContextMenu.roomid
+                childMenu: rootSpaceMenu.childMenu
+            }
+        }
+
+
         Platform.Menu {
             id: roomContextMenu
 
@@ -154,42 +165,51 @@ Page {
                 onTriggered: Rooms.copyLink(roomContextMenu.roomid)
             }
 
-            Platform.MenuSeparator {
-                text: qsTr("Tag room as:")
-            }
-
-            Instantiator {
-                model: Communities.tagsWithDefault
-                onObjectAdded: roomContextMenu.insertItem(index + 4, object)
-                onObjectRemoved: roomContextMenu.removeItem(object)
-
-                delegate: Platform.MenuItem {
-                    property string t: modelData
-
-                    text: {
-                        switch (t) {
-                        case "m.favourite":
-                            return qsTr("Favourite");
-                        case "m.lowpriority":
-                            return qsTr("Low priority");
-                        case "m.server_notice":
-                            return qsTr("Server notice");
-                        default:
-                            return t.substring(2);
+            Platform.Menu {
+                id: tagsMenu
+                title: qsTr("Tag room as:")
+
+                Instantiator {
+                    model: Communities.tagsWithDefault
+                    onObjectAdded: tagsMenu.insertItem(index, object)
+                    onObjectRemoved: tagsMenu.removeItem(object)
+
+                    delegate: Platform.MenuItem {
+                        property string t: modelData
+
+                        text: {
+                            switch (t) {
+                                case "m.favourite":
+                                return qsTr("Favourite");
+                                case "m.lowpriority":
+                                return qsTr("Low priority");
+                                case "m.server_notice":
+                                return qsTr("Server notice");
+                                default:
+                                return t.substring(2);
+                            }
                         }
+                        checkable: true
+                        checked: roomContextMenu.tags !== undefined && roomContextMenu.tags.includes(t)
+                        onTriggered: Rooms.toggleTag(roomContextMenu.roomid, t, checked)
                     }
-                    checkable: true
-                    checked: roomContextMenu.tags !== undefined && roomContextMenu.tags.includes(t)
-                    onTriggered: Rooms.toggleTag(roomContextMenu.roomid, t, checked)
+
                 }
 
+                Platform.MenuItem {
+                    text: qsTr("Create new tag...")
+                    onTriggered: newTag.show()
+                }
             }
 
-            Platform.MenuItem {
-                text: qsTr("Create new tag...")
-                onTriggered: newTag.show()
-            }
+            SpaceMenuLevel {
+                id: rootSpaceMenu
 
+                roomid: roomContextMenu.roomid
+                position: -1
+                title: qsTr("Add or remove from space")
+                childMenu: nestedSpaceMenuLevel
+            }
         }
 
         delegate: ItemDelegate {
diff --git a/resources/qml/components/SpaceMenuLevel.qml b/resources/qml/components/SpaceMenuLevel.qml
new file mode 100644
index 00000000..419b0f6e
--- /dev/null
+++ b/resources/qml/components/SpaceMenuLevel.qml
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick 2.15
+import Qt.labs.platform 1.1 as Platform
+import im.nheko 1.0
+
+Platform.Menu {
+    id: spacesMenu
+
+    property string roomid
+    property Component childMenu
+
+    property int position: modelData == undefined ? -2 : modelData.treeIndex
+    title: modelData != undefined ? modelData.name : qsTr("Add or remove from space")
+    property bool loadChildren: false
+
+    onAboutToShow: loadChildren = true
+    //onAboutToHide: loadChildren = false
+
+    Platform.MenuItemGroup {
+        id: modificationGroup
+        visible: position != -1
+    }
+
+    Platform.MenuItem {
+        text: qsTr("Official community for this room")
+        group: modificationGroup
+        checkable: true
+        checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && modelData.canonical)
+        enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
+        onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, true)
+    }
+    Platform.MenuItem {
+        text: qsTr("Affiliated community for this room")
+        group: modificationGroup
+        checkable: true
+        checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && !modelData.canonical)
+        enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
+        onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, false)
+    }
+    Platform.MenuItem {
+        text: qsTr("Listed only for community members")
+        group: modificationGroup
+        checkable: true
+        checked: spacesMenu.position >= 0 && (modelData.childValid && !modelData.parentValid)
+        enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
+        onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, true, false)
+    }
+    Platform.MenuItem {
+        text: qsTr("Listed only for room members")
+        group: modificationGroup
+        checkable: true
+        checked: spacesMenu.position >= 0 && (!modelData.childValid && modelData.parentValid)
+        enabled: spacesMenu.position >= 0 && ((modelData.canEditChild) && (modelData.parentValid || modelData.canEditParent))
+        onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, false, false)
+    }
+    Platform.MenuItem {
+        text: qsTr("Not related")
+        group: modificationGroup
+        checkable: true
+        checked: spacesMenu.position >= 0 && (!modelData.childValid && !modelData.parentValid)
+        enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || !modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
+        onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, false, false)
+    }
+
+    Platform.MenuSeparator {
+        text: qsTr("Subcommunities")
+        group: modificationGroup
+        visible: modificationGroup.visible && inst.model != undefined
+    }
+
+    Instantiator {
+        id: inst
+        model: spacesMenu.loadChildren ? Communities.spaceChildrenListFromIndex(spacesMenu.roomid, spacesMenu.position) : undefined
+        onObjectAdded: (idx, o) => {
+            spacesMenu.insertMenu(idx + (spacesMenu.position != -1 ? 6 : 0), o)
+        }
+        //onObjectRemoved: spacesMenu.removeMenu(object)
+
+        delegate: childMenu
+    }
+}
diff --git a/resources/res.qrc b/resources/res.qrc
index 4bdb3cb8..c14ebd5f 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -120,16 +120,14 @@
         <file>qml/SelfVerificationCheck.qml</file>
         <file>qml/TypingIndicator.qml</file>
         <file>qml/NotificationWarning.qml</file>
-        <file>qml/pages/UserSettingsPage.qml</file>
-        <file>qml/pages/WelcomePage.qml</file>
-        <file>qml/pages/LoginPage.qml</file>
-        <file>qml/pages/RegisterPage.qml</file>
         <file>qml/components/AdaptiveLayout.qml</file>
         <file>qml/components/AdaptiveLayoutElement.qml</file>
         <file>qml/components/AvatarListTile.qml</file>
         <file>qml/components/FlatButton.qml</file>
         <file>qml/components/MainWindowDialog.qml</file>
+        <file>qml/components/NotificationBubble.qml</file>
         <file>qml/components/ReorderableListview.qml</file>
+        <file>qml/components/SpaceMenuLevel.qml</file>
         <file>qml/components/TextButton.qml</file>
         <file>qml/delegates/Encrypted.qml</file>
         <file>qml/delegates/FileMessage.qml</file>
@@ -172,10 +170,14 @@
         <file>qml/dialogs/UserProfile.qml</file>
         <file>qml/emoji/EmojiPicker.qml</file>
         <file>qml/emoji/StickerPicker.qml</file>
+        <file>qml/pages/LoginPage.qml</file>
+        <file>qml/pages/RegisterPage.qml</file>
+        <file>qml/pages/UserSettingsPage.qml</file>
+        <file>qml/pages/WelcomePage.qml</file>
         <file>qml/ui/NhekoSlider.qml</file>
         <file>qml/ui/Ripple.qml</file>
-        <file>qml/ui/Spinner.qml</file>
         <file>qml/ui/Snackbar.qml</file>
+        <file>qml/ui/Spinner.qml</file>
         <file>qml/ui/animations/BlinkAnimation.qml</file>
         <file>qml/ui/media/MediaControls.qml</file>
         <file>qml/voip/ActiveCallBar.qml</file>
@@ -186,7 +188,6 @@
         <file>qml/voip/PlaceCall.qml</file>
         <file>qml/voip/ScreenShare.qml</file>
         <file>qml/voip/VideoCall.qml</file>
-        <file>qml/components/NotificationBubble.qml</file>
     </qresource>
     <qresource prefix="/media">
         <file>media/ring.ogg</file>
diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp
index 7b267bcb..6d60c2b9 100644
--- a/src/timeline/CommunitiesModel.cpp
+++ b/src/timeline/CommunitiesModel.cpp
@@ -5,20 +5,29 @@
 
 #include "CommunitiesModel.h"
 
+#include <mtx/responses/common.hpp>
 #include <set>
 
 #include "Cache.h"
 #include "Cache_p.h"
 #include "ChatPage.h"
 #include "Logging.h"
+#include "MatrixClient.h"
+#include "Permissions.h"
 #include "UserSettingsPage.h"
 #include "Utils.h"
+#include "timeline/TimelineModel.h"
+
+Q_DECLARE_METATYPE(SpaceItem)
 
 CommunitiesModel::CommunitiesModel(QObject *parent)
   : QAbstractListModel(parent)
   , hiddenTagIds_{UserSettings::instance()->hiddenTags()}
   , mutedTagIds_{UserSettings::instance()->mutedTags()}
-{}
+{
+    static auto ignore = qRegisterMetaType<SpaceItem>();
+    (void)ignore;
+}
 
 QHash<int, QByteArray>
 CommunitiesModel::roleNames() const
@@ -723,3 +732,156 @@ FilteredCommunitiesModel::filterAcceptsRow(int sourceRow, const QModelIndex &) c
 
     return true;
 }
+
+QVariantList
+CommunitiesModel::spaceChildrenListFromIndex(QString room, int idx) const
+{
+    if (idx < -1)
+        return {};
+
+    auto room_ = room.toStdString();
+
+    int begin = idx + 1;
+    int end   = idx >= 0 ? this->spaceOrder_.lastChild(idx) + 1 : this->spaceOrder_.size();
+    QVariantList ret;
+
+    bool canSendParent = Permissions(room).canChange(qml_mtx_events::SpaceParent);
+
+    for (int i = begin; i < end; i++) {
+        const auto &e = spaceOrder_.tree[i];
+        if (e.depth == spaceOrder_.tree[begin].depth && spaces_.count(e.id)) {
+            bool canSendChild = Permissions(e.id).canChange(qml_mtx_events::SpaceChild);
+            auto spaceId      = e.id.toStdString();
+            auto child =
+              cache::client()->getStateEvent<mtx::events::state::space::Child>(spaceId, room_);
+            auto parent =
+              cache::client()->getStateEvent<mtx::events::state::space::Parent>(room_, spaceId);
+
+            bool childValid =
+              child && !child->content.via.value_or(std::vector<std::string>{}).empty();
+            bool parentValid =
+              parent && !parent->content.via.value_or(std::vector<std::string>{}).empty();
+            bool canonical = parent && parent->content.canonical;
+
+            if (e.id == room) {
+                canonical = parentValid = childValid = canSendChild = canSendParent = false;
+            }
+
+            ret.push_back(
+              QVariant::fromValue(SpaceItem(e.id,
+                                            QString::fromStdString(spaces_.at(e.id).name),
+                                            i,
+                                            childValid,
+                                            parentValid,
+                                            canonical,
+                                            canSendChild,
+                                            canSendParent)));
+        }
+    }
+
+    nhlog::ui()->critical("Returning {} spaces", ret.size());
+    return ret;
+}
+
+void
+CommunitiesModel::updateSpaceStatus(QString space,
+                                    QString room,
+                                    bool setParent,
+                                    bool setChild,
+                                    bool canonical) const
+{
+    nhlog::ui()->critical("Setting space {} children {}: {} {} {}",
+                          space.toStdString(),
+                          room.toStdString(),
+                          setParent,
+                          setChild,
+                          canonical);
+    auto child =
+      cache::client()
+        ->getStateEvent<mtx::events::state::space::Child>(space.toStdString(), room.toStdString())
+        .value_or(mtx::events::StateEvent<mtx::events::state::space::Child>{})
+        .content;
+    auto parent =
+      cache::client()
+        ->getStateEvent<mtx::events::state::space::Parent>(room.toStdString(), space.toStdString())
+        .value_or(mtx::events::StateEvent<mtx::events::state::space::Parent>{})
+        .content;
+
+    if (setChild) {
+        if (!child.via || child.via->empty()) {
+            child.via       = utils::roomVias(room.toStdString());
+            child.suggested = true;
+
+            http::client()->send_state_event(
+              space.toStdString(),
+              room.toStdString(),
+              child,
+              [space, room](mtx::responses::EventId, mtx::http::RequestErr err) {
+                  if (err) {
+                      ChatPage::instance()->showNotification(
+                        tr("Failed to update space child: %1")
+                          .arg(QString::fromStdString(err->matrix_error.error)));
+                      nhlog::net()->error("Failed to update child {} of {}: {}",
+                                          room.toStdString(),
+                                          space.toStdString());
+                  }
+              });
+        }
+    } else {
+        if (child.via && !child.via->empty()) {
+            http::client()->send_state_event(
+              space.toStdString(),
+              room.toStdString(),
+              mtx::events::state::space::Child{},
+              [space, room](mtx::responses::EventId, mtx::http::RequestErr err) {
+                  if (err) {
+                      ChatPage::instance()->showNotification(
+                        tr("Failed to delete space child: %1")
+                          .arg(QString::fromStdString(err->matrix_error.error)));
+                      nhlog::net()->error("Failed to delete child {} of {}: {}",
+                                          room.toStdString(),
+                                          space.toStdString());
+                  }
+              });
+        }
+    }
+
+    if (setParent) {
+        if (!parent.via || parent.via->empty() || canonical != parent.canonical) {
+            parent.via       = utils::roomVias(room.toStdString());
+            parent.canonical = canonical;
+
+            http::client()->send_state_event(
+              room.toStdString(),
+              space.toStdString(),
+              parent,
+              [space, room](mtx::responses::EventId, mtx::http::RequestErr err) {
+                  if (err) {
+                      ChatPage::instance()->showNotification(
+                        tr("Failed to update space parent: %1")
+                          .arg(QString::fromStdString(err->matrix_error.error)));
+                      nhlog::net()->error("Failed to update parent {} of {}: {}",
+                                          space.toStdString(),
+                                          room.toStdString());
+                  }
+              });
+        }
+    } else {
+        if (parent.via && !parent.via->empty()) {
+            http::client()->send_state_event(
+              room.toStdString(),
+              space.toStdString(),
+              mtx::events::state::space::Parent{},
+              [space, room](mtx::responses::EventId, mtx::http::RequestErr err) {
+                  if (err) {
+                      ChatPage::instance()->showNotification(
+                        tr("Failed to delete space parent: %1")
+                          .arg(QString::fromStdString(err->matrix_error.error)));
+                      nhlog::net()->error("Failed to delete parent {} of {}: {}",
+                                          space.toStdString(),
+                                          room.toStdString());
+                  }
+              });
+        }
+    }
+}
diff --git a/src/timeline/CommunitiesModel.h b/src/timeline/CommunitiesModel.h
index 85e65dd7..89f1ed07 100644
--- a/src/timeline/CommunitiesModel.h
+++ b/src/timeline/CommunitiesModel.h
@@ -29,6 +29,47 @@ public:
     bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override;
 };
 
+class SpaceItem
+{
+    Q_GADGET
+
+    Q_PROPERTY(QString roomid MEMBER roomid CONSTANT)
+    Q_PROPERTY(QString name MEMBER name CONSTANT)
+    Q_PROPERTY(int treeIndex MEMBER treeIndex CONSTANT)
+
+    Q_PROPERTY(bool childValid MEMBER childValid CONSTANT)
+    Q_PROPERTY(bool parentValid MEMBER parentValid CONSTANT)
+    Q_PROPERTY(bool canonical MEMBER canonical CONSTANT)
+
+    Q_PROPERTY(bool canEditParent MEMBER canEditParent CONSTANT)
+    Q_PROPERTY(bool canEditChild MEMBER canEditChild CONSTANT)
+
+public:
+    SpaceItem() {}
+    SpaceItem(QString roomid_,
+              QString name_,
+              int treeIndex_,
+              bool childValid_,
+              bool parentValid_,
+              bool canonical_,
+              bool canEditChild_,
+              bool canEditParent_)
+      : roomid(std::move(roomid_))
+      , name(std::move(name_))
+      , treeIndex(treeIndex_)
+      , childValid(childValid_)
+      , parentValid(parentValid_)
+      , canonical(canonical_)
+      , canEditParent(canEditParent_)
+      , canEditChild(canEditChild_)
+    {}
+
+    QString roomid, name;
+    int treeIndex   = 0;
+    bool childValid = false, parentValid = false, canonical = false;
+    bool canEditParent = false, canEditChild = false;
+};
+
 class CommunitiesModel : public QAbstractListModel
 {
     Q_OBJECT
@@ -125,6 +166,13 @@ public:
         return false;
     }
 
+    Q_INVOKABLE QVariantList spaceChildrenListFromIndex(QString room, int idx = -1) const;
+    Q_INVOKABLE void updateSpaceStatus(QString space,
+                                       QString room,
+                                       bool setParent,
+                                       bool setChild,
+                                       bool canonical) const;
+
 public slots:
     void initializeSidebar();
     void sync(const mtx::responses::Sync &sync_);
@@ -148,6 +196,7 @@ public slots:
     }
     void toggleTagId(QString tagId);
     void toggleTagMute(QString tagId);
+
     FilteredCommunitiesModel *filtered() { return new FilteredCommunitiesModel(this, this); }
 
 signals:
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index fe4e7850..03abd3d5 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -546,6 +546,13 @@ RoomlistModel::sync(const mtx::responses::Sync &sync_)
                 }
             }
         }
+        for (const auto &e : room.account_data.events) {
+            if (std::holds_alternative<
+                  mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) {
+                if (auto idx = roomidToIndex(qroomid); idx != -1)
+                    emit dataChanged(index(idx), index(idx), {Tags});
+            }
+        }
     }
 
     for (const auto &[room_id, room] : sync_.rooms.leave) {