Notification Consistency

Make sure that the new rules for counting notifications for muted, mention and low priority rooms is applied consistently to the room list, space drawer and the task manager notification badge

implements #644
This commit is contained in:
James Graham
2024-03-01 17:56:13 +00:00
parent 943f6c762c
commit f557ceda19
15 changed files with 238 additions and 154 deletions

View File

@@ -26,6 +26,7 @@
#include <Quotient/qt_connection_util.h> #include <Quotient/qt_connection_util.h>
#include "neochatconfig.h" #include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "notificationsmanager.h" #include "notificationsmanager.h"
#include "proxycontroller.h" #include "proxycontroller.h"
@@ -37,6 +38,14 @@
#include "trayicon_sni.h" #include "trayicon_sni.h"
#endif #endif
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#endif
#endif
bool testMode = false; bool testMode = false;
using namespace Quotient; using namespace Quotient;
@@ -149,6 +158,7 @@ void Controller::addConnection(NeoChatConnection *c)
connect(c, &NeoChatConnection::loggedOut, this, [this, c] { connect(c, &NeoChatConnection::loggedOut, this, [this, c] {
dropConnection(c); dropConnection(c);
}); });
connect(c, &NeoChatConnection::badgeNotificationCountChanged, this, &Controller::updateBadgeNotificationCount);
c->sync(); c->sync();
@@ -291,7 +301,14 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
if (connection == m_connection) { if (connection == m_connection) {
return; return;
} }
m_connection = connection; m_connection = connection;
if (m_connection != nullptr) {
m_connection->refreshBadgeNotificationCount();
updateBadgeNotificationCount(m_connection, m_connection->badgeNotificationCount());
}
Q_EMIT activeConnectionChanged(); Q_EMIT activeConnectionChanged();
} }
@@ -316,6 +333,36 @@ void Controller::listenForNotifications()
#endif #endif
} }
void Controller::updateBadgeNotificationCount(NeoChatConnection *connection, int count)
{
if (connection == m_connection) {
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
// copied from Telegram desktop
const auto launcherUrl = "application://org.kde.neochat.desktop"_ls;
// Gnome requires that count is a 64bit integer
const qint64 counterSlice = std::min(count, 9999);
QVariantMap dbusUnityProperties;
if (counterSlice > 0) {
dbusUnityProperties["count"_ls] = counterSlice;
dbusUnityProperties["count-visible"_ls] = true;
} else {
dbusUnityProperties["count-visible"_ls] = false;
}
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat"_ls, "com.canonical.Unity.LauncherEntry"_ls, "Update"_ls);
signal.setArguments({launcherUrl, dbusUnityProperties});
QDBusConnection::sessionBus().send(signal);
#endif // Q_OS_ANDROID
#else
qGuiApp->setBadgeNumber(count);
#endif // QT_VERSION_CHECK(6, 6, 0)
}
}
bool Controller::isFlatpak() const bool Controller::isFlatpak() const
{ {
#ifdef NEOCHAT_FLATPAK #ifdef NEOCHAT_FLATPAK

View File

@@ -117,6 +117,7 @@ private:
private Q_SLOTS: private Q_SLOTS:
void invokeLogin(); void invokeLogin();
void setQuitOnLastWindowClosed(); void setQuitOnLastWindowClosed();
void updateBadgeNotificationCount(NeoChatConnection *connection, int count);
Q_SIGNALS: Q_SIGNALS:
void errorOccured(const QString &error, const QString &detail); void errorOccured(const QString &error, const QString &detail);

View File

@@ -9,17 +9,7 @@
#include "roommanager.h" #include "roommanager.h"
#include "spacehierarchycache.h" #include "spacehierarchycache.h"
#include <QDebug>
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#endif
#endif
#include <KLocalizedString> #include <KLocalizedString>
#include <QGuiApplication>
using namespace Quotient; using namespace Quotient;
@@ -28,32 +18,6 @@ Q_DECLARE_METATYPE(Quotient::JoinState)
RoomListModel::RoomListModel(QObject *parent) RoomListModel::RoomListModel(QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
{ {
connect(this, &RoomListModel::highlightCountChanged, this, [this]() {
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
// copied from Telegram desktop
const auto launcherUrl = "application://org.kde.neochat.desktop"_ls;
// Gnome requires that count is a 64bit integer
const qint64 counterSlice = std::min(m_highlightCount, 9999);
QVariantMap dbusUnityProperties;
if (counterSlice > 0) {
dbusUnityProperties["count"_ls] = counterSlice;
dbusUnityProperties["count-visible"_ls] = true;
} else {
dbusUnityProperties["count-visible"_ls] = false;
}
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat"_ls, "com.canonical.Unity.LauncherEntry"_ls, "Update"_ls);
signal.setArguments({launcherUrl, dbusUnityProperties});
QDBusConnection::sessionBus().send(signal);
#endif // Q_OS_ANDROID
#else
qGuiApp->setBadgeNumber(m_highlightCount);
#endif // QT_VERSION_CHECK(6, 6, 0)
});
connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceHierarchyChanged, this, [this]() { connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceHierarchyChanged, this, [this]() {
Q_EMIT dataChanged(index(0, 0), index(rowCount(), 0), {IsChildSpaceRole}); Q_EMIT dataChanged(index(0, 0), index(rowCount(), 0), {IsChildSpaceRole});
}); });
@@ -122,7 +86,6 @@ void RoomListModel::doResetModel()
doAddRoom(room); doAddRoom(room);
} }
endResetModel(); endResetModel();
refreshNotificationCount();
} }
NeoChatRoom *RoomListModel::roomAt(int row) const NeoChatRoom *RoomListModel::roomAt(int row) const
@@ -148,7 +111,7 @@ void RoomListModel::connectRoomSignals(NeoChatRoom *room)
refresh(room, {DisplayNameRole}); refresh(room, {DisplayNameRole});
}); });
connect(room, &Room::unreadStatsChanged, this, [this, room] { connect(room, &Room::unreadStatsChanged, this, [this, room] {
refresh(room, {NotificationCountRole, HighlightCountRole}); refresh(room, {ContextNotificationCountRole, HasHighlightNotificationsRole});
}); });
connect(room, &Room::notificationCountChanged, this, [this, room] { connect(room, &Room::notificationCountChanged, this, [this, room] {
refresh(room); refresh(room);
@@ -171,44 +134,6 @@ void RoomListModel::connectRoomSignals(NeoChatRoom *room)
connect(room, &Room::pendingEventMerged, this, [this, room] { connect(room, &Room::pendingEventMerged, this, [this, room] {
refresh(room, {SubtitleTextRole}); refresh(room, {SubtitleTextRole});
}); });
connect(room, &Room::unreadStatsChanged, this, &RoomListModel::refreshNotificationCount);
connect(room, &Room::highlightCountChanged, this, &RoomListModel::refreshHighlightCount);
}
int RoomListModel::notificationCount() const
{
return m_notificationCount;
}
int RoomListModel::highlightCount() const
{
return m_highlightCount;
}
void RoomListModel::refreshNotificationCount()
{
int count = 0;
for (auto room : std::as_const(m_rooms)) {
count += room->notificationCount();
}
if (m_notificationCount == count) {
return;
}
m_notificationCount = count;
Q_EMIT notificationCountChanged();
}
void RoomListModel::refreshHighlightCount()
{
int count = 0;
for (auto room : std::as_const(m_rooms)) {
count += room->highlightCount();
}
if (m_highlightCount == count) {
return;
}
m_highlightCount = count;
Q_EMIT highlightCountChanged();
} }
void RoomListModel::updateRoom(Room *room, Room *prev) void RoomListModel::updateRoom(Room *room, Room *prev)
@@ -295,11 +220,11 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
if (role == CategoryRole) { if (role == CategoryRole) {
return NeoChatRoomType::typeForRoom(room); return NeoChatRoomType::typeForRoom(room);
} }
if (role == NotificationCountRole) { if (role == ContextNotificationCountRole) {
return room->notificationCount(); return room->contextAwareNotificationCount();
} }
if (role == HighlightCountRole) { if (role == HasHighlightNotificationsRole) {
return room->highlightCount(); return room->highlightCount() > 0 && room->contextAwareNotificationCount() > 0;
} }
if (role == LastActiveTimeRole) { if (role == LastActiveTimeRole) {
return room->lastActiveTime(); return room->lastActiveTime();
@@ -361,8 +286,8 @@ QHash<int, QByteArray> RoomListModel::roleNames() const
roles[CanonicalAliasRole] = "canonicalAlias"; roles[CanonicalAliasRole] = "canonicalAlias";
roles[TopicRole] = "topic"; roles[TopicRole] = "topic";
roles[CategoryRole] = "category"; roles[CategoryRole] = "category";
roles[NotificationCountRole] = "notificationCount"; roles[ContextNotificationCountRole] = "contextNotificationCount";
roles[HighlightCountRole] = "highlightCount"; roles[HasHighlightNotificationsRole] = "hasHighlightNotifications";
roles[LastActiveTimeRole] = "lastActiveTime"; roles[LastActiveTimeRole] = "lastActiveTime";
roles[JoinStateRole] = "joinState"; roles[JoinStateRole] = "joinState";
roles[CurrentRoomRole] = "currentRoom"; roles[CurrentRoomRole] = "currentRoom";

View File

@@ -31,11 +31,6 @@ class RoomListModel : public QAbstractListModel
*/ */
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged) Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
/**
* @brief The total number of notifications for all the rooms.
*/
Q_PROPERTY(int notificationCount READ notificationCount NOTIFY notificationCountChanged)
public: public:
/** /**
* @brief Defines the model roles. * @brief Defines the model roles.
@@ -46,8 +41,8 @@ public:
CanonicalAliasRole, /**< The room canonical alias. */ CanonicalAliasRole, /**< The room canonical alias. */
TopicRole, /**< The room topic. */ TopicRole, /**< The room topic. */
CategoryRole, /**< The room category, e.g favourite. */ CategoryRole, /**< The room category, e.g favourite. */
NotificationCountRole, /**< The number of notifications in the room. */ ContextNotificationCountRole, /**< The context aware notification count for the room. */
HighlightCountRole, /**< The number of highlighted messages in the room. */ HasHighlightNotificationsRole, /**< Whether there are any highlight notifications. */
LastActiveTimeRole, /**< The timestamp of the last event sent in the room. */ LastActiveTimeRole, /**< The timestamp of the last event sent in the room. */
JoinStateRole, /**< The local user's join state in the room. */ JoinStateRole, /**< The local user's join state in the room. */
CurrentRoomRole, /**< The room object for the room. */ CurrentRoomRole, /**< The room object for the room. */
@@ -67,9 +62,6 @@ public:
[[nodiscard]] Quotient::Connection *connection() const; [[nodiscard]] Quotient::Connection *connection() const;
void setConnection(Quotient::Connection *connection); void setConnection(Quotient::Connection *connection);
[[nodiscard]] int notificationCount() const;
[[nodiscard]] int highlightCount() const;
/** /**
* @brief Get the given role value at the given index. * @brief Get the given role value at the given index.
* *
@@ -114,23 +106,16 @@ private Q_SLOTS:
void updateRoom(Quotient::Room *room, Quotient::Room *prev); void updateRoom(Quotient::Room *room, Quotient::Room *prev);
void deleteRoom(Quotient::Room *room); void deleteRoom(Quotient::Room *room);
void refresh(NeoChatRoom *room, const QList<int> &roles = {}); void refresh(NeoChatRoom *room, const QList<int> &roles = {});
void refreshNotificationCount();
void refreshHighlightCount();
private: private:
Quotient::Connection *m_connection = nullptr; Quotient::Connection *m_connection = nullptr;
QList<NeoChatRoom *> m_rooms; QList<NeoChatRoom *> m_rooms;
int m_notificationCount = 0;
int m_highlightCount = 0;
QString m_activeSpaceId; QString m_activeSpaceId;
void connectRoomSignals(NeoChatRoom *room); void connectRoomSignals(NeoChatRoom *room);
Q_SIGNALS: Q_SIGNALS:
void connectionChanged(); void connectionChanged();
void notificationCountChanged();
void highlightCountChanged();
void roomAdded(NeoChatRoom *_t1); void roomAdded(NeoChatRoom *_t1);
}; };

View File

@@ -127,7 +127,7 @@ void RoomTreeModel::connectRoomSignals(NeoChatRoom *room)
refreshRoomRoles(room, {DisplayNameRole}); refreshRoomRoles(room, {DisplayNameRole});
}); });
connect(room, &Room::unreadStatsChanged, this, [this, room] { connect(room, &Room::unreadStatsChanged, this, [this, room] {
refreshRoomRoles(room, {NotificationCountRole, HighlightCountRole}); refreshRoomRoles(room, {ContextNotificationCountRole, HasHighlightNotificationsRole});
}); });
connect(room, &Room::avatarChanged, this, [this, room] { connect(room, &Room::avatarChanged, this, [this, room] {
refreshRoomRoles(room, {AvatarRole}); refreshRoomRoles(room, {AvatarRole});
@@ -144,6 +144,9 @@ void RoomTreeModel::connectRoomSignals(NeoChatRoom *room)
connect(room, &Room::pendingEventMerged, this, [this, room] { connect(room, &Room::pendingEventMerged, this, [this, room] {
refreshRoomRoles(room, {SubtitleTextRole}); refreshRoomRoles(room, {SubtitleTextRole});
}); });
connect(room, &NeoChatRoom::pushNotificationStateChanged, this, [this, room] {
refreshRoomRoles(room, {ContextNotificationCountRole, HasHighlightNotificationsRole});
});
} }
void RoomTreeModel::refreshRoomRoles(NeoChatRoom *room, const QList<int> &roles) void RoomTreeModel::refreshRoomRoles(NeoChatRoom *room, const QList<int> &roles)
@@ -208,8 +211,8 @@ QHash<int, QByteArray> RoomTreeModel::roleNames() const
roles[CanonicalAliasRole] = "canonicalAlias"; roles[CanonicalAliasRole] = "canonicalAlias";
roles[TopicRole] = "topic"; roles[TopicRole] = "topic";
roles[CategoryRole] = "category"; roles[CategoryRole] = "category";
roles[NotificationCountRole] = "notificationCount"; roles[ContextNotificationCountRole] = "contextNotificationCount";
roles[HighlightCountRole] = "highlightCount"; roles[HasHighlightNotificationsRole] = "hasHighlightNotifications";
roles[LastActiveTimeRole] = "lastActiveTime"; roles[LastActiveTimeRole] = "lastActiveTime";
roles[JoinStateRole] = "joinState"; roles[JoinStateRole] = "joinState";
roles[CurrentRoomRole] = "currentRoom"; roles[CurrentRoomRole] = "currentRoom";
@@ -271,11 +274,11 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
if (role == CategoryRole) { if (role == CategoryRole) {
return NeoChatRoomType::typeForRoom(room); return NeoChatRoomType::typeForRoom(room);
} }
if (role == NotificationCountRole) { if (role == ContextNotificationCountRole) {
return int(room->notificationCount()); return int(room->contextAwareNotificationCount());
} }
if (role == HighlightCountRole) { if (role == HasHighlightNotificationsRole) {
return int(room->highlightCount()); return room->highlightCount() > 0 && room->contextAwareNotificationCount() > 0;
} }
if (role == LastActiveTimeRole) { if (role == LastActiveTimeRole) {
return room->lastActiveTime(); return room->lastActiveTime();

View File

@@ -33,8 +33,8 @@ public:
CanonicalAliasRole, /**< The room canonical alias. */ CanonicalAliasRole, /**< The room canonical alias. */
TopicRole, /**< The room topic. */ TopicRole, /**< The room topic. */
CategoryRole, /**< The room category, e.g favourite. */ CategoryRole, /**< The room category, e.g favourite. */
NotificationCountRole, /**< The number of notifications in the room. */ ContextNotificationCountRole, /**< The context aware notification count for the room. */
HighlightCountRole, /**< The number of highlighted messages in the room. */ HasHighlightNotificationsRole, /**< Whether there are any highlight notifications. */
LastActiveTimeRole, /**< The timestamp of the last event sent in the room. */ LastActiveTimeRole, /**< The timestamp of the last event sent in the room. */
JoinStateRole, /**< The local user's join state in the room. */ JoinStateRole, /**< The local user's join state in the room. */
CurrentRoomRole, /**< The room object for the room. */ CurrentRoomRole, /**< The room object for the room. */

View File

@@ -52,8 +52,8 @@ static const QVector<RoomTreeModel::EventRoles> activitySortPriorities{
// Anything useful at the top, quiet rooms at the bottom // Anything useful at the top, quiet rooms at the bottom
RoomTreeModel::AttentionRole, RoomTreeModel::AttentionRole,
// Organize by highlights, notifications, unread favorites, all other unread, in that order // Organize by highlights, notifications, unread favorites, all other unread, in that order
RoomTreeModel::HighlightCountRole, RoomTreeModel::HasHighlightNotificationsRole,
RoomTreeModel::NotificationCountRole, RoomTreeModel::ContextNotificationCountRole,
RoomTreeModel::FavouriteRole, RoomTreeModel::FavouriteRole,
// Finally sort by last activity time // Finally sort by last activity time
RoomTreeModel::LastActiveTimeRole, RoomTreeModel::LastActiveTimeRole,

View File

@@ -77,7 +77,9 @@ void NeoChatConnection::connectSignals()
for (const auto &chatId : additions) { for (const auto &chatId : additions) {
if (const auto chat = room(chatId)) { if (const auto chat = room(chatId)) {
connect(chat, &Room::unreadStatsChanged, this, [this]() { connect(chat, &Room::unreadStatsChanged, this, [this]() {
refreshBadgeNotificationCount();
Q_EMIT directChatNotificationsChanged(); Q_EMIT directChatNotificationsChanged();
Q_EMIT directChatsHaveHighlightNotificationsChanged();
}); });
} }
} }
@@ -91,29 +93,51 @@ void NeoChatConnection::connectSignals()
if (room->isDirectChat()) { if (room->isDirectChat()) {
connect(room, &Room::unreadStatsChanged, this, [this]() { connect(room, &Room::unreadStatsChanged, this, [this]() {
Q_EMIT directChatNotificationsChanged(); Q_EMIT directChatNotificationsChanged();
Q_EMIT directChatsHaveHighlightNotificationsChanged();
}); });
} }
connect(room, &Room::unreadStatsChanged, this, [this]() {
refreshBadgeNotificationCount();
Q_EMIT homeNotificationsChanged();
Q_EMIT homeHaveHighlightNotificationsChanged();
});
}); });
connect(this, &NeoChatConnection::leftRoom, this, [this](Room *room, Room *prev) { connect(this, &NeoChatConnection::leftRoom, this, [this](Room *room, Room *prev) {
Q_UNUSED(room) Q_UNUSED(room)
if (prev && prev->isDirectChat()) { if (prev && prev->isDirectChat()) {
Q_EMIT directChatInvitesChanged(); Q_EMIT directChatInvitesChanged();
Q_EMIT directChatNotificationsChanged();
Q_EMIT directChatsHaveHighlightNotificationsChanged();
} }
refreshBadgeNotificationCount();
Q_EMIT homeNotificationsChanged();
Q_EMIT homeHaveHighlightNotificationsChanged();
}); });
connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceHierarchyChanged, this, [this]() { connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceHierarchyChanged, this, [this]() {
refreshBadgeNotificationCount();
Q_EMIT homeNotificationsChanged(); Q_EMIT homeNotificationsChanged();
Q_EMIT homeHaveHighlightNotificationsChanged();
}); });
for (const auto room : allRooms()) { }
connect(room, &NeoChatRoom::unreadStatsChanged, this, [this, room]() {
if (room != nullptr) { int NeoChatConnection::badgeNotificationCount() const
auto category = NeoChatRoomType::typeForRoom(static_cast<NeoChatRoom *>(room)); {
if (!SpaceHierarchyCache::instance().isChild(room->id()) && (category == NeoChatRoomType::Normal || category == NeoChatRoomType::Favorite) return m_badgeNotificationCount;
&& room->successorId().isEmpty()) { }
Q_EMIT homeNotificationsChanged();
} void NeoChatConnection::refreshBadgeNotificationCount()
} {
}); int count = 0;
for (const auto &r : allRooms()) {
if (const auto room = static_cast<NeoChatRoom *>(r)) {
count += room->contextAwareNotificationCount();
}
}
if (count != m_badgeNotificationCount) {
m_badgeNotificationCount = count;
Q_EMIT badgeNotificationCountChanged(this, m_badgeNotificationCount);
} }
} }
@@ -329,7 +353,7 @@ qsizetype NeoChatConnection::directChatNotifications() const
for (const auto &chatId : directChats()) { for (const auto &chatId : directChats()) {
if (!added.contains(chatId)) { if (!added.contains(chatId)) {
if (const auto chat = room(chatId)) { if (const auto chat = room(chatId)) {
notifications += chat->notificationCount(); notifications += dynamic_cast<NeoChatRoom *>(chat)->contextAwareNotificationCount();
added += chatId; added += chatId;
} }
} }
@@ -337,29 +361,47 @@ qsizetype NeoChatConnection::directChatNotifications() const
return notifications; return notifications;
} }
bool NeoChatConnection::directChatsHaveHighlightNotifications() const
{
for (const auto &childId : directChats()) {
if (const auto child = static_cast<NeoChatRoom *>(room(childId))) {
if (child->highlightCount() > 0) {
return true;
}
}
}
return false;
}
qsizetype NeoChatConnection::homeNotifications() const qsizetype NeoChatConnection::homeNotifications() const
{ {
qsizetype notifications = 0; qsizetype notifications = 0;
QStringList added; QStringList added;
const auto &spaceHierarchyCache = SpaceHierarchyCache::instance(); const auto &spaceHierarchyCache = SpaceHierarchyCache::instance();
for (const auto &room : allRooms()) { for (const auto &r : allRooms()) {
auto category = NeoChatRoomType::typeForRoom(static_cast<NeoChatRoom *>(room)); if (const auto room = static_cast<NeoChatRoom *>(r)) {
if (!added.contains(room->id()) && room->joinState() == JoinState::Join && !room->isDirectChat() && !spaceHierarchyCache.isChild(room->id()) if (!added.contains(room->id()) && !room->isDirectChat() && !spaceHierarchyCache.isChild(room->id())) {
&& room->successorId().isEmpty()) { notifications += dynamic_cast<NeoChatRoom *>(room)->contextAwareNotificationCount();
switch (category) { added += room->id();
case NeoChatRoomType::Normal:
case NeoChatRoomType::Favorite:
notifications += room->notificationCount();
break;
default:
notifications += room->highlightCount();
} }
added += room->id();
} }
} }
return notifications; return notifications;
} }
bool NeoChatConnection::homeHaveHighlightNotifications() const
{
const auto &spaceHierarchyCache = SpaceHierarchyCache::instance();
for (const auto &r : allRooms()) {
if (const auto room = static_cast<NeoChatRoom *>(r)) {
if (!room->isDirectChat() && !spaceHierarchyCache.isChild(room->id()) && room->highlightCount() > 0) {
return true;
}
}
}
return false;
}
bool NeoChatConnection::directChatInvites() const bool NeoChatConnection::directChatInvites() const
{ {
auto inviteRooms = rooms(JoinState::Invite); auto inviteRooms = rooms(JoinState::Invite);

View File

@@ -32,11 +32,21 @@ class NeoChatConnection : public Quotient::Connection
*/ */
Q_PROPERTY(qsizetype directChatNotifications READ directChatNotifications NOTIFY directChatNotificationsChanged) Q_PROPERTY(qsizetype directChatNotifications READ directChatNotifications NOTIFY directChatNotificationsChanged)
/**
* @brief Whether any direct chats have highlight notifications.
*/
Q_PROPERTY(bool directChatsHaveHighlightNotifications READ directChatsHaveHighlightNotifications NOTIFY directChatsHaveHighlightNotificationsChanged)
/** /**
* @brief The total number of notifications for all rooms in the home tab. * @brief The total number of notifications for all rooms in the home tab.
*/ */
Q_PROPERTY(qsizetype homeNotifications READ homeNotifications NOTIFY homeNotificationsChanged) Q_PROPERTY(qsizetype homeNotifications READ homeNotifications NOTIFY homeNotificationsChanged)
/**
* @brief Whether any of the rooms in the home tab have highlight notifications.
*/
Q_PROPERTY(bool homeHaveHighlightNotifications READ homeHaveHighlightNotifications NOTIFY homeHaveHighlightNotificationsChanged)
/** /**
* @brief Whether there is at least one invite to a direct chat. * @brief Whether there is at least one invite to a direct chat.
*/ */
@@ -119,7 +129,13 @@ public:
Q_INVOKABLE QString accountDataJsonString(const QString &type) const; Q_INVOKABLE QString accountDataJsonString(const QString &type) const;
qsizetype directChatNotifications() const; qsizetype directChatNotifications() const;
bool directChatsHaveHighlightNotifications() const;
qsizetype homeNotifications() const; qsizetype homeNotifications() const;
bool homeHaveHighlightNotifications() const;
int badgeNotificationCount() const;
void refreshBadgeNotificationCount();
bool directChatInvites() const; bool directChatInvites() const;
// note: this is intentionally a copied QString because // note: this is intentionally a copied QString because
@@ -134,15 +150,20 @@ public:
Q_SIGNALS: Q_SIGNALS:
void labelChanged(); void labelChanged();
void directChatNotificationsChanged(); void directChatNotificationsChanged();
void directChatsHaveHighlightNotificationsChanged();
void homeNotificationsChanged(); void homeNotificationsChanged();
void homeHaveHighlightNotificationsChanged();
void directChatInvitesChanged(); void directChatInvitesChanged();
void isOnlineChanged(); void isOnlineChanged();
void passwordStatus(NeoChatConnection::PasswordStatus status); void passwordStatus(NeoChatConnection::PasswordStatus status);
void userConsentRequired(QUrl url); void userConsentRequired(QUrl url);
void badgeNotificationCountChanged(NeoChatConnection *connection, int count);
private: private:
bool m_isOnline = true; bool m_isOnline = true;
void setIsOnline(bool isOnline); void setIsOnline(bool isOnline);
void connectSignals(); void connectSignals();
int m_badgeNotificationCount = 0;
}; };

