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.
This commit is contained in:
James Graham
2023-11-11 13:32:19 +00:00
parent 624578ec77
commit aab69c5bae
7 changed files with 127 additions and 18 deletions

View File

@@ -86,6 +86,7 @@ void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJ
SpaceTreeItem *parentItem = getItem(parent); SpaceTreeItem *parentItem = getItem(parent);
if (children[0].roomId == m_space->id() || children[0].roomId == parentItem->id()) { if (children[0].roomId == m_space->id() || children[0].roomId == parentItem->id()) {
parentItem->setChildStates(std::move(children[0].childrenState));
children.erase(children.begin()); children.erase(children.begin());
} }
@@ -112,6 +113,13 @@ void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJ
m_replacedRooms += successorId; m_replacedRooms += successorId;
} }
} }
if (children[i].childrenState.size() > 0) {
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(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, parentItem->insertChild(insertRow,
new SpaceTreeItem(dynamic_cast<NeoChatConnection *>(m_space->connection()), new SpaceTreeItem(dynamic_cast<NeoChatConnection *>(m_space->connection()),
parentItem, parentItem,
@@ -123,14 +131,8 @@ void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJ
children[i].avatarUrl, children[i].avatarUrl,
children[i].guestCanJoin, children[i].guestCanJoin,
children[i].worldReadable, children[i].worldReadable,
children[i].roomType == QLatin1String("m.space"))); children[i].roomType == QLatin1String("m.space"),
if (children[i].childrenState.size() > 0) { std::move(children[i].childrenState)));
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(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));
});
}
} }
} }
endInsertRows(); endInsertRows();
@@ -194,6 +196,9 @@ QVariant SpaceChildrenModel::data(const QModelIndex &index, int role) const
if (role == IsSpaceRole) { if (role == IsSpaceRole) {
return child->isSpace(); return child->isSpace();
} }
if (role == IsSuggestedRole) {
return child->isSuggested();
}
if (role == CanAddChildrenRole) { if (role == CanAddChildrenRole) {
if (const auto room = static_cast<NeoChatRoom *>(m_space->connection()->room(child->id()))) { if (const auto room = static_cast<NeoChatRoom *>(m_space->connection()->room(child->id()))) {
return room->canSendState(QLatin1String("m.space.child")); return room->canSendState(QLatin1String("m.space.child"));
@@ -313,6 +318,7 @@ QHash<int, QByteArray> SpaceChildrenModel::roleNames() const
roles[IsJoinedRole] = "isJoined"; roles[IsJoinedRole] = "isJoined";
roles[AliasRole] = "alias"; roles[AliasRole] = "alias";
roles[IsSpaceRole] = "isSpace"; roles[IsSpaceRole] = "isSpace";
roles[IsSuggestedRole] = "isSuggested";
roles[CanAddChildrenRole] = "canAddChildren"; roles[CanAddChildrenRole] = "canAddChildren";
roles[ParentDisplayNameRole] = "parentDisplayName"; roles[ParentDisplayNameRole] = "parentDisplayName";
roles[CanSetParentRole] = "canSetParent"; roles[CanSetParentRole] = "canSetParent";

View File

@@ -44,6 +44,7 @@ public:
WorldReadableRole, WorldReadableRole,
IsJoinedRole, IsJoinedRole,
IsSpaceRole, IsSpaceRole,
IsSuggestedRole,
CanAddChildrenRole, CanAddChildrenRole,
ParentDisplayNameRole, ParentDisplayNameRole,
CanSetParentRole, CanSetParentRole,

View File

@@ -15,7 +15,8 @@ SpaceTreeItem::SpaceTreeItem(NeoChatConnection *connection,
const QUrl &avatarUrl, const QUrl &avatarUrl,
bool allowGuests, bool allowGuests,
bool worldReadable, bool worldReadable,
bool isSpace) bool isSpace,
Quotient::StateEvents childStates)
: m_connection(connection) : m_connection(connection)
, m_parentItem(parent) , m_parentItem(parent)
, m_id(id) , m_id(id)
@@ -27,6 +28,7 @@ SpaceTreeItem::SpaceTreeItem(NeoChatConnection *connection,
, m_allowGuests(allowGuests) , m_allowGuests(allowGuests)
, m_worldReadable(worldReadable) , m_worldReadable(worldReadable)
, m_isSpace(isSpace) , m_isSpace(isSpace)
, m_childStates(std::move(childStates))
{ {
} }
@@ -74,7 +76,7 @@ int SpaceTreeItem::row() const
return 0; return 0;
} }
SpaceTreeItem *SpaceTreeItem::parentItem() SpaceTreeItem *SpaceTreeItem::parentItem() const
{ {
return m_parentItem; return m_parentItem;
} }
@@ -138,3 +140,34 @@ bool SpaceTreeItem::isSpace() const
{ {
return m_isSpace; 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();
}

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <Quotient/csapi/space_hierarchy.h> #include <Quotient/csapi/space_hierarchy.h>
#include <Quotient/events/stateevent.h>
class NeoChatConnection; class NeoChatConnection;
@@ -30,7 +31,8 @@ public:
const QUrl &avatarUrl = {}, const QUrl &avatarUrl = {},
bool allowGuests = {}, bool allowGuests = {},
bool worldReadable = {}, bool worldReadable = {},
bool isSpace = {}); bool isSpace = {},
Quotient::StateEvents childStates = {});
~SpaceTreeItem(); ~SpaceTreeItem();
/** /**
@@ -60,7 +62,7 @@ public:
/** /**
* @brief Return this item's parent. * @brief Return this item's parent.
*/ */
SpaceTreeItem *parentItem(); SpaceTreeItem *parentItem() const;
/** /**
* @brief Return the row number for this child relative to the parent. * @brief Return the row number for this child relative to the parent.
@@ -123,6 +125,23 @@ public:
*/ */
bool isSpace() const; 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: private:
NeoChatConnection *m_connection; NeoChatConnection *m_connection;
QList<SpaceTreeItem *> m_children; QList<SpaceTreeItem *> m_children;
@@ -137,4 +156,5 @@ private:
bool m_allowGuests; bool m_allowGuests;
bool m_worldReadable; bool m_worldReadable;
bool m_isSpace; bool m_isSpace;
Quotient::StateEvents m_childStates;
}; };

