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 @@
qml/SelfVerificationCheck.qml
qml/TypingIndicator.qml
qml/NotificationWarning.qml
- qml/pages/UserSettingsPage.qml
- qml/pages/WelcomePage.qml
- qml/pages/LoginPage.qml
- qml/pages/RegisterPage.qml
qml/components/AdaptiveLayout.qml
qml/components/AdaptiveLayoutElement.qml
qml/components/AvatarListTile.qml
qml/components/FlatButton.qml
qml/components/MainWindowDialog.qml
+ qml/components/NotificationBubble.qml
qml/components/ReorderableListview.qml
+ qml/components/SpaceMenuLevel.qml
qml/components/TextButton.qml
qml/delegates/Encrypted.qml
qml/delegates/FileMessage.qml
@@ -172,10 +170,14 @@
qml/dialogs/UserProfile.qml
qml/emoji/EmojiPicker.qml
qml/emoji/StickerPicker.qml
+ qml/pages/LoginPage.qml
+ qml/pages/RegisterPage.qml
+ qml/pages/UserSettingsPage.qml
+ qml/pages/WelcomePage.qml
qml/ui/NhekoSlider.qml
qml/ui/Ripple.qml
- qml/ui/Spinner.qml
qml/ui/Snackbar.qml
+ qml/ui/Spinner.qml
qml/ui/animations/BlinkAnimation.qml
qml/ui/media/MediaControls.qml
qml/voip/ActiveCallBar.qml
@@ -186,7 +188,6 @@
qml/voip/PlaceCall.qml
qml/voip/ScreenShare.qml
qml/voip/VideoCall.qml
- qml/components/NotificationBubble.qml
media/ring.ogg
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
#include
#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();
+ (void)ignore;
+}
QHash
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(spaceId, room_);
+ auto parent =
+ cache::client()->getStateEvent(room_, spaceId);
+
+ bool childValid =
+ child && !child->content.via.value_or(std::vector{}).empty();
+ bool parentValid =
+ parent && !parent->content.via.value_or(std::vector{}).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(space.toStdString(), room.toStdString())
+ .value_or(mtx::events::StateEvent{})
+ .content;
+ auto parent =
+ cache::client()
+ ->getStateEvent(room.toStdString(), space.toStdString())
+ .value_or(mtx::events::StateEvent{})
+ .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>(e)) {
+ if (auto idx = roomidToIndex(qroomid); idx != -1)
+ emit dataChanged(index(idx), index(idx), {Tags});
+ }
+ }
}
for (const auto &[room_id, room] : sync_.rooms.leave) {