View File

@@ -10,6 +10,7 @@
#include <QTemporaryFile> #include <QTemporaryFile>
#include <Quotient/jobs/basejob.h> #include <Quotient/jobs/basejob.h>
#include <Quotient/quotient_common.h>
#include <Quotient/user.h> #include <Quotient/user.h>
#include <qcoro/qcorosignal.h> #include <qcoro/qcorosignal.h>
@@ -129,15 +130,32 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceHierarchyChanged, this, [this]() { connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceHierarchyChanged, this, [this]() {
if (isSpace()) { if (isSpace()) {
Q_EMIT childrenNotificationCountChanged(); Q_EMIT childrenNotificationCountChanged();
Q_EMIT childrenHaveHighlightNotificationsChanged();
} }
}); });
connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceNotifcationCountChanged, this, [this](const QStringList &spaces) { connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceNotifcationCountChanged, this, [this](const QStringList &spaces) {
if (spaces.contains(id())) { if (spaces.contains(id())) {
Q_EMIT childrenNotificationCountChanged(); Q_EMIT childrenNotificationCountChanged();
Q_EMIT childrenHaveHighlightNotificationsChanged();
} }
}); });
} }
int NeoChatRoom::contextAwareNotificationCount() const
{
// DOn't include spaces, rooms that the user hasn't joined and rooms where the user has joined the successor.
if (isSpace() || joinState() != JoinState::Join || successor(JoinState::Join) != nullptr) {
return 0;
}
if (m_currentPushNotificationState == PushNotificationState::Mute) {
return 0;
}
if (m_currentPushNotificationState == PushNotificationState::MentionKeyword || isLowPriority()) {
return int(highlightCount());
}
return int(notificationCount());
}
bool NeoChatRoom::hasFileUploading() const bool NeoChatRoom::hasFileUploading() const
{ {
return m_hasFileUploading; return m_hasFileUploading;
@@ -1300,6 +1318,14 @@ qsizetype NeoChatRoom::childrenNotificationCount()
return SpaceHierarchyCache::instance().notificationCountForSpace(id()); return SpaceHierarchyCache::instance().notificationCountForSpace(id());
} }
bool NeoChatRoom::childrenHaveHighlightNotifications() const
{
if (!isSpace()) {
return false;
}
return SpaceHierarchyCache::instance().spaceHasHighlightNotifications(id());
}
void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool canonical, bool suggested) void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool canonical, bool suggested)
{ {
if (!isSpace()) { if (!isSpace()) {

View File

@@ -133,6 +133,13 @@ class NeoChatRoom : public Quotient::Room
*/ */
Q_PROPERTY(qsizetype childrenNotificationCount READ childrenNotificationCount NOTIFY childrenNotificationCountChanged) Q_PROPERTY(qsizetype childrenNotificationCount READ childrenNotificationCount NOTIFY childrenNotificationCountChanged)
/**
* @brief Whether this room's children have any highlight notifications.
*
* Will always return false if this is not a space.
*/
Q_PROPERTY(bool childrenHaveHighlightNotifications READ childrenHaveHighlightNotifications NOTIFY childrenHaveHighlightNotificationsChanged)
/** /**
* @brief Whether the local user has an invite to the room. * @brief Whether the local user has an invite to the room.
* *
@@ -405,6 +412,16 @@ public:
*/ */
[[nodiscard]] bool lastEventIsSpoiler() const; [[nodiscard]] bool lastEventIsSpoiler() const;
/**
* @brief Return the notification count for the room accounting for tags and notification state.
*
* The following rules are observed:
* - Rooms tagged as low priority or mentions and keywords notification state
* only return the number of highlights.
* - Muted rooms always return 0.
*/
int contextAwareNotificationCount() const;
[[nodiscard]] bool hasFileUploading() const; [[nodiscard]] bool hasFileUploading() const;
void setHasFileUploading(bool value); void setHasFileUploading(bool value);
@@ -535,6 +552,8 @@ public:
qsizetype childrenNotificationCount(); qsizetype childrenNotificationCount();
bool childrenHaveHighlightNotifications() const;
/** /**
* @brief Add the given room as a child. * @brief Add the given room as a child.
* *
@@ -825,6 +844,7 @@ Q_SIGNALS:
void canonicalParentChanged(); void canonicalParentChanged();
void lastActiveTimeChanged(); void lastActiveTimeChanged();
void childrenNotificationCountChanged(); void childrenNotificationCountChanged();
void childrenHaveHighlightNotificationsChanged();
void isInviteChanged(); void isInviteChanged();
void readOnlyChanged(); void readOnlyChanged();
void displayNameChanged(); void displayNameChanged();

View File

@@ -18,8 +18,8 @@ Delegates.RoundedItemDelegate {
id: root id: root
required property int index required property int index
required property int notificationCount required property int contextNotificationCount
required property int highlightCount required property bool hasHighlightNotifications
required property NeoChatRoom currentRoom required property NeoChatRoom currentRoom
required property NeoChatConnection connection required property NeoChatConnection connection
required property string avatar required property string avatar
@@ -28,7 +28,7 @@ Delegates.RoundedItemDelegate {
property bool collapsed: false property bool collapsed: false
readonly property bool hasNotifications: currentRoom.pushNotificationState === PushNotificationState.MentionKeyword || currentRoom.isLowPriority ? highlightCount > 0 : notificationCount > 0 readonly property bool hasNotifications: contextNotificationCount > 0
Accessible.name: root.displayName Accessible.name: root.displayName
Accessible.onPressAction: clicked() Accessible.onPressAction: clicked()
@@ -106,16 +106,16 @@ Delegates.RoundedItemDelegate {
QQC2.Label { QQC2.Label {
id: notificationCountLabel id: notificationCountLabel
text: currentRoom.pushNotificationState === PushNotificationState.MentionKeyword || currentRoom.isLowPriority ? root.highlightCount : root.notificationCount text: root.contextNotificationCount
visible: root.hasNotifications && currentRoom.pushNotificationState !== PushNotificationState.Mute && !root.collapsed visible: root.hasNotifications && !root.collapsed
color: Kirigami.Theme.textColor color: Kirigami.Theme.textColor
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
background: Rectangle { background: Rectangle {
visible: root.hasNotifications visible: root.hasNotifications
Kirigami.Theme.colorSet: Kirigami.Theme.Button Kirigami.Theme.colorSet: Kirigami.Theme.Button
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor Kirigami.Theme.inherit: false
opacity: highlightCount > 0 ? 1 : 0.3 color: root.hasHighlightNotifications > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
radius: height / 2 radius: height / 2
} }
@@ -141,7 +141,7 @@ Delegates.RoundedItemDelegate {
} }
function createRoomListContextMenu() { function createRoomListContextMenu() {
const component = Qt.createComponent(Qt.createComponent('org.kde.neochat', 'ContextMenu.qml')); const component = Qt.createComponent('org.kde.neochat', 'ContextMenu.qml');
if (component.status === Component.Error) { if (component.status === Component.Error) {
console.error(component.errorString()); console.error(component.errorString());
} }

View File

@@ -125,7 +125,8 @@ QQC2.Control {
background: Rectangle { background: Rectangle {
visible: true visible: true
Kirigami.Theme.colorSet: Kirigami.Theme.Button Kirigami.Theme.colorSet: Kirigami.Theme.Button
color: Kirigami.Theme.positiveTextColor Kirigami.Theme.inherit: false
color: root.connection.homeHaveHighlightNotifications ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
radius: height / 2 radius: height / 2
} }
@@ -173,7 +174,8 @@ QQC2.Control {
background: Rectangle { background: Rectangle {
visible: true visible: true
Kirigami.Theme.colorSet: Kirigami.Theme.Button Kirigami.Theme.colorSet: Kirigami.Theme.Button
color: Kirigami.Theme.positiveTextColor Kirigami.Theme.inherit: false
color: root.connection.directChatsHaveHighlightNotifications ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
radius: height / 2 radius: height / 2
} }
@@ -239,10 +241,12 @@ QQC2.Control {
visible: spaceDelegate.currentRoom.childrenNotificationCount > 0 && root.selectedSpaceId != spaceDelegate.roomId visible: spaceDelegate.currentRoom.childrenNotificationCount > 0 && root.selectedSpaceId != spaceDelegate.roomId
color: Kirigami.Theme.textColor color: Kirigami.Theme.textColor
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
background: Rectangle { background: Rectangle {
visible: true visible: true
Kirigami.Theme.colorSet: Kirigami.Theme.Button Kirigami.Theme.colorSet: Kirigami.Theme.Button
color: Kirigami.Theme.positiveTextColor Kirigami.Theme.inherit: false
color: spaceDelegate.currentRoom.childrenHaveHighlightNotifications ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
radius: height / 2 radius: height / 2
} }

View File

@@ -126,16 +126,8 @@ qsizetype SpaceHierarchyCache::notificationCountForSpace(const QString &spaceId)
for (const auto &childId : children) { for (const auto &childId : children) {
if (const auto child = static_cast<NeoChatRoom *>(m_connection->room(childId))) { if (const auto child = static_cast<NeoChatRoom *>(m_connection->room(childId))) {
auto category = NeoChatRoomType::typeForRoom(child); if (!added.contains(child->id())) {
if (!added.contains(child->id()) && child->successorId().isEmpty()) { notifications += child->contextAwareNotificationCount();
switch (category) {
case NeoChatRoomType::Normal:
case NeoChatRoomType::Favorite:
notifications += child->notificationCount();
break;
default:
notifications += child->highlightCount();
}
added += child->id(); added += child->id();
} }
} }
@@ -143,6 +135,19 @@ qsizetype SpaceHierarchyCache::notificationCountForSpace(const QString &spaceId)
return notifications; return notifications;
} }
bool SpaceHierarchyCache::spaceHasHighlightNotifications(const QString &spaceId)
{
auto children = m_spaceHierarchy[spaceId];
for (const auto &childId : children) {
if (const auto child = static_cast<NeoChatRoom *>(m_connection->room(childId))) {
if (child->highlightCount() > 0) {
return true;
}
}
}
return false;
}
bool SpaceHierarchyCache::isChild(const QString &roomId) const bool SpaceHierarchyCache::isChild(const QString &roomId) const
{ {
const auto childrens = m_spaceHierarchy.values(); const auto childrens = m_spaceHierarchy.values();

View File

@@ -69,6 +69,11 @@ public:
*/ */
qsizetype notificationCountForSpace(const QString &spaceId); qsizetype notificationCountForSpace(const QString &spaceId);
/**
* @brief Whether any of the child rooms have highlight notifications.
*/
bool spaceHasHighlightNotifications(const QString &spaceId);
/** /**
* @brief Returns whether the room is a child space of any space. * @brief Returns whether the room is a child space of any space.
* *