From aab69c5baed25997972170167467735454f131ea Mon Sep 17 00:00:00 2001 From: James Graham Date: Sat, 11 Nov 2023 13:32:19 +0000 Subject: [PATCH] Suggested rooms spaces Add the ability to set and show suggested rooms for spaces. This is just adding the basic functionality, we can do more things with it later like sort/filter the space home for example. --- src/models/spacechildrenmodel.cpp | 22 +++++++++++------- src/models/spacechildrenmodel.h | 1 + src/models/spacetreeitem.cpp | 37 ++++++++++++++++++++++++++++-- src/models/spacetreeitem.h | 24 +++++++++++++++++-- src/neochatroom.cpp | 28 ++++++++++++++++++++-- src/neochatroom.h | 15 +++++++++++- src/qml/SpaceHierarchyDelegate.qml | 18 ++++++++++++--- 7 files changed, 127 insertions(+), 18 deletions(-) diff --git a/src/models/spacechildrenmodel.cpp b/src/models/spacechildrenmodel.cpp index 7f49fde65..a7503ec67 100644 --- a/src/models/spacechildrenmodel.cpp +++ b/src/models/spacechildrenmodel.cpp @@ -86,6 +86,7 @@ void SpaceChildrenModel::insertChildren(std::vectorid() || children[0].roomId == parentItem->id()) { + parentItem->setChildStates(std::move(children[0].childrenState)); children.erase(children.begin()); } @@ -112,6 +113,13 @@ void SpaceChildrenModel::insertChildren(std::vector 0) { + auto job = m_space->connection()->callApi(children[i].roomId, Quotient::none, Quotient::none, 1); + m_currentJobs.append(job); + connect(job, &Quotient::BaseJob::success, this, [this, parent, insertRow, job]() { + insertChildren(job->rooms(), index(insertRow, 0, parent)); + }); + } parentItem->insertChild(insertRow, new SpaceTreeItem(dynamic_cast(m_space->connection()), parentItem, @@ -123,14 +131,8 @@ void SpaceChildrenModel::insertChildren(std::vector 0) { - auto job = m_space->connection()->callApi(children[i].roomId, Quotient::none, Quotient::none, 1); - m_currentJobs.append(job); - connect(job, &Quotient::BaseJob::success, this, [this, parent, insertRow, job]() { - insertChildren(job->rooms(), index(insertRow, 0, parent)); - }); - } + children[i].roomType == QLatin1String("m.space"), + std::move(children[i].childrenState))); } } endInsertRows(); @@ -194,6 +196,9 @@ QVariant SpaceChildrenModel::data(const QModelIndex &index, int role) const if (role == IsSpaceRole) { return child->isSpace(); } + if (role == IsSuggestedRole) { + return child->isSuggested(); + } if (role == CanAddChildrenRole) { if (const auto room = static_cast(m_space->connection()->room(child->id()))) { return room->canSendState(QLatin1String("m.space.child")); @@ -313,6 +318,7 @@ QHash SpaceChildrenModel::roleNames() const roles[IsJoinedRole] = "isJoined"; roles[AliasRole] = "alias"; roles[IsSpaceRole] = "isSpace"; + roles[IsSuggestedRole] = "isSuggested"; roles[CanAddChildrenRole] = "canAddChildren"; roles[ParentDisplayNameRole] = "parentDisplayName"; roles[CanSetParentRole] = "canSetParent"; diff --git a/src/models/spacechildrenmodel.h b/src/models/spacechildrenmodel.h index 36925152d..bbea62fc8 100644 --- a/src/models/spacechildrenmodel.h +++ b/src/models/spacechildrenmodel.h @@ -44,6 +44,7 @@ public: WorldReadableRole, IsJoinedRole, IsSpaceRole, + IsSuggestedRole, CanAddChildrenRole, ParentDisplayNameRole, CanSetParentRole, diff --git a/src/models/spacetreeitem.cpp b/src/models/spacetreeitem.cpp index 036546d14..ebc50c5df 100644 --- a/src/models/spacetreeitem.cpp +++ b/src/models/spacetreeitem.cpp @@ -15,7 +15,8 @@ SpaceTreeItem::SpaceTreeItem(NeoChatConnection *connection, const QUrl &avatarUrl, bool allowGuests, bool worldReadable, - bool isSpace) + bool isSpace, + Quotient::StateEvents childStates) : m_connection(connection) , m_parentItem(parent) , m_id(id) @@ -27,6 +28,7 @@ SpaceTreeItem::SpaceTreeItem(NeoChatConnection *connection, , m_allowGuests(allowGuests) , m_worldReadable(worldReadable) , m_isSpace(isSpace) + , m_childStates(std::move(childStates)) { } @@ -74,7 +76,7 @@ int SpaceTreeItem::row() const return 0; } -SpaceTreeItem *SpaceTreeItem::parentItem() +SpaceTreeItem *SpaceTreeItem::parentItem() const { return m_parentItem; } @@ -138,3 +140,34 @@ bool SpaceTreeItem::isSpace() const { return m_isSpace; } + +QJsonObject SpaceTreeItem::childStateContent(const SpaceTreeItem *child) const +{ + if (child == nullptr) { + return {}; + } + if (child->parentItem() != this) { + return {}; + } + for (const auto &childState : m_childStates) { + if (childState->stateKey() == child->id()) { + return childState->contentJson(); + } + } + return {}; +} + +void SpaceTreeItem::setChildStates(Quotient::StateEvents childStates) +{ + m_childStates.clear(); + m_childStates = std::move(childStates); +} + +bool SpaceTreeItem::isSuggested() const +{ + if (m_parentItem == nullptr) { + return false; + } + const auto childStateContent = m_parentItem->childStateContent(this); + return childStateContent.value(QLatin1String("suggested")).toBool(); +} diff --git a/src/models/spacetreeitem.h b/src/models/spacetreeitem.h index bfdfe87ab..d49d74fe3 100644 --- a/src/models/spacetreeitem.h +++ b/src/models/spacetreeitem.h @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL #include +#include class NeoChatConnection; @@ -30,7 +31,8 @@ public: const QUrl &avatarUrl = {}, bool allowGuests = {}, bool worldReadable = {}, - bool isSpace = {}); + bool isSpace = {}, + Quotient::StateEvents childStates = {}); ~SpaceTreeItem(); /** @@ -60,7 +62,7 @@ public: /** * @brief Return this item's parent. */ - SpaceTreeItem *parentItem(); + SpaceTreeItem *parentItem() const; /** * @brief Return the row number for this child relative to the parent. @@ -123,6 +125,23 @@ public: */ bool isSpace() const; + /** + * @brief Return the m.space.child state event content for the given child. + */ + QJsonObject childStateContent(const SpaceTreeItem *child) const; + + /** + * @brief Set the list of m.space.child events. + * + * Overwrites existing states. Calling with no input will clear the existing states. + */ + void setChildStates(Quotient::StateEvents childStates = {}); + + /** + * @brief Whether the room is suggested in the parent space. + */ + bool isSuggested() const; + private: NeoChatConnection *m_connection; QList m_children; @@ -137,4 +156,5 @@ private: bool m_allowGuests; bool m_worldReadable; bool m_isSpace; + Quotient::StateEvents m_childStates; }; diff --git a/src/neochatroom.cpp b/src/neochatroom.cpp index f9c9f4191..f2de2b987 100644 --- a/src/neochatroom.cpp +++ b/src/neochatroom.cpp @@ -1304,7 +1304,7 @@ bool NeoChatRoom::isSpace() return creationEvent->roomType() == RoomType::Space; } -void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool canonical) +void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool canonical, bool suggested) { if (!isSpace()) { return; @@ -1312,7 +1312,7 @@ void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool can if (!canSendEvent("m.space.child"_ls)) { return; } - setState("m.space.child"_ls, childId, QJsonObject{{QLatin1String("via"), QJsonArray{connection()->domain()}}}); + setState("m.space.child"_ls, childId, QJsonObject{{QLatin1String("via"), QJsonArray{connection()->domain()}}, {"suggested"_ls, suggested}}); if (setChildParent) { if (auto child = static_cast(connection()->room(childId))) { @@ -1354,6 +1354,30 @@ void NeoChatRoom::removeChild(const QString &childId, bool unsetChildParent) } } +bool NeoChatRoom::isSuggested(const QString &childId) +{ + if (!currentState().contains("m.space.child"_ls, childId)) { + return false; + } + const auto childEvent = currentState().get("m.space.child"_ls, childId); + return childEvent->contentPart("suggested"_ls); +} + +void NeoChatRoom::toggleChildSuggested(const QString &childId) +{ + if (!isSpace()) { + return; + } + if (!canSendEvent("m.space.child"_ls)) { + return; + } + if (const auto childEvent = currentState().get("m.space.child"_ls, childId)) { + auto content = childEvent->contentJson(); + content.insert("suggested"_ls, !childEvent->contentPart("suggested"_ls)); + setState("m.space.child"_ls, childId, content); + } +} + PushNotificationState::State NeoChatRoom::pushNotificationState() const { return m_currentPushNotificationState; diff --git a/src/neochatroom.h b/src/neochatroom.h index 360a8c76e..bb68692f9 100644 --- a/src/neochatroom.h +++ b/src/neochatroom.h @@ -562,7 +562,7 @@ public: * Will fail if the user doesn't have the required privileges or this room is * not a space. */ - Q_INVOKABLE void addChild(const QString &childId, bool setChildParent = false, bool canonical = false); + Q_INVOKABLE void addChild(const QString &childId, bool setChildParent = false, bool canonical = false, bool suggested = false); /** * @brief Remove the given room as a child. @@ -572,6 +572,19 @@ public: */ Q_INVOKABLE void removeChild(const QString &childId, bool unsetChildParent = false); + /** + * @brief Whether the given child is a suggested room in the space. + */ + Q_INVOKABLE bool isSuggested(const QString &childId); + + /** + * @brief Toggle whether the given child is a suggested room in the space. + * + * Will fail if the user doesn't have the required privileges, this room is + * not a space or the given room is not a child of this space. + */ + Q_INVOKABLE void toggleChildSuggested(const QString &childId); + bool isInvite() const; Q_INVOKABLE void clearInvitationNotification(); diff --git a/src/qml/SpaceHierarchyDelegate.qml b/src/qml/SpaceHierarchyDelegate.qml index 9852e7977..dd7c552ac 100644 --- a/src/qml/SpaceHierarchyDelegate.qml +++ b/src/qml/SpaceHierarchyDelegate.qml @@ -22,6 +22,7 @@ Item { required property string displayName required property url avatarUrl required property bool isSpace + required property bool isSuggested required property int memberCount required property string topic required property bool isJoined @@ -79,9 +80,9 @@ Item { textFormat: Text.PlainText } QQC2.Label { - visible: root.isJoined - text: i18n("Joined") - color: Kirigami.Theme.linkColor + visible: root.isJoined || root.isSuggested + text: root.isJoined ? i18n("Joined") : i18n("Suggested") + color: root.isJoined ? Kirigami.Theme.linkColor : Kirigami.Theme.disabledTextColor } } QQC2.Label { @@ -123,6 +124,17 @@ Item { }).open(); } + QQC2.ToolTip.text: text + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } + QQC2.ToolButton { + visible: root.parentRoom?.canSendState("m.space.child") ?? false + text: root.isSuggested ? i18nc("@button", "Don't Make Suggested") : i18nc("@button", "Make Suggested") + icon.name: root.isSuggested ? "edit-delete-remove" : "checkmark" + display: QQC2.AbstractButton.IconOnly + onClicked: root.parentRoom.toggleChildSuggested(root.roomId) + QQC2.ToolTip.text: text QQC2.ToolTip.visible: hovered QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay