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