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:") - } + Platform.Menu { + id: tagsMenu + title: qsTr("Tag room as:") - Instantiator { - model: Communities.tagsWithDefault - onObjectAdded: roomContextMenu.insertItem(index + 4, object) - onObjectRemoved: roomContextMenu.removeItem(object) + Instantiator { + model: Communities.tagsWithDefault + onObjectAdded: tagsMenu.insertItem(index, object) + onObjectRemoved: tagsMenu.removeItem(object) - delegate: Platform.MenuItem { - property string t: modelData + 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); + 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) {