diff --git a/src/libneochat/neochatroom.cpp b/src/libneochat/neochatroom.cpp index 5d8584e04..1a788ebfd 100644 --- a/src/libneochat/neochatroom.cpp +++ b/src/libneochat/neochatroom.cpp @@ -157,12 +157,14 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS if (isSpace()) { Q_EMIT childrenNotificationCountChanged(); Q_EMIT childrenHaveHighlightNotificationsChanged(); + Q_EMIT spaceHasUnreadMessagesChanged(); } }); connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceNotificationCountChanged, this, [this](const QStringList &spaces) { if (spaces.contains(id())) { Q_EMIT childrenNotificationCountChanged(); Q_EMIT childrenHaveHighlightNotificationsChanged(); + Q_EMIT spaceHasUnreadMessagesChanged(); } }); @@ -1906,4 +1908,20 @@ void NeoChatRoom::invalidateLastUnreadHighlightId(const QString &fromEventId, co } } +bool NeoChatRoom::spaceHasUnreadMessages() const +{ + if (!isSpace()) { + return false; + } + + return SpaceHierarchyCache::instance().spaceHasUnreadMessages(id()); +} + +void NeoChatRoom::markAllChildrenMessagesAsRead() +{ + if (isSpace()) { + SpaceHierarchyCache::instance().markAllChildrenMessagesAsRead(id()); + } +}; + #include "moc_neochatroom.cpp" diff --git a/src/libneochat/neochatroom.h b/src/libneochat/neochatroom.h index 39894713f..a40371a61 100644 --- a/src/libneochat/neochatroom.h +++ b/src/libneochat/neochatroom.h @@ -213,6 +213,13 @@ class NeoChatRoom : public Quotient::Room */ Q_PROPERTY(bool highlightCycleStarted READ highlightCycleStarted NOTIFY highlightCycleStartedChanged) + /** + * @brief Whether the space has unread messages. + * + * Will always return false if this is not a space. + */ + Q_PROPERTY(bool spaceHasUnreadMessages READ spaceHasUnreadMessages NOTIFY spaceHasUnreadMessagesChanged) + public: explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {}); @@ -652,6 +659,18 @@ public: */ bool highlightCycleStarted() const; + /** + * @brief Whether the space has unread messages. + * + * Will always return false if this is not a space. + */ + bool spaceHasUnreadMessages() const; + + /** + * @brief Mark the space and all its children messages as read. + */ + Q_INVOKABLE void markAllChildrenMessagesAsRead(); + private: bool m_visible = false; @@ -722,6 +741,7 @@ Q_SIGNALS: void inviteTimestampChanged(); void pinnedMessageChanged(); void highlightCycleStartedChanged(); + void spaceHasUnreadMessagesChanged(); /** * @brief Request a message be shown to the user of the given type. diff --git a/src/libneochat/spacehierarchycache.cpp b/src/libneochat/spacehierarchycache.cpp index 8a5b07372..bc7b44bd5 100644 --- a/src/libneochat/spacehierarchycache.cpp +++ b/src/libneochat/spacehierarchycache.cpp @@ -187,6 +187,21 @@ bool SpaceHierarchyCache::isChild(const QString &roomId) const return false; } +bool SpaceHierarchyCache::spaceHasUnreadMessages(const QString &spaceId) +{ + auto children = m_spaceHierarchy[spaceId]; + + for (const auto &childId : children) { + if (const auto child = static_cast(m_connection->room(childId))) { + if (child->notificationCount() > 0) { + return true; + } + } + } + + return false; +} + NeoChatConnection *SpaceHierarchyCache::connection() const { return m_connection; @@ -239,4 +254,40 @@ void SpaceHierarchyCache::setRecommendedSpaceHidden(bool hidden) Q_EMIT recommendedSpaceHiddenChanged(); } +void SpaceHierarchyCache::markAllChildrenMessagesAsRead(const QString &spaceId) +{ + const auto children = m_spaceHierarchy[spaceId]; + + for (const auto &childId : children) { + if (const auto child = static_cast(m_connection->room(childId))) { + if (child->notificationCount() <= 0) { + continue; + } + + if (child->messageEvents().crbegin() == child->historyEdge()) { + if (!child->eventsHistoryJob()) { + if (child->allHistoryLoaded()) { + continue; + } + + child->getPreviousContent(); + } + + connect( + child, + &NeoChatRoom::addedMessages, + child, + [child] { + if (child->messageEvents().crbegin() != child->historyEdge()) { + child->markAllMessagesAsRead(); + } + }, + Qt::SingleShotConnection); + } else { + child->markAllMessagesAsRead(); + } + } + } +} + #include "moc_spacehierarchycache.cpp" diff --git a/src/libneochat/spacehierarchycache.h b/src/libneochat/spacehierarchycache.h index d23301bdf..39790f828 100644 --- a/src/libneochat/spacehierarchycache.h +++ b/src/libneochat/spacehierarchycache.h @@ -84,6 +84,11 @@ public: */ [[nodiscard]] bool isChild(const QString &roomId) const; + /** + * @brief Return whether the given space has unread messages. + */ + bool spaceHasUnreadMessages(const QString &spaceId); + NeoChatConnection *connection() const; void setConnection(NeoChatConnection *connection); @@ -95,6 +100,8 @@ public: bool recommendedSpaceHidden() const; void setRecommendedSpaceHidden(bool hidden); + void markAllChildrenMessagesAsRead(const QString &spaceId); + Q_SIGNALS: void spaceHierarchyChanged(); void connectionChanged(); diff --git a/src/rooms/SpaceListContextMenu.qml b/src/rooms/SpaceListContextMenu.qml index 1502733e3..71d96bba2 100644 --- a/src/rooms/SpaceListContextMenu.qml +++ b/src/rooms/SpaceListContextMenu.qml @@ -40,6 +40,19 @@ KirigamiComponents.ConvergentContextMenu { } } + QQC2.Action { + text: i18nc("@action:inmenu", "Mark Space as Read") + icon.name: "checkmark" + enabled: root.room.spaceHasUnreadMessages + onTriggered: { + root.room.markAllChildrenMessagesAsRead(); + } + } + + Kirigami.Action { + separator: true + } + QQC2.Action { text: i18nc("'Space' is a matrix space", "View Space") icon.name: "view-list-details"