View File

@@ -1304,7 +1304,7 @@ bool NeoChatRoom::isSpace()
return creationEvent->roomType() == RoomType::Space; 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()) { if (!isSpace()) {
return; return;
@@ -1312,7 +1312,7 @@ void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool can
if (!canSendEvent("m.space.child"_ls)) { if (!canSendEvent("m.space.child"_ls)) {
return; 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 (setChildParent) {
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) { if (auto child = static_cast<NeoChatRoom *>(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<bool>("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<bool>("suggested"_ls));
setState("m.space.child"_ls, childId, content);
}
}
PushNotificationState::State NeoChatRoom::pushNotificationState() const PushNotificationState::State NeoChatRoom::pushNotificationState() const
{ {
return m_currentPushNotificationState; return m_currentPushNotificationState;

View File

@@ -562,7 +562,7 @@ public:
* Will fail if the user doesn't have the required privileges or this room is * Will fail if the user doesn't have the required privileges or this room is
* not a space. * 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. * @brief Remove the given room as a child.
@@ -572,6 +572,19 @@ public:
*/ */
Q_INVOKABLE void removeChild(const QString &childId, bool unsetChildParent = false); 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; bool isInvite() const;
Q_INVOKABLE void clearInvitationNotification(); Q_INVOKABLE void clearInvitationNotification();

View File

@@ -22,6 +22,7 @@ Item {
required property string displayName required property string displayName
required property url avatarUrl required property url avatarUrl
required property bool isSpace required property bool isSpace
required property bool isSuggested
required property int memberCount required property int memberCount
required property string topic required property string topic
required property bool isJoined required property bool isJoined
@@ -79,9 +80,9 @@ Item {
textFormat: Text.PlainText textFormat: Text.PlainText
} }
QQC2.Label { QQC2.Label {
visible: root.isJoined visible: root.isJoined || root.isSuggested
text: i18n("Joined") text: root.isJoined ? i18n("Joined") : i18n("Suggested")
color: Kirigami.Theme.linkColor color: root.isJoined ? Kirigami.Theme.linkColor : Kirigami.Theme.disabledTextColor
} }
} }
QQC2.Label { QQC2.Label {
@@ -123,6 +124,17 @@ Item {
}).open(); }).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.text: text
QQC2.ToolTip.visible: hovered QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay