From 1b59917f162b643fc9d7e11064d0ef3ab4a7861c Mon Sep 17 00:00:00 2001 From: James Graham Date: Sun, 18 Feb 2024 11:04:56 +0000 Subject: [PATCH] Space notification count Show the number of notifications for a space if it isn't selected. This respects choices like low priority only adding highlights. --- src/models/roomlistmodel.cpp | 45 ++++++++++++-------------- src/models/roomlistmodel.h | 5 +++ src/neochatconnection.cpp | 40 +++++++++++++++++++++++ src/neochatconnection.h | 7 ++++ src/neochatroom.cpp | 19 +++++++++++ src/neochatroom.h | 10 ++++++ src/qml/SpaceDrawer.qml | 62 +++++++++++++++++++++++++++++++++--- src/spacehierarchycache.cpp | 47 +++++++++++++++++++++++++++ src/spacehierarchycache.h | 11 +++++++ 9 files changed, 217 insertions(+), 29 deletions(-) diff --git a/src/models/roomlistmodel.cpp b/src/models/roomlistmodel.cpp index 94659d46d..4aef2b8f3 100644 --- a/src/models/roomlistmodel.cpp +++ b/src/models/roomlistmodel.cpp @@ -298,30 +298,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const return room->topic(); } if (role == CategoryRole) { - if (room->joinState() == JoinState::Invite) { - return NeoChatRoomType::Invited; - } - if (room->isFavourite()) { - return NeoChatRoomType::Favorite; - } - if (room->isLowPriority()) { - return NeoChatRoomType::Deprioritized; - } - if (room->isDirectChat()) { - return NeoChatRoomType::Direct; - } - const RoomCreateEvent *creationEvent = room->creation(); - if (!creationEvent) { - return NeoChatRoomType::Normal; - } - QJsonObject contentJson = creationEvent->contentJson(); - QJsonObject::const_iterator typeIter = contentJson.find("type"_ls); - if (typeIter != contentJson.end()) { - if (typeIter.value().toString() == "m.space"_ls) { - return NeoChatRoomType::Space; - } - } - return NeoChatRoomType::Normal; + return category(room); } if (role == NotificationCountRole) { return room->notificationCount(); @@ -406,6 +383,26 @@ QHash RoomListModel::roleNames() const return roles; } +NeoChatRoomType::Types RoomListModel::category(NeoChatRoom *room) +{ + if (room->isSpace()) { + return NeoChatRoomType::Space; + } + if (room->joinState() == JoinState::Invite) { + return NeoChatRoomType::Invited; + } + if (room->isFavourite()) { + return NeoChatRoomType::Favorite; + } + if (room->isLowPriority()) { + return NeoChatRoomType::Deprioritized; + } + if (room->isDirectChat()) { + return NeoChatRoomType::Direct; + } + return NeoChatRoomType::Normal; +} + QString RoomListModel::categoryName(int category) { switch (category) { diff --git a/src/models/roomlistmodel.h b/src/models/roomlistmodel.h index dab8b1a86..763d07e6b 100644 --- a/src/models/roomlistmodel.h +++ b/src/models/roomlistmodel.h @@ -116,6 +116,11 @@ public: */ Q_INVOKABLE [[nodiscard]] NeoChatRoom *roomAt(int row) const; + /** + * @brief The category for the given room. + */ + static NeoChatRoomType::Types category(NeoChatRoom *room); + /** * @brief Return a string to represent the given room category. */ diff --git a/src/neochatconnection.cpp b/src/neochatconnection.cpp index 0a02e47f7..e014e4818 100644 --- a/src/neochatconnection.cpp +++ b/src/neochatconnection.cpp @@ -8,7 +8,9 @@ #include "controller.h" #include "jobs/neochatchangepasswordjob.h" #include "jobs/neochatdeactivateaccountjob.h" +#include "neochatroom.h" #include "roommanager.h" +#include "spacehierarchycache.h" #include #include @@ -97,6 +99,21 @@ void NeoChatConnection::connectSignals() Q_EMIT directChatInvitesChanged(); } }); + + connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceHierarchyChanged, this, [this]() { + Q_EMIT homeNotificationsChanged(); + }); + for (const auto room : allRooms()) { + connect(room, &NeoChatRoom::unreadStatsChanged, this, [this, room]() { + if (room != nullptr) { + auto category = RoomListModel::category(static_cast(room)); + if (!SpaceHierarchyCache::instance().isChild(room->id()) && (category == NeoChatRoomType::Normal || category == NeoChatRoomType::Favorite) + && room->successorId().isEmpty()) { + Q_EMIT homeNotificationsChanged(); + } + } + }); + } } void NeoChatConnection::logout(bool serverSideLogout) @@ -319,6 +336,29 @@ qsizetype NeoChatConnection::directChatNotifications() const return notifications; } +qsizetype NeoChatConnection::homeNotifications() const +{ + qsizetype notifications = 0; + QStringList added; + const auto &spaceHierarchyCache = SpaceHierarchyCache::instance(); + for (const auto &room : allRooms()) { + auto category = RoomListModel::category(static_cast(room)); + if (!added.contains(room->id()) && room->joinState() == JoinState::Join && !room->isDirectChat() && !spaceHierarchyCache.isChild(room->id()) + && room->successorId().isEmpty()) { + switch (category) { + case NeoChatRoomType::Normal: + case NeoChatRoomType::Favorite: + notifications += room->notificationCount(); + break; + default: + notifications += room->highlightCount(); + } + added += room->id(); + } + } + return notifications; +} + bool NeoChatConnection::directChatInvites() const { auto inviteRooms = rooms(JoinState::Invite); diff --git a/src/neochatconnection.h b/src/neochatconnection.h index c4e05cf6d..945e7730d 100644 --- a/src/neochatconnection.h +++ b/src/neochatconnection.h @@ -32,6 +32,11 @@ class NeoChatConnection : public Quotient::Connection */ Q_PROPERTY(qsizetype directChatNotifications READ directChatNotifications NOTIFY directChatNotificationsChanged) + /** + * @brief The total number of notifications for all rooms in the home tab. + */ + Q_PROPERTY(qsizetype homeNotifications READ homeNotifications NOTIFY homeNotificationsChanged) + /** * @brief Whether there is at least one invite to a direct chat. */ @@ -109,6 +114,7 @@ public: Q_INVOKABLE void openOrCreateDirectChat(Quotient::User *user); qsizetype directChatNotifications() const; + qsizetype homeNotifications() const; bool directChatInvites() const; // note: this is intentionally a copied QString because @@ -123,6 +129,7 @@ public: Q_SIGNALS: void labelChanged(); void directChatNotificationsChanged(); + void homeNotificationsChanged(); void directChatInvitesChanged(); void isOnlineChanged(); void passwordStatus(NeoChatConnection::PasswordStatus status); diff --git a/src/neochatroom.cpp b/src/neochatroom.cpp index 20fc65fa5..2e0b7a4ef 100644 --- a/src/neochatroom.cpp +++ b/src/neochatroom.cpp @@ -42,6 +42,7 @@ #include "neochatconfig.h" #include "notificationsmanager.h" #include "roomlastmessageprovider.h" +#include "spacehierarchycache.h" #include "texthandler.h" #include "urlhelper.h" #include "utils.h" @@ -125,6 +126,16 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS Q_EMIT urlPreviewEnabledChanged(); } }); + connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceHierarchyChanged, this, [this]() { + if (isSpace()) { + Q_EMIT childrenNotificationCountChanged(); + } + }); + connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceNotifcationCountChanged, this, [this](const QStringList &spaces) { + if (spaces.contains(id())) { + Q_EMIT childrenNotificationCountChanged(); + } + }); } bool NeoChatRoom::hasFileUploading() const @@ -1278,6 +1289,14 @@ bool NeoChatRoom::isSpace() return creationEvent->roomType() == RoomType::Space; } +qsizetype NeoChatRoom::childrenNotificationCount() +{ + if (!isSpace()) { + return 0; + } + return SpaceHierarchyCache::instance().notificationCountForSpace(id()); +} + void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool canonical, bool suggested) { if (!isSpace()) { diff --git a/src/neochatroom.h b/src/neochatroom.h index 742cabc4c..afba805c2 100644 --- a/src/neochatroom.h +++ b/src/neochatroom.h @@ -126,6 +126,13 @@ class NeoChatRoom : public Quotient::Room */ Q_PROPERTY(bool isSpace READ isSpace CONSTANT) + /** + * @brief The number of notifications in this room's children. + * + * Will always return 0 if this is not a space. + */ + Q_PROPERTY(qsizetype childrenNotificationCount READ childrenNotificationCount NOTIFY childrenNotificationCountChanged) + /** * @brief Whether the local user has an invite to the room. * @@ -526,6 +533,8 @@ public: [[nodiscard]] bool isSpace(); + qsizetype childrenNotificationCount(); + /** * @brief Add the given room as a child. * @@ -815,6 +824,7 @@ Q_SIGNALS: void parentIdsChanged(); void canonicalParentChanged(); void lastActiveTimeChanged(); + void childrenNotificationCountChanged(); void isInviteChanged(); void readOnlyChanged(); void displayNameChanged(); diff --git a/src/qml/SpaceDrawer.qml b/src/qml/SpaceDrawer.qml index 55f4fca0e..d8cb8099a 100644 --- a/src/qml/SpaceDrawer.qml +++ b/src/qml/SpaceDrawer.qml @@ -108,6 +108,32 @@ QQC2.Control { RoomManager.lastSpaceId = ""; root.selectionChanged(); } + + QQC2.Label { + id: homeNotificationCountLabel + anchors.top: parent.top + anchors.right: parent.right + anchors.rightMargin: Kirigami.Units.smallSpacing / 2 + z: 1 + width: Math.max(homeNotificationCountTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height) + height: Kirigami.Units.iconSizes.smallMedium + + text: root.connection.homeNotifications > 0 ? root.connection.homeNotifications : "" + visible: root.connection.homeNotifications > 0 && (root.selectedSpaceId !== "" || root.showDirectChats === true) + color: Kirigami.Theme.textColor + horizontalAlignment: Text.AlignHCenter + background: Rectangle { + visible: true + Kirigami.Theme.colorSet: Kirigami.Theme.Button + color: Kirigami.Theme.positiveTextColor + radius: height / 2 + } + + TextMetrics { + id: homeNotificationCountTextMetrics + text: homeNotificationCountLabel.text + } + } } AvatarTabButton { id: directChatButton @@ -132,16 +158,16 @@ QQC2.Control { } QQC2.Label { - id: notificationCountLabel + id: directChatNotificationCountLabel anchors.top: parent.top anchors.right: parent.right anchors.rightMargin: Kirigami.Units.smallSpacing / 2 z: 1 - width: Math.max(notificationCountTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height) + width: Math.max(directChatNotificationCountTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height) height: Kirigami.Units.iconSizes.smallMedium text: root.connection.directChatNotifications > 0 ? root.connection.directChatNotifications : "" - visible: root.connection.directChatNotifications > 0 || root.connection.directChatInvites + visible: (root.connection.directChatNotifications > 0 || root.connection.directChatInvites) && root.showDirectChats === false color: Kirigami.Theme.textColor horizontalAlignment: Text.AlignHCenter background: Rectangle { @@ -152,8 +178,8 @@ QQC2.Control { } TextMetrics { - id: notificationCountTextMetrics - text: notificationCountLabel.text + id: directChatNotificationCountTextMetrics + text: directChatNotificationCountLabel.text } } } @@ -199,6 +225,32 @@ QQC2.Control { } checked: root.selectedSpaceId === roomId onContextMenuRequested: root.createContextMenu(currentRoom) + + QQC2.Label { + id: notificationCountLabel + anchors.top: parent.top + anchors.right: parent.right + anchors.rightMargin: Kirigami.Units.smallSpacing / 2 + z: 1 + width: Math.max(notificationCountTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height) + height: Kirigami.Units.iconSizes.smallMedium + + text: spaceDelegate.currentRoom.childrenNotificationCount > 0 ? spaceDelegate.currentRoom.childrenNotificationCount : "" + visible: spaceDelegate.currentRoom.childrenNotificationCount > 0 && root.selectedSpaceId != spaceDelegate.roomId + color: Kirigami.Theme.textColor + horizontalAlignment: Text.AlignHCenter + background: Rectangle { + visible: true + Kirigami.Theme.colorSet: Kirigami.Theme.Button + color: Kirigami.Theme.positiveTextColor + radius: height / 2 + } + + TextMetrics { + id: notificationCountTextMetrics + text: notificationCountLabel.text + } + } } } diff --git a/src/spacehierarchycache.cpp b/src/spacehierarchycache.cpp index e8a3984f7..bcd49795b 100644 --- a/src/spacehierarchycache.cpp +++ b/src/spacehierarchycache.cpp @@ -7,6 +7,7 @@ #include #include "neochatroom.h" +#include "roomlistmodel.h" using namespace Quotient; @@ -33,6 +34,15 @@ void SpaceHierarchyCache::cacheSpaceHierarchy() } }); } + + connect(neoChatRoom, &NeoChatRoom::unreadStatsChanged, this, [this, neoChatRoom]() { + if (neoChatRoom != nullptr) { + const auto parents = parentSpaces(neoChatRoom->id()); + if (parents.count() > 0) { + Q_EMIT spaceNotifcationCountChanged(parents); + } + } + }); } } @@ -74,6 +84,18 @@ void SpaceHierarchyCache::removeSpaceFromHierarchy(Quotient::Room *room) } } +QStringList SpaceHierarchyCache::parentSpaces(const QString &roomId) +{ + auto spaces = m_spaceHierarchy.keys(); + QStringList parents; + for (const auto &space : spaces) { + if (m_spaceHierarchy[space].contains(roomId)) { + parents += space; + } + } + return parents; +} + bool SpaceHierarchyCache::isSpaceChild(const QString &spaceId, const QString &roomId) { return getRoomListForSpace(spaceId, false).contains(roomId); @@ -87,6 +109,31 @@ QList &SpaceHierarchyCache::getRoomListForSpace(const QString &spaceId, return m_spaceHierarchy[spaceId]; } +qsizetype SpaceHierarchyCache::notificationCountForSpace(const QString &spaceId) +{ + qsizetype notifications = 0; + auto children = m_spaceHierarchy[spaceId]; + QStringList added; + + for (const auto &childId : children) { + if (const auto child = static_cast(m_connection->room(childId))) { + auto category = RoomListModel::category(child); + if (!added.contains(child->id()) && child->successorId().isEmpty()) { + switch (category) { + case NeoChatRoomType::Normal: + case NeoChatRoomType::Favorite: + notifications += child->notificationCount(); + break; + default: + notifications += child->highlightCount(); + } + added += child->id(); + } + } + } + return notifications; +} + bool SpaceHierarchyCache::isChild(const QString &roomId) const { const auto childrens = m_spaceHierarchy.values(); diff --git a/src/spacehierarchycache.h b/src/spacehierarchycache.h index a95a10af9..32e9d9c0d 100644 --- a/src/spacehierarchycache.h +++ b/src/spacehierarchycache.h @@ -44,6 +44,11 @@ public: return &instance(); } + /** + * @brief Returns the list of parent spaces for a child if any. + */ + QStringList parentSpaces(const QString &roomId); + /** * @brief Whether the given room is a member of the given space. */ @@ -54,6 +59,11 @@ public: */ [[nodiscard]] QList &getRoomListForSpace(const QString &spaceId, bool updateCache); + /** + * @brief Return the number of notifications for the child rooms in a given space ID. + */ + qsizetype notificationCountForSpace(const QString &spaceId); + /** * @brief Returns whether the room is a child space of any space. * @@ -69,6 +79,7 @@ public: Q_SIGNALS: void spaceHierarchyChanged(); void connectionChanged(); + void spaceNotifcationCountChanged(const QStringList &spaces); private Q_SLOTS: void addSpaceToHierarchy(Quotient::Room *room);