Support editing space children

pull/1155/head
Nicolas Werner 2 years ago
parent 68c02da6c8
commit 376612e4eb
No known key found for this signature in database
GPG Key ID: C8D75E610773F2D9
  1. 10
      CMakeLists.txt
  2. 6
      cmake/Hunter/config.cmake
  3. 2
      io.github.NhekoReborn.Nheko.yaml
  4. 78
      resources/qml/RoomList.qml
  5. 84
      resources/qml/components/SpaceMenuLevel.qml
  6. 13
      resources/res.qrc
  7. 164
      src/timeline/CommunitiesModel.cpp
  8. 49
      src/timeline/CommunitiesModel.h
  9. 7
      src/timeline/RoomlistModel.cpp

@ -26,10 +26,10 @@ set(CMAKE_AUTOMOC ON)
option(HUNTER_ENABLED "Enable Hunter package manager" OFF) option(HUNTER_ENABLED "Enable Hunter package manager" OFF)
include("cmake/HunterGate.cmake") include("cmake/HunterGate.cmake")
HunterGate( HunterGate(
URL "https://github.com/cpp-pm/hunter/archive/v0.23.305.tar.gz" URL "https://github.com/cpp-pm/hunter/archive/v0.24.3.tar.gz"
SHA1 "fc8d7a6dac2fa23681847b3872d88d3839b657b0" SHA1 "10738b59e539818a01090e64c2d09896247530c7"
LOCAL LOCAL
) )
macro(hunter_add_package_safe) macro(hunter_add_package_safe)
set(pkg_temp_backup_libdir "$ENV{PKG_CONFIG_LIBDIR}") set(pkg_temp_backup_libdir "$ENV{PKG_CONFIG_LIBDIR}")
@ -583,7 +583,7 @@ if(USE_BUNDLED_MTXCLIENT)
FetchContent_Declare( FetchContent_Declare(
MatrixClient MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git 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_EXAMPLES OFF CACHE INTERNAL "")
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")

@ -1,9 +1,3 @@
hunter_config(
spdlog
VERSION 1.8.0-p1
)
hunter_config( hunter_config(
lmdb lmdb
VERSION 0.9.21-p2 VERSION 0.9.21-p2

@ -203,7 +203,7 @@ modules:
buildsystem: cmake-ninja buildsystem: cmake-ninja
name: mtxclient name: mtxclient
sources: sources:
- commit: b706492de042455630063c847574bbc5ed5d4641 - commit: eb747bb7723c11e38ed21543d05c42cc883c9d06
#tag: v0.8.0 #tag: v0.8.0
type: git type: git
url: https://github.com/Nheko-Reborn/mtxclient.git url: https://github.com/Nheko-Reborn/mtxclient.git

@ -110,6 +110,17 @@ Page {
} }
Component {
id: nestedSpaceMenuLevel
SpaceMenuLevel {
roomid: roomContextMenu.roomid
childMenu: rootSpaceMenu.childMenu
}
}
Platform.Menu { Platform.Menu {
id: roomContextMenu id: roomContextMenu
@ -154,42 +165,51 @@ Page {
onTriggered: Rooms.copyLink(roomContextMenu.roomid) onTriggered: Rooms.copyLink(roomContextMenu.roomid)
} }
Platform.MenuSeparator { Platform.Menu {
text: qsTr("Tag room as:") id: tagsMenu
} title: qsTr("Tag room as:")
Instantiator { Instantiator {
model: Communities.tagsWithDefault model: Communities.tagsWithDefault
onObjectAdded: roomContextMenu.insertItem(index + 4, object) onObjectAdded: tagsMenu.insertItem(index, object)
onObjectRemoved: roomContextMenu.removeItem(object) onObjectRemoved: tagsMenu.removeItem(object)
delegate: Platform.MenuItem { delegate: Platform.MenuItem {
property string t: modelData property string t: modelData
text: { text: {
switch (t) { switch (t) {
case "m.favourite": case "m.favourite":
return qsTr("Favourite"); return qsTr("Favourite");
case "m.lowpriority": case "m.lowpriority":
return qsTr("Low priority"); return qsTr("Low priority");
case "m.server_notice": case "m.server_notice":
return qsTr("Server notice"); return qsTr("Server notice");
default: default:
return t.substring(2); 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 { SpaceMenuLevel {
text: qsTr("Create new tag...") id: rootSpaceMenu
onTriggered: newTag.show()
}
roomid: roomContextMenu.roomid
position: -1
title: qsTr("Add or remove from space")
childMenu: nestedSpaceMenuLevel
}
} }
delegate: ItemDelegate { delegate: ItemDelegate {

@ -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
}
}

@ -120,16 +120,14 @@
<file>qml/SelfVerificationCheck.qml</file> <file>qml/SelfVerificationCheck.qml</file>
<file>qml/TypingIndicator.qml</file> <file>qml/TypingIndicator.qml</file>
<file>qml/NotificationWarning.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/AdaptiveLayout.qml</file>
<file>qml/components/AdaptiveLayoutElement.qml</file> <file>qml/components/AdaptiveLayoutElement.qml</file>
<file>qml/components/AvatarListTile.qml</file> <file>qml/components/AvatarListTile.qml</file>
<file>qml/components/FlatButton.qml</file> <file>qml/components/FlatButton.qml</file>
<file>qml/components/MainWindowDialog.qml</file> <file>qml/components/MainWindowDialog.qml</file>
<file>qml/components/NotificationBubble.qml</file>
<file>qml/components/ReorderableListview.qml</file> <file>qml/components/ReorderableListview.qml</file>
<file>qml/components/SpaceMenuLevel.qml</file>
<file>qml/components/TextButton.qml</file> <file>qml/components/TextButton.qml</file>
<file>qml/delegates/Encrypted.qml</file> <file>qml/delegates/Encrypted.qml</file>
<file>qml/delegates/FileMessage.qml</file> <file>qml/delegates/FileMessage.qml</file>
@ -172,10 +170,14 @@
<file>qml/dialogs/UserProfile.qml</file> <file>qml/dialogs/UserProfile.qml</file>
<file>qml/emoji/EmojiPicker.qml</file> <file>qml/emoji/EmojiPicker.qml</file>
<file>qml/emoji/StickerPicker.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/NhekoSlider.qml</file>
<file>qml/ui/Ripple.qml</file> <file>qml/ui/Ripple.qml</file>
<file>qml/ui/Spinner.qml</file>
<file>qml/ui/Snackbar.qml</file> <file>qml/ui/Snackbar.qml</file>
<file>qml/ui/Spinner.qml</file>
<file>qml/ui/animations/BlinkAnimation.qml</file> <file>qml/ui/animations/BlinkAnimation.qml</file>
<file>qml/ui/media/MediaControls.qml</file> <file>qml/ui/media/MediaControls.qml</file>
<file>qml/voip/ActiveCallBar.qml</file> <file>qml/voip/ActiveCallBar.qml</file>
@ -186,7 +188,6 @@
<file>qml/voip/PlaceCall.qml</file> <file>qml/voip/PlaceCall.qml</file>
<file>qml/voip/ScreenShare.qml</file> <file>qml/voip/ScreenShare.qml</file>
<file>qml/voip/VideoCall.qml</file> <file>qml/voip/VideoCall.qml</file>
<file>qml/components/NotificationBubble.qml</file>
</qresource> </qresource>
<qresource prefix="/media"> <qresource prefix="/media">
<file>media/ring.ogg</file> <file>media/ring.ogg</file>

@ -5,20 +5,29 @@
#include "CommunitiesModel.h" #include "CommunitiesModel.h"
#include <mtx/responses/common.hpp>
#include <set> #include <set>
#include "Cache.h" #include "Cache.h"
#include "Cache_p.h" #include "Cache_p.h"
#include "ChatPage.h" #include "ChatPage.h"
#include "Logging.h" #include "Logging.h"
#include "MatrixClient.h"
#include "Permissions.h"
#include "UserSettingsPage.h" #include "UserSettingsPage.h"
#include "Utils.h" #include "Utils.h"
#include "timeline/TimelineModel.h"
Q_DECLARE_METATYPE(SpaceItem)
CommunitiesModel::CommunitiesModel(QObject *parent) CommunitiesModel::CommunitiesModel(QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
, hiddenTagIds_{UserSettings::instance()->hiddenTags()} , hiddenTagIds_{UserSettings::instance()->hiddenTags()}
, mutedTagIds_{UserSettings::instance()->mutedTags()} , mutedTagIds_{UserSettings::instance()->mutedTags()}
{} {
static auto ignore = qRegisterMetaType<SpaceItem>();
(void)ignore;
}
QHash<int, QByteArray> QHash<int, QByteArray>
CommunitiesModel::roleNames() const CommunitiesModel::roleNames() const
@ -723,3 +732,156 @@ FilteredCommunitiesModel::filterAcceptsRow(int sourceRow, const QModelIndex &) c
return true; 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());
}
});
}
}
}

@ -29,6 +29,47 @@ public:
bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override; 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 class CommunitiesModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
@ -125,6 +166,13 @@ public:
return false; 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: public slots:
void initializeSidebar(); void initializeSidebar();
void sync(const mtx::responses::Sync &sync_); void sync(const mtx::responses::Sync &sync_);
@ -148,6 +196,7 @@ public slots:
} }
void toggleTagId(QString tagId); void toggleTagId(QString tagId);
void toggleTagMute(QString tagId); void toggleTagMute(QString tagId);
FilteredCommunitiesModel *filtered() { return new FilteredCommunitiesModel(this, this); } FilteredCommunitiesModel *filtered() { return new FilteredCommunitiesModel(this, this); }
signals: signals:

@ -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) { for (const auto &[room_id, room] : sync_.rooms.leave) {

Loading…
Cancel
Save