From 2df2046d1dc5d3e4133f89f634efa6a1726a2f30 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 12 Apr 2022 21:49:21 -0400 Subject: [PATCH 01/16] Display unread notifications for spaces --- resources/qml/CommunitiesList.qml | 70 +++++++++++++++++++++++++++++++ resources/qml/RoomList.qml | 3 -- src/timeline/CommunitiesModel.cpp | 54 ++++++++++++++++++------ src/timeline/CommunitiesModel.h | 9 ++-- 4 files changed, 118 insertions(+), 18 deletions(-) diff --git a/resources/qml/CommunitiesList.qml b/resources/qml/CommunitiesList.qml index 61287789..f5f20a91 100644 --- a/resources/qml/CommunitiesList.qml +++ b/resources/qml/CommunitiesList.qml @@ -153,6 +153,42 @@ Page { roomid: model.id displayName: model.displayName color: communityItem.backgroundColor + + Rectangle { + id: collapsedNotificationBubble + + visible: model.unreadMessages > 0 && communitySidebar.collapsed + anchors.right: avatar.right + anchors.bottom: avatar.bottom + anchors.margins: -Nheko.paddingSmall + height: collapsedNotificationBubbleText.height + Nheko.paddingMedium + width: Math.max(collapsedNotificationBubbleText.width, height) + radius: height / 2 + color: /*hasLoudNotification ? Nheko.theme.red :*/ communityItem.bubbleBackground + ToolTip.text: model.unreadMessages + ToolTip.delay: Nheko.tooltipDelay + ToolTip.visible: collapsedNotificationBubbleHover.hovered && (model.unreadMessages > 9999) + + Label { + id: collapsedNotificationBubbleText + + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: Math.max(implicitWidth + Nheko.paddingMedium, parent.height) + font.bold: true + font.pixelSize: fontMetrics.font.pixelSize * 0.6 + color: /*hasLoudNotification ? "white" :*/ communityItem.bubbleText + text: model.unreadMessages > 9999 ? "9999+" : model.unreadMessages + + HoverHandler { + id: collapsedNotificationBubbleHover + } + + } + + } + } ElidedLabel { @@ -169,6 +205,40 @@ Page { Layout.fillWidth: true } + Rectangle { + id: notificationBubble + + visible: model.unreadMessages > 0 && !communitySidebar.collapsed + Layout.alignment: Qt.AlignRight + Layout.leftMargin: Nheko.paddingSmall + height: notificationBubbleText.height + Nheko.paddingMedium + Layout.preferredWidth: Math.max(notificationBubbleText.width, height) + radius: height / 2 + color: /*hasLoudNotification ? Nheko.theme.red :*/ communityItem.bubbleBackground + ToolTip.text: model.unreadMessages + ToolTip.delay: Nheko.tooltipDelay + ToolTip.visible: notificationBubbleHover.hovered && (model.unreadMessages > 9999) + + Label { + id: notificationBubbleText + + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: Math.max(implicitWidth + Nheko.paddingMedium, parent.height) + font.bold: true + font.pixelSize: fontMetrics.font.pixelSize * 0.8 + color: /*hasLoudNotification ? "white" :*/ communityItem.bubbleText + text: model.unreadMessages > 9999 ? "9999+" : model.unreadMessages + + HoverHandler { + id: notificationBubbleHover + } + + } + + } + } background: Rectangle { diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index f3509f06..910fc252 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -294,9 +294,6 @@ Page { anchors.margins: Nheko.paddingMedium Avatar { - // In the future we could show an online indicator by setting the userid for the avatar - //userid: Nheko.currentUser.userid - id: avatar enabled: false diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp index 4f650f49..b88896ee 100644 --- a/src/timeline/CommunitiesModel.cpp +++ b/src/timeline/CommunitiesModel.cpp @@ -9,12 +9,19 @@ #include "Cache.h" #include "Cache_p.h" +#include "ChatPage.h" #include "Logging.h" #include "UserSettingsPage.h" CommunitiesModel::CommunitiesModel(QObject *parent) : QAbstractListModel(parent) -{} +{ + connect(ChatPage::instance(), &ChatPage::unreadMessages, this, [this](int) { + // Simply updating every space is easier than tracking which ones need updated. + if (!spaces_.empty()) + emit dataChanged(index(2, 0), index(spaces_.size() + 2, 0), {Roles::UnreadMessages}); + }); +} QHash CommunitiesModel::roleNames() const @@ -28,6 +35,7 @@ CommunitiesModel::roleNames() const {Hidden, "hidden"}, {Depth, "depth"}, {Id, "id"}, + {UnreadMessages, "unreadMessages"}, }; } @@ -70,6 +78,8 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return 0; case CommunitiesModel::Roles::Id: return ""; + case CommunitiesModel::Roles::UnreadMessages: + return 0; } } else if (index.row() == 1) { switch (role) { @@ -91,9 +101,11 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return 0; case CommunitiesModel::Roles::Id: return "dm"; + case CommunitiesModel::Roles::UnreadMessages: + return 0; } } else if (index.row() - 2 < spaceOrder_.size()) { - auto id = spaceOrder_.tree.at(index.row() - 2).name; + auto id = spaceOrder_.tree.at(index.row() - 2).id; switch (role) { case CommunitiesModel::Roles::AvatarUrl: return QString::fromStdString(spaces_.at(id).avatar_url); @@ -110,7 +122,7 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return hiddentTagIds_.contains("space:" + id); case CommunitiesModel::Roles::Parent: { if (auto p = spaceOrder_.parent(index.row() - 2); p >= 0) - return spaceOrder_.tree[p].name; + return spaceOrder_.tree[p].id; return ""; } @@ -118,6 +130,8 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return spaceOrder_.tree.at(index.row() - 2).depth; case CommunitiesModel::Roles::Id: return "space:" + id; + case CommunitiesModel::Roles::UnreadMessages: + return getChildNotifications(id); } } else if (index.row() - 2 < tags_.size() + spaceOrder_.size()) { auto tag = tags_.at(index.row() - 2 - spaceOrder_.size()); @@ -171,6 +185,8 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return 0; case CommunitiesModel::Roles::Id: return "tag:" + tag; + case CommunitiesModel::Roles::UnreadMessages: + return 0; } } return QVariant(); @@ -279,6 +295,7 @@ CommunitiesModel::initializeSidebar() hiddentTagIds_ = UserSettings::instance()->hiddenTags(); spaceOrder_.restoreCollapsed(); + endResetModel(); emit tagsChanged(); @@ -298,12 +315,12 @@ CommunitiesModel::FlatTree::storeCollapsed() for (const auto &e : tree) { if (e.depth > depth) { - current.push_back(e.name); + current.push_back(e.id); } else if (e.depth == depth) { - current.back() = e.name; + current.back() = e.id; } else { current.pop_back(); - current.back() = e.name; + current.back() = e.id; } if (e.collapsed) @@ -323,12 +340,12 @@ CommunitiesModel::FlatTree::restoreCollapsed() for (auto &e : tree) { if (e.depth > depth) { - current.push_back(e.name); + current.push_back(e.id); } else if (e.depth == depth) { - current.back() = e.name; + current.back() = e.id; } else { current.pop_back(); - current.back() = e.name; + current.back() = e.id; } if (elements.contains(current)) @@ -353,7 +370,6 @@ CommunitiesModel::sync(const mtx::responses::Sync &sync_) bool tagsUpdated = false; for (const auto &[roomid, room] : sync_.rooms.join) { - (void)roomid; for (const auto &e : room.account_data.events) if (std::holds_alternative< mtx::events::AccountDataEvent>(e)) { @@ -392,7 +408,7 @@ CommunitiesModel::sync(const mtx::responses::Sync &sync_) } void -CommunitiesModel::setCurrentTagId(QString tagId) +CommunitiesModel::setCurrentTagId(const QString &tagId) { if (tagId.startsWith(QLatin1String("tag:"))) { auto tag = tagId.mid(4); @@ -406,7 +422,7 @@ CommunitiesModel::setCurrentTagId(QString tagId) } else if (tagId.startsWith(QLatin1String("space:"))) { auto tag = tagId.mid(6); for (const auto &t : spaceOrder_.tree) { - if (t.name == tag) { + if (t.id == tag) { this->currentTagId_ = tagId; emit currentTagIdChanged(currentTagId_); return; @@ -449,6 +465,20 @@ CommunitiesModel::toggleTagId(QString tagId) emit hiddenTagsChanged(); } +int +CommunitiesModel::getChildNotifications(const QString &space_id) const +{ + auto children = cache::getRoomInfo(cache::client()->getChildRoomIds(space_id.toStdString())); + int total{0}; + for (const auto &[child_id, child] : children) { + if (child.is_space) + total += getChildNotifications(child_id); + else + total += child.notification_count; + } + return total; +} + FilteredCommunitiesModel::FilteredCommunitiesModel(CommunitiesModel *model, QObject *parent) : QSortFilterProxyModel(parent) { diff --git a/src/timeline/CommunitiesModel.h b/src/timeline/CommunitiesModel.h index 5da7d1bd..d54d9907 100644 --- a/src/timeline/CommunitiesModel.h +++ b/src/timeline/CommunitiesModel.h @@ -48,13 +48,14 @@ public: Parent, Depth, Id, + UnreadMessages, }; struct FlatTree { struct Elem { - QString name; + QString id; int depth = 0; bool collapsed = false; }; @@ -65,7 +66,7 @@ public: int indexOf(const QString &s) const { for (int i = 0; i < size(); i++) - if (tree[i].name == s) + if (tree[i].id == s) return i; return -1; } @@ -121,7 +122,7 @@ public slots: void sync(const mtx::responses::Sync &sync_); void clear(); QString currentTagId() const { return currentTagId_; } - void setCurrentTagId(QString tagId); + void setCurrentTagId(const QString &tagId); void resetCurrentTagId() { currentTagId_.clear(); @@ -147,6 +148,8 @@ signals: void containsSubspacesChanged(); private: + int getChildNotifications(const QString &space_id) const; + QStringList tags_; QString currentTagId_; QStringList hiddentTagIds_; From e7c24b094fcf400acd6b038105e13272cf87d617 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 19 Apr 2022 20:49:37 -0400 Subject: [PATCH 02/16] Fix typoed variable name --- src/timeline/CommunitiesModel.cpp | 18 +++++++++--------- src/timeline/CommunitiesModel.h | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp index b88896ee..116fa0a0 100644 --- a/src/timeline/CommunitiesModel.cpp +++ b/src/timeline/CommunitiesModel.cpp @@ -94,7 +94,7 @@ CommunitiesModel::data(const QModelIndex &index, int role) const case CommunitiesModel::Roles::Collapsible: return false; case CommunitiesModel::Roles::Hidden: - return hiddentTagIds_.contains(QStringLiteral("dm")); + return hiddenTagIds_.contains(QStringLiteral("dm")); case CommunitiesModel::Roles::Parent: return ""; case CommunitiesModel::Roles::Depth: @@ -119,7 +119,7 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return idx != spaceOrder_.lastChild(idx); } case CommunitiesModel::Roles::Hidden: - return hiddentTagIds_.contains("space:" + id); + return hiddenTagIds_.contains("space:" + id); case CommunitiesModel::Roles::Parent: { if (auto p = spaceOrder_.parent(index.row() - 2); p >= 0) return spaceOrder_.tree[p].id; @@ -174,7 +174,7 @@ CommunitiesModel::data(const QModelIndex &index, int role) const switch (role) { case CommunitiesModel::Roles::Hidden: - return hiddentTagIds_.contains("tag:" + tag); + return hiddenTagIds_.contains("tag:" + tag); case CommunitiesModel::Roles::Collapsed: return true; case CommunitiesModel::Roles::Collapsible: @@ -293,7 +293,7 @@ CommunitiesModel::initializeSidebar() for (const auto &t : ts) tags_.push_back(QString::fromStdString(t)); - hiddentTagIds_ = UserSettings::instance()->hiddenTags(); + hiddenTagIds_ = UserSettings::instance()->hiddenTags(); spaceOrder_.restoreCollapsed(); endResetModel(); @@ -441,12 +441,12 @@ CommunitiesModel::setCurrentTagId(const QString &tagId) void CommunitiesModel::toggleTagId(QString tagId) { - if (hiddentTagIds_.contains(tagId)) { - hiddentTagIds_.removeOne(tagId); - UserSettings::instance()->setHiddenTags(hiddentTagIds_); + if (hiddenTagIds_.contains(tagId)) { + hiddenTagIds_.removeOne(tagId); + UserSettings::instance()->setHiddenTags(hiddenTagIds_); } else { - hiddentTagIds_.push_back(tagId); - UserSettings::instance()->setHiddenTags(hiddentTagIds_); + hiddenTagIds_.push_back(tagId); + UserSettings::instance()->setHiddenTags(hiddenTagIds_); } if (tagId.startsWith(QLatin1String("tag:"))) { diff --git a/src/timeline/CommunitiesModel.h b/src/timeline/CommunitiesModel.h index d54d9907..30980f00 100644 --- a/src/timeline/CommunitiesModel.h +++ b/src/timeline/CommunitiesModel.h @@ -152,7 +152,7 @@ private: QStringList tags_; QString currentTagId_; - QStringList hiddentTagIds_; + QStringList hiddenTagIds_; FlatTree spaceOrder_; std::map spaces_; From 169384f0fa41cc8a52428f27bc3042dc849dd8c9 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 19 Apr 2022 23:18:11 -0400 Subject: [PATCH 03/16] Add space notifications to room list --- resources/qml/RoomList.qml | 108 +++++++++++++++++------------- src/Utils.cpp | 15 +++++ src/Utils.h | 3 + src/timeline/CommunitiesModel.cpp | 17 +---- src/timeline/CommunitiesModel.h | 2 - src/timeline/RoomlistModel.cpp | 5 +- src/timeline/TimelineModel.cpp | 17 +++-- 7 files changed, 98 insertions(+), 69 deletions(-) diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 910fc252..126d82be 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -348,58 +348,15 @@ Page { height: avatar.height spacing: Nheko.paddingSmall - RowLayout { - Layout.alignment: Qt.AlignTop - Layout.fillWidth: true - spacing: Nheko.paddingSmall - - ElidedLabel { - id: rN - Layout.alignment: Qt.AlignBaseline - color: roomItem.importantText - elideWidth: width - fullText: roomName - textFormat: Text.RichText - Layout.fillWidth: true - } - - Label { - id: timestamp - - visible: !isInvite && !isSpace - width: visible ? 0 : undefined - Layout.alignment: Qt.AlignRight | Qt.AlignBaseline - font.pixelSize: fontMetrics.font.pixelSize * 0.9 - color: roomItem.unimportantText - text: time - } - - } - - RowLayout { - Layout.fillWidth: true - spacing: 0 - visible: !isSpace - height: visible ? 0 : undefined - Layout.alignment: Qt.AlignBottom - - ElidedLabel { - color: roomItem.unimportantText - font.pixelSize: fontMetrics.font.pixelSize * 0.9 - elideWidth: width - fullText: lastMessage - textFormat: Text.RichText - Layout.fillWidth: true - } + Component { + id: notificationBubble Rectangle { - id: notificationBubble - visible: notificationCount > 0 Layout.alignment: Qt.AlignRight Layout.leftMargin: Nheko.paddingSmall height: notificationBubbleText.height + Nheko.paddingMedium - Layout.preferredWidth: Math.max(notificationBubbleText.width, height) + width: Math.max(notificationBubbleText.width, height) radius: height / 2 color: hasLoudNotification ? Nheko.theme.red : roomItem.bubbleBackground ToolTip.text: notificationCount @@ -425,7 +382,66 @@ Page { } } + } + RowLayout { + id: titleRow + + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + spacing: Nheko.paddingSmall + + ElidedLabel { + id: rN + Layout.alignment: Qt.AlignBaseline + color: roomItem.importantText + elideWidth: width + fullText: roomName + textFormat: Text.RichText + Layout.fillWidth: true + } + + Label { + id: timestamp + + visible: !isInvite && !isSpace + width: visible ? 0 : undefined + Layout.alignment: Qt.AlignRight | Qt.AlignBaseline + font.pixelSize: fontMetrics.font.pixelSize * 0.9 + color: roomItem.unimportantText + text: time + } + + Loader { + sourceComponent: notificationBubble + active: isSpace + } + + } + + RowLayout { + id: subtextRow + + Layout.fillWidth: true + spacing: 0 + visible: !isSpace + height: visible ? 0 : undefined + Layout.alignment: Qt.AlignBottom + + ElidedLabel { + color: roomItem.unimportantText + font.pixelSize: fontMetrics.font.pixelSize * 0.9 + elideWidth: width + fullText: lastMessage + textFormat: Text.RichText + Layout.fillWidth: true + } + + + Loader { + sourceComponent: notificationBubble + active: !isSpace + } } } diff --git a/src/Utils.cpp b/src/Utils.cpp index 0ac37d8e..aa36d3d9 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -27,6 +27,7 @@ #include #include "Cache.h" +#include "Cache_p.h" #include "Config.h" #include "EventAccessors.h" #include "Logging.h" @@ -880,3 +881,17 @@ utils::markRoomAsDirect(QString roomid, std::vector members) }); }); } + +int +utils::getChildNotificationsForSpace(const QString &spaceId) +{ + auto children = cache::getRoomInfo(cache::client()->getChildRoomIds(spaceId.toStdString())); + int total{0}; + for (const auto &[childId, child] : children) { + if (child.is_space) + total += utils::getChildNotificationsForSpace(childId); + else + total += child.notification_count; + } + return total; +} diff --git a/src/Utils.h b/src/Utils.h index 0b6034ac..c20544f2 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -311,4 +311,7 @@ removeDirectFromRoom(QString roomid); void markRoomAsDirect(QString roomid, std::vector members); + +int +getChildNotificationsForSpace(const QString &spaceId); } diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp index 116fa0a0..724a6e60 100644 --- a/src/timeline/CommunitiesModel.cpp +++ b/src/timeline/CommunitiesModel.cpp @@ -12,6 +12,7 @@ #include "ChatPage.h" #include "Logging.h" #include "UserSettingsPage.h" +#include "Utils.h" CommunitiesModel::CommunitiesModel(QObject *parent) : QAbstractListModel(parent) @@ -131,7 +132,7 @@ CommunitiesModel::data(const QModelIndex &index, int role) const case CommunitiesModel::Roles::Id: return "space:" + id; case CommunitiesModel::Roles::UnreadMessages: - return getChildNotifications(id); + return utils::getChildNotificationsForSpace(id); } } else if (index.row() - 2 < tags_.size() + spaceOrder_.size()) { auto tag = tags_.at(index.row() - 2 - spaceOrder_.size()); @@ -465,20 +466,6 @@ CommunitiesModel::toggleTagId(QString tagId) emit hiddenTagsChanged(); } -int -CommunitiesModel::getChildNotifications(const QString &space_id) const -{ - auto children = cache::getRoomInfo(cache::client()->getChildRoomIds(space_id.toStdString())); - int total{0}; - for (const auto &[child_id, child] : children) { - if (child.is_space) - total += getChildNotifications(child_id); - else - total += child.notification_count; - } - return total; -} - FilteredCommunitiesModel::FilteredCommunitiesModel(CommunitiesModel *model, QObject *parent) : QSortFilterProxyModel(parent) { diff --git a/src/timeline/CommunitiesModel.h b/src/timeline/CommunitiesModel.h index 30980f00..b29a5385 100644 --- a/src/timeline/CommunitiesModel.h +++ b/src/timeline/CommunitiesModel.h @@ -148,8 +148,6 @@ signals: void containsSubspacesChanged(); private: - int getChildNotifications(const QString &space_id) const; - QStringList tags_; QString currentTagId_; QStringList hiddenTagIds_; diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index 1cf16243..3b46c053 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -330,10 +330,13 @@ RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification) Qt::DisplayRole, }); + if (getRoomById(room_id)->isSpace()) + return; // no need to update space notifications + int total_unread_msgs = 0; for (const auto &room : qAsConst(models)) { - if (!room.isNull()) + if (!room.isNull() && !room->isSpace()) total_unread_msgs += room->notificationCount(); } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 3fe4c07f..308d358b 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -352,16 +352,23 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj { this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString()); - auto roomInfo = cache::singleRoomInfo(room_id_.toStdString()); - this->isSpace_ = roomInfo.is_space; - this->notification_count = roomInfo.notification_count; - this->highlight_count = roomInfo.highlight_count; - lastMessage_.timestamp = roomInfo.approximate_last_modification_ts; + auto roomInfo = cache::singleRoomInfo(room_id_.toStdString()); + this->isSpace_ = roomInfo.is_space; + this->notification_count = + isSpace_ ? utils::getChildNotificationsForSpace(room_id_) : roomInfo.notification_count; + this->highlight_count = roomInfo.highlight_count; + lastMessage_.timestamp = roomInfo.approximate_last_modification_ts; // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it // needs to be connect(this, &TimelineModel::roomNameChanged, this, &TimelineModel::plainRoomNameChanged); + if (isSpace_) + connect(ChatPage::instance(), &ChatPage::unreadMessages, this, [this](int) { + notification_count = utils::getChildNotificationsForSpace(room_id_); + emit notificationsChanged(); + }); + connect( this, &TimelineModel::redactionFailed, From e446e3d6792ff6a1664934b24e19fc80ffbbd22e Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Wed, 20 Apr 2022 21:30:16 -0400 Subject: [PATCH 04/16] Add loud notifications for spaces --- resources/qml/CommunitiesList.qml | 8 ++++---- src/Utils.cpp | 18 +++++++++++------- src/Utils.h | 4 +++- src/timeline/CommunitiesModel.cpp | 8 +++++++- src/timeline/CommunitiesModel.h | 1 + src/timeline/TimelineModel.cpp | 9 ++++++--- 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/resources/qml/CommunitiesList.qml b/resources/qml/CommunitiesList.qml index f5f20a91..371e347e 100644 --- a/resources/qml/CommunitiesList.qml +++ b/resources/qml/CommunitiesList.qml @@ -164,7 +164,7 @@ Page { height: collapsedNotificationBubbleText.height + Nheko.paddingMedium width: Math.max(collapsedNotificationBubbleText.width, height) radius: height / 2 - color: /*hasLoudNotification ? Nheko.theme.red :*/ communityItem.bubbleBackground + color: model.hasLoudNotification ? Nheko.theme.red : communityItem.bubbleBackground ToolTip.text: model.unreadMessages ToolTip.delay: Nheko.tooltipDelay ToolTip.visible: collapsedNotificationBubbleHover.hovered && (model.unreadMessages > 9999) @@ -178,7 +178,7 @@ Page { width: Math.max(implicitWidth + Nheko.paddingMedium, parent.height) font.bold: true font.pixelSize: fontMetrics.font.pixelSize * 0.6 - color: /*hasLoudNotification ? "white" :*/ communityItem.bubbleText + color: model.hasLoudNotification ? "white" : communityItem.bubbleText text: model.unreadMessages > 9999 ? "9999+" : model.unreadMessages HoverHandler { @@ -214,7 +214,7 @@ Page { height: notificationBubbleText.height + Nheko.paddingMedium Layout.preferredWidth: Math.max(notificationBubbleText.width, height) radius: height / 2 - color: /*hasLoudNotification ? Nheko.theme.red :*/ communityItem.bubbleBackground + color: model.hasLoudNotification ? Nheko.theme.red : communityItem.bubbleBackground ToolTip.text: model.unreadMessages ToolTip.delay: Nheko.tooltipDelay ToolTip.visible: notificationBubbleHover.hovered && (model.unreadMessages > 9999) @@ -228,7 +228,7 @@ Page { width: Math.max(implicitWidth + Nheko.paddingMedium, parent.height) font.bold: true font.pixelSize: fontMetrics.font.pixelSize * 0.8 - color: /*hasLoudNotification ? "white" :*/ communityItem.bubbleText + color: model.hasLoudNotification ? "white" : communityItem.bubbleText text: model.unreadMessages > 9999 ? "9999+" : model.unreadMessages HoverHandler { diff --git a/src/Utils.cpp b/src/Utils.cpp index aa36d3d9..3a90bd50 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -882,16 +882,20 @@ utils::markRoomAsDirect(QString roomid, std::vector members) }); } -int +QPair utils::getChildNotificationsForSpace(const QString &spaceId) { auto children = cache::getRoomInfo(cache::client()->getChildRoomIds(spaceId.toStdString())); - int total{0}; + QPair retVal; for (const auto &[childId, child] : children) { - if (child.is_space) - total += utils::getChildNotificationsForSpace(childId); - else - total += child.notification_count; + if (child.is_space) { + auto temp{utils::getChildNotificationsForSpace(childId)}; + retVal.first += temp.first; + retVal.second += temp.second; + } else { + retVal.first += child.notification_count; + retVal.second += child.highlight_count; + } } - return total; + return retVal; } diff --git a/src/Utils.h b/src/Utils.h index c20544f2..bdd56d55 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -312,6 +312,8 @@ removeDirectFromRoom(QString roomid); void markRoomAsDirect(QString roomid, std::vector members); -int +//! Returns a pair of integers representing the unread notifications in a space and how many of them +//! are loud notifications, respectively. +QPair getChildNotificationsForSpace(const QString &spaceId); } diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp index 724a6e60..ccc0adfe 100644 --- a/src/timeline/CommunitiesModel.cpp +++ b/src/timeline/CommunitiesModel.cpp @@ -37,6 +37,7 @@ CommunitiesModel::roleNames() const {Depth, "depth"}, {Id, "id"}, {UnreadMessages, "unreadMessages"}, + {HasLoudNotification, "hasLoudNotification"}, }; } @@ -80,6 +81,7 @@ CommunitiesModel::data(const QModelIndex &index, int role) const case CommunitiesModel::Roles::Id: return ""; case CommunitiesModel::Roles::UnreadMessages: + case CommunitiesModel::Roles::HasLoudNotification: return 0; } } else if (index.row() == 1) { @@ -103,6 +105,7 @@ CommunitiesModel::data(const QModelIndex &index, int role) const case CommunitiesModel::Roles::Id: return "dm"; case CommunitiesModel::Roles::UnreadMessages: + case CommunitiesModel::Roles::HasLoudNotification: return 0; } } else if (index.row() - 2 < spaceOrder_.size()) { @@ -132,7 +135,9 @@ CommunitiesModel::data(const QModelIndex &index, int role) const case CommunitiesModel::Roles::Id: return "space:" + id; case CommunitiesModel::Roles::UnreadMessages: - return utils::getChildNotificationsForSpace(id); + return utils::getChildNotificationsForSpace(id).first; + case CommunitiesModel::Roles::HasLoudNotification: + return utils::getChildNotificationsForSpace(id).second > 0; } } else if (index.row() - 2 < tags_.size() + spaceOrder_.size()) { auto tag = tags_.at(index.row() - 2 - spaceOrder_.size()); @@ -187,6 +192,7 @@ CommunitiesModel::data(const QModelIndex &index, int role) const case CommunitiesModel::Roles::Id: return "tag:" + tag; case CommunitiesModel::Roles::UnreadMessages: + case CommunitiesModel::Roles::HasLoudNotification: return 0; } } diff --git a/src/timeline/CommunitiesModel.h b/src/timeline/CommunitiesModel.h index b29a5385..7be98939 100644 --- a/src/timeline/CommunitiesModel.h +++ b/src/timeline/CommunitiesModel.h @@ -49,6 +49,7 @@ public: Depth, Id, UnreadMessages, + HasLoudNotification, }; struct FlatTree diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 308d358b..1a9f957b 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -355,8 +355,9 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj auto roomInfo = cache::singleRoomInfo(room_id_.toStdString()); this->isSpace_ = roomInfo.is_space; this->notification_count = - isSpace_ ? utils::getChildNotificationsForSpace(room_id_) : roomInfo.notification_count; - this->highlight_count = roomInfo.highlight_count; + isSpace_ ? utils::getChildNotificationsForSpace(room_id_).first : roomInfo.notification_count; + this->highlight_count = + isSpace_ ? utils::getChildNotificationsForSpace(room_id_).second : roomInfo.highlight_count; lastMessage_.timestamp = roomInfo.approximate_last_modification_ts; // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it @@ -365,7 +366,9 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj if (isSpace_) connect(ChatPage::instance(), &ChatPage::unreadMessages, this, [this](int) { - notification_count = utils::getChildNotificationsForSpace(room_id_); + auto temp{utils::getChildNotificationsForSpace(room_id_)}; + notification_count = temp.first; + highlight_count = temp.second; emit notificationsChanged(); }); From 8ec0577807c8abd3e3a1cee33f2d1efcc56b4a58 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 21 Apr 2022 18:26:14 -0400 Subject: [PATCH 05/16] Make the notification bubble its own component --- resources/qml/CommunitiesList.qml | 122 ++++++------------ .../qml/components/NotificationBubble.qml | 42 ++++++ resources/res.qrc | 1 + 3 files changed, 86 insertions(+), 79 deletions(-) create mode 100644 resources/qml/components/NotificationBubble.qml diff --git a/resources/qml/CommunitiesList.qml b/resources/qml/CommunitiesList.qml index 371e347e..1775e5fd 100644 --- a/resources/qml/CommunitiesList.qml +++ b/resources/qml/CommunitiesList.qml @@ -3,6 +3,7 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +import "./components" import "./dialogs" import Qt.labs.platform 1.1 as Platform import QtQml 2.12 @@ -57,19 +58,29 @@ Page { property color unimportantText: Nheko.colors.buttonText property color bubbleBackground: Nheko.colors.highlight property color bubbleText: Nheko.colors.highlightedText + required property string avatarUrl + required property string displayName + required property string tooltip + required property bool collapsed + required property bool collapsible + required property bool hidden + required property int depth + required property string id + required property int unreadMessages + required property bool hasLoudNotification height: avatarSize + 2 * Nheko.paddingMedium width: ListView.view.width state: "normal" ToolTip.visible: hovered && collapsed - ToolTip.text: model.tooltip + ToolTip.text: communityItem.tooltip ToolTip.delay: Nheko.tooltipDelay - onClicked: Communities.setCurrentTagId(model.id) - onPressAndHold: communityContextMenu.show(model.id) + onClicked: Communities.setCurrentTagId(communityItem.id) + onPressAndHold: communityContextMenu.show(communityItem.id) states: [ State { name: "highlight" - when: (communityItem.hovered || model.hidden) && !(Communities.currentTagId == model.id) + when: (communityItem.hovered || communityItem.hidden) && !(Communities.currentTagId === communityItem.id) PropertyChanges { target: communityItem @@ -83,7 +94,7 @@ Page { }, State { name: "selected" - when: Communities.currentTagId == model.id + when: Communities.currentTagId == communityItem.id PropertyChanges { target: communityItem @@ -102,7 +113,7 @@ Page { TapHandler { acceptedButtons: Qt.RightButton - onSingleTapped: communityContextMenu.show(model.id) + onSingleTapped: communityContextMenu.show(communityItem.id) gesturePolicy: TapHandler.ReleaseWithinBounds acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad } @@ -114,27 +125,27 @@ Page { spacing: Nheko.paddingMedium anchors.fill: parent anchors.margins: Nheko.paddingMedium - anchors.leftMargin: Nheko.paddingMedium + (communitySidebar.collapsed ? 0 : (fontMetrics.lineSpacing * model.depth)) + anchors.leftMargin: Nheko.paddingMedium + (communitySidebar.collapsed ? 0 : (fontMetrics.lineSpacing * communityItem.depth)) ImageButton { - visible: !communitySidebar.collapsed && model.collapsible + visible: !communitySidebar.collapsed && communityItem.collapsible Layout.preferredHeight: fontMetrics.lineSpacing Layout.preferredWidth: fontMetrics.lineSpacing Layout.alignment: Qt.AlignVCenter height: fontMetrics.lineSpacing width: fontMetrics.lineSpacing - image: model.collapsed ? ":/icons/icons/ui/collapsed.svg" : ":/icons/icons/ui/expanded.svg" + image: communityItem.collapsed ? ":/icons/icons/ui/collapsed.svg" : ":/icons/icons/ui/expanded.svg" ToolTip.visible: hovered ToolTip.delay: Nheko.tooltipDelay - ToolTip.text: model.collapsed ? qsTr("Expand") : qsTr("Collapse") + ToolTip.text: communityItem.collapsed ? qsTr("Expand") : qsTr("Collapse") hoverEnabled: true - onClicked: model.collapsed = !model.collapsed + onClicked: communityItem.collapsed = !communityItem.collapsed } Item { Layout.preferredWidth: fontMetrics.lineSpacing - visible: !communitySidebar.collapsed && !model.collapsible && Communities.containsSubspaces + visible: !communitySidebar.collapsed && !communityItem.collapsible && Communities.containsSubspaces } Avatar { @@ -145,48 +156,25 @@ Page { height: avatarSize width: avatarSize url: { - if (model.avatarUrl.startsWith("mxc://")) - return model.avatarUrl.replace("mxc://", "image://MxcImage/"); + if (communityItem.avatarUrl.startsWith("mxc://")) + return communityItem.avatarUrl.replace("mxc://", "image://MxcImage/"); else - return "image://colorimage/" + model.avatarUrl + "?" + communityItem.unimportantText; + return "image://colorimage/" + communityItem.avatarUrl + "?" + communityItem.unimportantText; } - roomid: model.id - displayName: model.displayName + roomid: communityItem.id + displayName: communityItem.displayName color: communityItem.backgroundColor - Rectangle { - id: collapsedNotificationBubble - - visible: model.unreadMessages > 0 && communitySidebar.collapsed + NotificationBubble { + notificationCount: communityItem.unreadMessages + hasLoudNotification: communityItem.hasLoudNotification + bubbleBackgroundColor: communityItem.bubbleBackground + bubbleTextColor: communityItem.bubbleText + font.pixelSize: fontMetrics.font.pixelSize * 0.6 + mayBeVisible: communitySidebar.collapsed anchors.right: avatar.right anchors.bottom: avatar.bottom anchors.margins: -Nheko.paddingSmall - height: collapsedNotificationBubbleText.height + Nheko.paddingMedium - width: Math.max(collapsedNotificationBubbleText.width, height) - radius: height / 2 - color: model.hasLoudNotification ? Nheko.theme.red : communityItem.bubbleBackground - ToolTip.text: model.unreadMessages - ToolTip.delay: Nheko.tooltipDelay - ToolTip.visible: collapsedNotificationBubbleHover.hovered && (model.unreadMessages > 9999) - - Label { - id: collapsedNotificationBubbleText - - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - width: Math.max(implicitWidth + Nheko.paddingMedium, parent.height) - font.bold: true - font.pixelSize: fontMetrics.font.pixelSize * 0.6 - color: model.hasLoudNotification ? "white" : communityItem.bubbleText - text: model.unreadMessages > 9999 ? "9999+" : model.unreadMessages - - HoverHandler { - id: collapsedNotificationBubbleHover - } - - } - } } @@ -197,7 +185,7 @@ Page { color: communityItem.importantText Layout.fillWidth: true elideWidth: width - fullText: model.displayName + fullText: communityItem.displayName textFormat: Text.PlainText } @@ -205,44 +193,20 @@ Page { Layout.fillWidth: true } - Rectangle { - id: notificationBubble - - visible: model.unreadMessages > 0 && !communitySidebar.collapsed + NotificationBubble { + notificationCount: communityItem.unreadMessages + hasLoudNotification: communityItem.hasLoudNotification + bubbleBackgroundColor: communityItem.bubbleBackground + bubbleTextColor: communityItem.bubbleText + mayBeVisible: !communitySidebar.collapsed Layout.alignment: Qt.AlignRight Layout.leftMargin: Nheko.paddingSmall - height: notificationBubbleText.height + Nheko.paddingMedium - Layout.preferredWidth: Math.max(notificationBubbleText.width, height) - radius: height / 2 - color: model.hasLoudNotification ? Nheko.theme.red : communityItem.bubbleBackground - ToolTip.text: model.unreadMessages - ToolTip.delay: Nheko.tooltipDelay - ToolTip.visible: notificationBubbleHover.hovered && (model.unreadMessages > 9999) - - Label { - id: notificationBubbleText - - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - width: Math.max(implicitWidth + Nheko.paddingMedium, parent.height) - font.bold: true - font.pixelSize: fontMetrics.font.pixelSize * 0.8 - color: model.hasLoudNotification ? "white" : communityItem.bubbleText - text: model.unreadMessages > 9999 ? "9999+" : model.unreadMessages - - HoverHandler { - id: notificationBubbleHover - } - - } - } } background: Rectangle { - color: backgroundColor + color: communityItem.backgroundColor } } diff --git a/resources/qml/components/NotificationBubble.qml b/resources/qml/components/NotificationBubble.qml new file mode 100644 index 00000000..07dd76a8 --- /dev/null +++ b/resources/qml/components/NotificationBubble.qml @@ -0,0 +1,42 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import im.nheko 1.0 + +Rectangle { + id: bubbleRoot + + required property int notificationCount + required property bool hasLoudNotification + required property color bubbleBackgroundColor + required property color bubbleTextColor + property bool mayBeVisible: true + property alias font: notificationBubbleText.font + + visible: mayBeVisible && notificationCount > 0 + implicitHeight: notificationBubbleText.height + Nheko.paddingMedium + implicitWidth: Math.max(notificationBubbleText.width, height) + radius: height / 2 + color: hasLoudNotification ? Nheko.theme.red : bubbleBackgroundColor + ToolTip.text: notificationCount + ToolTip.delay: Nheko.tooltipDelay + ToolTip.visible: notificationBubbleHover.hovered && (notificationCount > 9999) + + Label { + id: notificationBubbleText + + anchors.centerIn: bubbleRoot + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: Math.max(implicitWidth + Nheko.paddingMedium, bubbleRoot.height) + font.bold: true + font.pixelSize: fontMetrics.font.pixelSize * 0.8 + color: bubbleRoot.hasLoudNotification ? "white" : bubbleRoot.bubbleTextColor + text: bubbleRoot.notificationCount > 9999 ? "9999+" : bubbleRoot.notificationCount + + HoverHandler { + id: notificationBubbleHover + } + + } + +} diff --git a/resources/res.qrc b/resources/res.qrc index 6e3023ea..94901b2b 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -182,6 +182,7 @@ qml/voip/PlaceCall.qml qml/voip/ScreenShare.qml qml/voip/VideoCall.qml + qml/components/NotificationBubble.qml media/ring.ogg From c2b6728955a74975cc3c01c40f49e70a35b65bd4 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 21 Apr 2022 18:26:25 -0400 Subject: [PATCH 06/16] Add space notifs to room list --- resources/qml/RoomList.qml | 82 +++++++++----------------------------- 1 file changed, 18 insertions(+), 64 deletions(-) diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 126d82be..614abf92 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -3,6 +3,7 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +import "./components" import "./dialogs" import "./ui" import Qt.labs.platform 1.1 as Platform @@ -305,33 +306,17 @@ Page { userid: isDirect ? directChatOtherUserId : "" roomid: roomId - Rectangle { + NotificationBubble { id: collapsedNotificationBubble + notificationCount: roomItem.notificationCount + hasLoudNotification: roomItem.hasLoudNotification + bubbleBackgroundColor: roomItem.bubbleBackground + bubbleTextColor: roomItem.bubbleText anchors.right: parent.right anchors.bottom: parent.bottom anchors.margins: -Nheko.paddingSmall - visible: collapsed && notificationCount > 0 - enabled: false - Layout.alignment: Qt.AlignRight - height: fontMetrics.averageCharacterWidth * 3 - width: Math.max(collapsedBubbleText.width, height) - radius: height / 2 - color: hasLoudNotification ? Nheko.theme.red : roomItem.bubbleBackground - - Label { - id: collapsedBubbleText - - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - width: Math.max(implicitWidth + Nheko.paddingMedium, parent.height) - font.bold: true - font.pixelSize: fontMetrics.font.pixelSize * 0.8 - color: hasLoudNotification ? "white" : roomItem.bubbleText - text: notificationCount > 9999 ? "9999+" : notificationCount - } - + mayBeVisible: collapsed } } @@ -348,40 +333,19 @@ Page { height: avatar.height spacing: Nheko.paddingSmall - Component { + NotificationBubble { id: notificationBubble - Rectangle { - visible: notificationCount > 0 - Layout.alignment: Qt.AlignRight - Layout.leftMargin: Nheko.paddingSmall - height: notificationBubbleText.height + Nheko.paddingMedium - width: Math.max(notificationBubbleText.width, height) - radius: height / 2 - color: hasLoudNotification ? Nheko.theme.red : roomItem.bubbleBackground - ToolTip.text: notificationCount - ToolTip.delay: Nheko.tooltipDelay - ToolTip.visible: notificationBubbleHover.hovered && (notificationCount > 9999) - - Label { - id: notificationBubbleText - - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - width: Math.max(implicitWidth + Nheko.paddingMedium, parent.height) - font.bold: true - font.pixelSize: fontMetrics.font.pixelSize * 0.8 - color: hasLoudNotification ? "white" : roomItem.bubbleText - text: notificationCount > 9999 ? "9999+" : notificationCount - - HoverHandler { - id: notificationBubbleHover - } - - } - - } + parent: isSpace ? titleRow : subtextRow + notificationCount: roomItem.notificationCount + hasLoudNotification: roomItem.hasLoudNotification + bubbleBackgroundColor: roomItem.bubbleBackground + bubbleTextColor: roomItem.bubbleText + Layout.alignment: Qt.AlignRight + Layout.leftMargin: Nheko.paddingSmall + Layout.preferredWidth: implicitWidth + Layout.preferredHeight: implicitHeight + mayBeVisible: !collapsed } RowLayout { @@ -412,11 +376,6 @@ Page { text: time } - Loader { - sourceComponent: notificationBubble - active: isSpace - } - } RowLayout { @@ -437,11 +396,6 @@ Page { Layout.fillWidth: true } - - Loader { - sourceComponent: notificationBubble - active: !isSpace - } } } From 863eaa1910e84c0657a718f880462e661c220528 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 22 Apr 2022 20:34:15 -0400 Subject: [PATCH 07/16] Add space notification configuration --- resources/qml/CommunitiesList.qml | 27 ++++++++++++++-- resources/qml/RoomList.qml | 4 +-- src/UserSettingsPage.cpp | 52 +++++++++++++++++++++++++++++-- src/UserSettingsPage.h | 16 ++++++++++ 4 files changed, 92 insertions(+), 7 deletions(-) diff --git a/resources/qml/CommunitiesList.qml b/resources/qml/CommunitiesList.qml index 1775e5fd..62ac341a 100644 --- a/resources/qml/CommunitiesList.qml +++ b/resources/qml/CommunitiesList.qml @@ -171,7 +171,21 @@ Page { bubbleBackgroundColor: communityItem.bubbleBackground bubbleTextColor: communityItem.bubbleText font.pixelSize: fontMetrics.font.pixelSize * 0.6 - mayBeVisible: communitySidebar.collapsed + mayBeVisible: { + if (!communitySidebar.collapsed) + return false + else if (Settings.spaceNotifications === Settings.SpaceNotificationsOff) + return false + else if (Settings.spaceNotifications === Settings.SidebarHiddenRooms) + { + if (communityItem.hidden) + return true + else + return false + } + else + return true + } anchors.right: avatar.right anchors.bottom: avatar.bottom anchors.margins: -Nheko.paddingSmall @@ -198,7 +212,16 @@ Page { hasLoudNotification: communityItem.hasLoudNotification bubbleBackgroundColor: communityItem.bubbleBackground bubbleTextColor: communityItem.bubbleText - mayBeVisible: !communitySidebar.collapsed + mayBeVisible: { + if (communitySidebar.collapsed) + return false + else if (Settings.spaceNotification === Settings.SpaceNotificationsOff) + return false + else if ((Settings.spaceNotifications === Settings.SidebarHiddenRooms) && communityItem.hidden) + return true + else + return true + } Layout.alignment: Qt.AlignRight Layout.leftMargin: Nheko.paddingSmall } diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 614abf92..b34a94a1 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -316,7 +316,7 @@ Page { anchors.right: parent.right anchors.bottom: parent.bottom anchors.margins: -Nheko.paddingSmall - mayBeVisible: collapsed + mayBeVisible: collapsed && (isSpace ? Settings.spaceNotifications === Settings.SidebarAndRoomlist : true) } } @@ -345,7 +345,7 @@ Page { Layout.leftMargin: Nheko.paddingSmall Layout.preferredWidth: implicitWidth Layout.preferredHeight: implicitHeight - mayBeVisible: !collapsed + mayBeVisible: !collapsed && (isSpace ? Settings.spaceNotifications === Settings.SidebarAndRoomlist : true) } RowLayout { diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 666a03b4..c4cc339b 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -88,7 +88,15 @@ UserSettings::load(std::optional profile) openImageExternal_ = settings.value(QStringLiteral("user/open_image_external"), false).toBool(); openVideoExternal_ = settings.value(QStringLiteral("user/open_video_external"), false).toBool(); decryptSidebar_ = settings.value(QStringLiteral("user/decrypt_sidebar"), true).toBool(); - privacyScreen_ = settings.value(QStringLiteral("user/privacy_screen"), false).toBool(); + auto tempSpaceNotifs = settings.value(QStringLiteral("user/space_notifications"), QString{}) + .toString() + .toStdString(); + auto spaceNotifsValue = + QMetaEnum::fromType().keyToValue(tempSpaceNotifs.c_str()); + if (spaceNotifsValue < 0) + spaceNotifsValue = 0; + spaceNotifications_ = static_cast(spaceNotifsValue); + privacyScreen_ = settings.value(QStringLiteral("user/privacy_screen"), false).toBool(); privacyScreenTimeout_ = settings.value(QStringLiteral("user/privacy_screen_timeout"), 0).toInt(); exposeDBusApi_ = settings.value(QStringLiteral("user/expose_dbus_api"), false).toBool(); @@ -416,6 +424,16 @@ UserSettings::setDecryptSidebar(bool state) save(); } +void +UserSettings::setSpaceNotifications(SpaceNotificationOptions state) +{ + if (state == spaceNotifications_) + return; + spaceNotifications_ = state; + emit spaceNotificationsChanged(state); + save(); +} + void UserSettings::setPrivacyScreen(bool state) { @@ -777,6 +795,9 @@ UserSettings::save() settings.setValue(QStringLiteral("avatar_circles"), avatarCircles_); settings.setValue(QStringLiteral("decrypt_sidebar"), decryptSidebar_); + settings.setValue(QStringLiteral("space_notifications"), + QString::fromUtf8(QMetaEnum::fromType().valueToKey( + static_cast(spaceNotifications_)))); settings.setValue(QStringLiteral("privacy_screen"), privacyScreen_); settings.setValue(QStringLiteral("privacy_screen_timeout"), privacyScreenTimeout_); settings.setValue(QStringLiteral("mobile_mode"), mobileMode_); @@ -923,6 +944,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const return tr("Open videos with external program"); case DecryptSidebar: return tr("Decrypt messages in sidebar"); + case SpaceNotifications: + return tr("Show message counts for spaces"); case PrivacyScreen: return tr("Privacy Screen"); case PrivacyScreenTimeout: @@ -1053,6 +1076,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const return i->openVideoExternal(); case DecryptSidebar: return i->decryptSidebar(); + case SpaceNotifications: + return static_cast(i->spaceNotifications()); case PrivacyScreen: return i->privacyScreen(); case PrivacyScreenTimeout: @@ -1208,6 +1233,9 @@ UserSettingsModel::data(const QModelIndex &index, int role) const case DecryptSidebar: return tr("Decrypt the messages shown in the sidebar.\nOnly affects messages in " "encrypted chats."); + case SpaceNotifications: + return tr( + "Choose where to show the total number of notifications contained within a space."); case PrivacyScreen: return tr("When the window loses focus, the timeline will\nbe blurred."); case MobileMode: @@ -1283,6 +1311,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const case CameraResolution: case CameraFrameRate: case Ringtone: + case SpaceNotifications: return Options; case TimelineMaxWidth: case PrivacyScreenTimeout: @@ -1409,7 +1438,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const return fontDb.families(); case EmojiFont: return fontDb.families(QFontDatabase::WritingSystem::Symbol); - case Ringtone: + case Ringtone: { QStringList l{ QStringLiteral("Mute"), QStringLiteral("Default"), @@ -1419,6 +1448,12 @@ UserSettingsModel::data(const QModelIndex &index, int role) const l.push_back(i->ringtone()); return l; } + case SpaceNotifications: + return QStringList{QStringLiteral("Sidebar and room list"), + QStringLiteral("Sidebar"), + QStringLiteral("Sidebar (hidden rooms only)"), + QStringLiteral("Off")}; + } } else if (role == Good) { switch (index.row()) { case OnlineBackupKey: @@ -1624,6 +1659,15 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int return false; } return i->decryptSidebar(); + case SpaceNotifications: { + if (value.toInt() > + static_cast(UserSettings::SpaceNotificationOptions::SpaceNotificationsOff) || + value.toInt() < 0) + return false; + + i->setSpaceNotifications(value.value()); + return true; + } case PrivacyScreen: { if (value.userType() == QMetaType::Bool) { i->setPrivacyScreen(value.toBool()); @@ -1936,7 +1980,9 @@ UserSettingsModel::UserSettingsModel(QObject *p) connect(s.get(), &UserSettings::decryptSidebarChanged, this, [this]() { emit dataChanged(index(DecryptSidebar), index(DecryptSidebar), {Value}); }); - + connect(s.get(), &UserSettings::spaceNotificationsChanged, this, [this] { + emit dataChanged(index(SpaceNotifications), index(SpaceNotifications), {Value}); + }); connect(s.get(), &UserSettings::trayChanged, this, [this]() { emit dataChanged(index(Tray), index(Tray), {Value}); emit dataChanged(index(StartInTray), index(StartInTray), {Enabled}); diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index 1fb3ddcf..daf41383 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -58,6 +58,8 @@ class UserSettings : public QObject bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged) Q_PROPERTY( bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY decryptSidebarChanged) + Q_PROPERTY(SpaceNotificationOptions spaceNotifications READ spaceNotifications WRITE + setSpaceNotifications NOTIFY spaceNotificationsChanged) Q_PROPERTY( bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged) Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout @@ -134,6 +136,15 @@ public: }; Q_ENUM(Presence) + enum class SpaceNotificationOptions + { + SidebarAndRoomlist = 0, + Sidebar, + SidebarHiddenRooms, + SpaceNotificationsOff, + }; + Q_ENUM(SpaceNotificationOptions) + void save(); void load(std::optional profile); void applyTheme(); @@ -162,6 +173,7 @@ public: void setAlertOnNotification(bool state); void setAvatarCircles(bool state); void setDecryptSidebar(bool state); + void setSpaceNotifications(SpaceNotificationOptions state); void setPrivacyScreen(bool state); void setPrivacyScreenTimeout(int state); void setPresence(Presence state); @@ -202,6 +214,7 @@ public: bool groupView() const { return groupView_; } bool avatarCircles() const { return avatarCircles_; } bool decryptSidebar() const { return decryptSidebar_; } + SpaceNotificationOptions spaceNotifications() const { return spaceNotifications_; } bool privacyScreen() const { return privacyScreen_; } int privacyScreenTimeout() const { return privacyScreenTimeout_; } bool markdown() const { return markdown_; } @@ -278,6 +291,7 @@ signals: void alertOnNotificationChanged(bool state); void avatarCirclesChanged(bool state); void decryptSidebarChanged(bool state); + void spaceNotificationsChanged(SpaceNotificationOptions state); void privacyScreenChanged(bool state); void privacyScreenTimeoutChanged(int state); void timelineMaxWidthChanged(int state); @@ -340,6 +354,7 @@ private: bool hasAlertOnNotification_; bool avatarCircles_; bool decryptSidebar_; + SpaceNotificationOptions spaceNotifications_; bool privacyScreen_; int privacyScreenTimeout_; bool shareKeysWithTrustedUsers_; @@ -424,6 +439,7 @@ class UserSettingsModel : public QAbstractListModel GroupView, SortByImportance, DecryptSidebar, + SpaceNotifications, TraySection, Tray, From f03c506bdf8a83b96fef9b05b565fabc27341e48 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 23 Apr 2022 22:58:44 -0400 Subject: [PATCH 08/16] Inform Qt that loud notifications changed --- src/timeline/CommunitiesModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp index ccc0adfe..c89e7661 100644 --- a/src/timeline/CommunitiesModel.cpp +++ b/src/timeline/CommunitiesModel.cpp @@ -20,7 +20,7 @@ CommunitiesModel::CommunitiesModel(QObject *parent) connect(ChatPage::instance(), &ChatPage::unreadMessages, this, [this](int) { // Simply updating every space is easier than tracking which ones need updated. if (!spaces_.empty()) - emit dataChanged(index(2, 0), index(spaces_.size() + 2, 0), {Roles::UnreadMessages}); + emit dataChanged(index(2, 0), index(spaces_.size() + 2, 0), {Roles::UnreadMessages, Roles::HasLoudNotification}); }); } From 7e9646cc25e09aab594f10d6a2210b550d9fb0af Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 25 Apr 2022 20:54:40 -0400 Subject: [PATCH 09/16] Add notifications to all sidebar items --- src/timeline/CommunitiesModel.cpp | 53 +++++++++++++++++++++++++------ src/timeline/CommunitiesModel.h | 2 ++ 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp index c89e7661..34da6157 100644 --- a/src/timeline/CommunitiesModel.cpp +++ b/src/timeline/CommunitiesModel.cpp @@ -20,7 +20,7 @@ CommunitiesModel::CommunitiesModel(QObject *parent) connect(ChatPage::instance(), &ChatPage::unreadMessages, this, [this](int) { // Simply updating every space is easier than tracking which ones need updated. if (!spaces_.empty()) - emit dataChanged(index(2, 0), index(spaces_.size() + 2, 0), {Roles::UnreadMessages, Roles::HasLoudNotification}); + emit dataChanged(index(0, 0), index(spaces_.size() + tags_.size() + 1, 0), {Roles::UnreadMessages, Roles::HasLoudNotification}); }); } @@ -80,9 +80,17 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return 0; case CommunitiesModel::Roles::Id: return ""; - case CommunitiesModel::Roles::UnreadMessages: + case CommunitiesModel::Roles::UnreadMessages: { + int total{0}; + for (const auto &[id, info] : cache::getRoomInfo(cache::joinedRooms())) + total += info.notification_count; + return total; + } case CommunitiesModel::Roles::HasLoudNotification: - return 0; + for (const auto &[id, info] : cache::getRoomInfo(cache::joinedRooms())) + if (info.highlight_count > 0) + return true; + return false; } } else if (index.row() == 1) { switch (role) { @@ -104,9 +112,17 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return 0; case CommunitiesModel::Roles::Id: return "dm"; - case CommunitiesModel::Roles::UnreadMessages: + case CommunitiesModel::Roles::UnreadMessages: { + int total{0}; + for (const auto &[id, info] : cache::getRoomInfo(directMessages_)) + total += info.notification_count; + return total; + } case CommunitiesModel::Roles::HasLoudNotification: - return 0; + for (const auto &[id, info] : cache::getRoomInfo(directMessages_)) + if (info.highlight_count > 0) + return true; + return false; } } else if (index.row() - 2 < spaceOrder_.size()) { auto id = spaceOrder_.tree.at(index.row() - 2).id; @@ -191,9 +207,22 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return 0; case CommunitiesModel::Roles::Id: return "tag:" + tag; - case CommunitiesModel::Roles::UnreadMessages: - case CommunitiesModel::Roles::HasLoudNotification: - return 0; + case CommunitiesModel::Roles::UnreadMessages: { + int total{0}; + auto rooms{cache::joinedRooms()}; + for (const auto &[roomid, info] : cache::getRoomInfo(rooms)) + if (std::find(std::begin(info.tags), std::end(info.tags), tag.toStdString()) != std::end(info.tags)) + total += info.notification_count; + return total; + } + case CommunitiesModel::Roles::HasLoudNotification: { + auto rooms{cache::joinedRooms()}; + for (const auto &[roomid, info] : cache::getRoomInfo(rooms)) + if (std::find(std::begin(info.tags), std::end(info.tags), tag.toStdString()) != std::end(info.tags)) + if (info.highlight_count > 0) + return true; + return false; + } } } return QVariant(); @@ -403,8 +432,12 @@ CommunitiesModel::sync(const mtx::responses::Sync &sync_) tagsUpdated = true; } for (const auto &e : sync_.account_data.events) { - if (std::holds_alternative< - mtx::events::AccountDataEvent>(e)) { + if (auto event = + std::get_if>(&e)) { + directMessages_.clear(); + for (const auto &[userId, roomIds] : event->content.user_to_rooms) + for (const auto &roomId : roomIds) + directMessages_.push_back(roomId); tagsUpdated = true; break; } diff --git a/src/timeline/CommunitiesModel.h b/src/timeline/CommunitiesModel.h index 7be98939..bcb487e7 100644 --- a/src/timeline/CommunitiesModel.h +++ b/src/timeline/CommunitiesModel.h @@ -50,6 +50,7 @@ public: Id, UnreadMessages, HasLoudNotification, + IsDirect, }; struct FlatTree @@ -154,6 +155,7 @@ private: QStringList hiddenTagIds_; FlatTree spaceOrder_; std::map spaces_; + std::vector directMessages_; friend class FilteredCommunitiesModel; }; From 4428e0e0b87f710501fb8210b8c310204bf3603e Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 25 Apr 2022 20:57:18 -0400 Subject: [PATCH 10/16] make lint --- src/timeline/CommunitiesModel.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp index 34da6157..8e1cf140 100644 --- a/src/timeline/CommunitiesModel.cpp +++ b/src/timeline/CommunitiesModel.cpp @@ -20,7 +20,9 @@ CommunitiesModel::CommunitiesModel(QObject *parent) connect(ChatPage::instance(), &ChatPage::unreadMessages, this, [this](int) { // Simply updating every space is easier than tracking which ones need updated. if (!spaces_.empty()) - emit dataChanged(index(0, 0), index(spaces_.size() + tags_.size() + 1, 0), {Roles::UnreadMessages, Roles::HasLoudNotification}); + emit dataChanged(index(0, 0), + index(spaces_.size() + tags_.size() + 1, 0), + {Roles::UnreadMessages, Roles::HasLoudNotification}); }); } @@ -211,14 +213,16 @@ CommunitiesModel::data(const QModelIndex &index, int role) const int total{0}; auto rooms{cache::joinedRooms()}; for (const auto &[roomid, info] : cache::getRoomInfo(rooms)) - if (std::find(std::begin(info.tags), std::end(info.tags), tag.toStdString()) != std::end(info.tags)) + if (std::find(std::begin(info.tags), std::end(info.tags), tag.toStdString()) != + std::end(info.tags)) total += info.notification_count; return total; } case CommunitiesModel::Roles::HasLoudNotification: { auto rooms{cache::joinedRooms()}; for (const auto &[roomid, info] : cache::getRoomInfo(rooms)) - if (std::find(std::begin(info.tags), std::end(info.tags), tag.toStdString()) != std::end(info.tags)) + if (std::find(std::begin(info.tags), std::end(info.tags), tag.toStdString()) != + std::end(info.tags)) if (info.highlight_count > 0) return true; return false; From 7cbde0712f2f46a8cbed133aa01889f2de99e0e5 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 28 Apr 2022 17:00:12 -0400 Subject: [PATCH 11/16] Allow muting spaces; other general improvements --- resources/qml/CommunitiesList.qml | 33 ++++++++++++++++++---- src/UserSettingsPage.cpp | 19 +++++++++---- src/UserSettingsPage.h | 9 ++++-- src/timeline/CommunitiesModel.cpp | 47 +++++++++++++++++++++++++++---- src/timeline/CommunitiesModel.h | 3 ++ 5 files changed, 92 insertions(+), 19 deletions(-) diff --git a/resources/qml/CommunitiesList.qml b/resources/qml/CommunitiesList.qml index 62ac341a..5bc20dbe 100644 --- a/resources/qml/CommunitiesList.qml +++ b/resources/qml/CommunitiesList.qml @@ -37,14 +37,27 @@ Page { id: communityContextMenu property string tagId + property bool hidden + property bool muted - function show(id_, tags_) { + function show(id_, hidden_, muted_) { tagId = id_; + hidden = hidden_; + muted = muted_; open(); } + Platform.MenuItem { + text: qsTr("Do not show notification counts for this space or tag.") + checkable: true + checked: communityContextMenu.muted + onTriggered: Communities.toggleTagMute(communityContextMenu.tagId) + } + Platform.MenuItem { text: qsTr("Hide rooms with this tag or from this space by default.") + checkable: true + checked: communityContextMenu.hidden onTriggered: Communities.toggleTagId(communityContextMenu.tagId) } @@ -68,6 +81,7 @@ Page { required property string id required property int unreadMessages required property bool hasLoudNotification + required property bool muted height: avatarSize + 2 * Nheko.paddingMedium width: ListView.view.width @@ -76,7 +90,7 @@ Page { ToolTip.text: communityItem.tooltip ToolTip.delay: Nheko.tooltipDelay onClicked: Communities.setCurrentTagId(communityItem.id) - onPressAndHold: communityContextMenu.show(communityItem.id) + onPressAndHold: communityContextMenu.show(communityItem.id, communityItem.hidden, communityItem.muted) states: [ State { name: "highlight" @@ -113,7 +127,7 @@ Page { TapHandler { acceptedButtons: Qt.RightButton - onSingleTapped: communityContextMenu.show(communityItem.id) + onSingleTapped: communityContextMenu.show(communityItem.id, communityItem.hidden, communityItem.muted) gesturePolicy: TapHandler.ReleaseWithinBounds acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad } @@ -174,6 +188,8 @@ Page { mayBeVisible: { if (!communitySidebar.collapsed) return false + else if (communityItem.muted) + return false else if (Settings.spaceNotifications === Settings.SpaceNotificationsOff) return false else if (Settings.spaceNotifications === Settings.SidebarHiddenRooms) @@ -215,10 +231,17 @@ Page { mayBeVisible: { if (communitySidebar.collapsed) return false + else if (communityItem.muted) + return false else if (Settings.spaceNotification === Settings.SpaceNotificationsOff) return false - else if ((Settings.spaceNotifications === Settings.SidebarHiddenRooms) && communityItem.hidden) - return true + else if (Settings.spaceNotifications === Settings.SidebarHiddenRooms) + { + if (communityItem.hidden) + return true + else + return false + } else return true } diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index c4cc339b..2c6dc782 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -140,7 +140,8 @@ UserSettings::load(std::optional profile) userId_ = settings.value(prefix + "auth/user_id", "").toString(); deviceId_ = settings.value(prefix + "auth/device_id", "").toString(); hiddenTags_ = settings.value(prefix + "user/hidden_tags", QStringList{}).toStringList(); - hiddenPins_ = settings.value(prefix + "user/hidden_pins", QStringList{}).toStringList(); + mutedTags_ = settings.value(prefix + "user/muted_tags", QStringList{"global"}).toStringList(); + hiddenPins_ = settings.value(prefix + "user/hidden_pins", QStringList{}).toStringList(); hiddenWidgets_ = settings.value(prefix + "user/hidden_widgets", QStringList{}).toStringList(); recentReactions_ = settings.value(prefix + "user/recent_reactions", QStringList{}).toStringList(); @@ -228,14 +229,21 @@ UserSettings::setGroupView(bool state) } void -UserSettings::setHiddenTags(QStringList hiddenTags) +UserSettings::setHiddenTags(const QStringList &hiddenTags) { hiddenTags_ = hiddenTags; save(); } void -UserSettings::setHiddenPins(QStringList hiddenTags) +UserSettings::setMutedTags(const QStringList &mutedTags) +{ + mutedTags_ = mutedTags; + save(); +} + +void +UserSettings::setHiddenPins(const QStringList &hiddenTags) { hiddenPins_ = hiddenTags; save(); @@ -243,7 +251,7 @@ UserSettings::setHiddenPins(QStringList hiddenTags) } void -UserSettings::setHiddenWidgets(QStringList hiddenTags) +UserSettings::setHiddenWidgets(const QStringList &hiddenTags) { hiddenWidgets_ = hiddenTags; save(); @@ -851,6 +859,7 @@ UserSettings::save() onlyShareKeysWithVerifiedUsers_); settings.setValue(prefix + "user/online_key_backup", useOnlineKeyBackup_); settings.setValue(prefix + "user/hidden_tags", hiddenTags_); + settings.setValue(prefix + "user/muted_tags", mutedTags_); settings.setValue(prefix + "user/hidden_pins", hiddenPins_); settings.setValue(prefix + "user/hidden_widgets", hiddenWidgets_); settings.setValue(prefix + "user/recent_reactions", recentReactions_); @@ -1451,7 +1460,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const case SpaceNotifications: return QStringList{QStringLiteral("Sidebar and room list"), QStringLiteral("Sidebar"), - QStringLiteral("Sidebar (hidden rooms only)"), + QStringLiteral("Sidebar (hidden spaces and tags only)"), QStringLiteral("Off")}; } } else if (role == Good) { diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index daf41383..db12b1e6 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -196,9 +196,10 @@ public: void setDeviceId(QString deviceId); void setHomeserver(QString homeserver); void setDisableCertificateValidation(bool disabled); - void setHiddenTags(QStringList hiddenTags); - void setHiddenPins(QStringList hiddenTags); - void setHiddenWidgets(QStringList hiddenTags); + void setHiddenTags(const QStringList &hiddenTags); + void setMutedTags(const QStringList &mutedTags); + void setHiddenPins(const QStringList &hiddenTags); + void setHiddenWidgets(const QStringList &hiddenTags); void setRecentReactions(QStringList recent); void setUseIdenticon(bool state); void setOpenImageExternal(bool state); @@ -263,6 +264,7 @@ public: QString homeserver() const { return homeserver_; } bool disableCertificateValidation() const { return disableCertificateValidation_; } QStringList hiddenTags() const { return hiddenTags_; } + QStringList mutedTags() const { return mutedTags_; } QStringList hiddenPins() const { return hiddenPins_; } QStringList hiddenWidgets() const { return hiddenWidgets_; } QStringList recentReactions() const { return recentReactions_; } @@ -385,6 +387,7 @@ private: QString deviceId_; QString homeserver_; QStringList hiddenTags_; + QStringList mutedTags_; QStringList hiddenPins_; QStringList hiddenWidgets_; QStringList recentReactions_; diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp index 8e1cf140..91f7d555 100644 --- a/src/timeline/CommunitiesModel.cpp +++ b/src/timeline/CommunitiesModel.cpp @@ -16,6 +16,8 @@ CommunitiesModel::CommunitiesModel(QObject *parent) : QAbstractListModel(parent) + , hiddenTagIds_{UserSettings::instance()->hiddenTags()} + , mutedTagIds_{UserSettings::instance()->mutedTags()} { connect(ChatPage::instance(), &ChatPage::unreadMessages, this, [this](int) { // Simply updating every space is easier than tracking which ones need updated. @@ -40,6 +42,7 @@ CommunitiesModel::roleNames() const {Id, "id"}, {UnreadMessages, "unreadMessages"}, {HasLoudNotification, "hasLoudNotification"}, + {Muted, "muted"}, }; } @@ -62,6 +65,13 @@ CommunitiesModel::setData(const QModelIndex &index, const QVariant &value, int r QVariant CommunitiesModel::data(const QModelIndex &index, int role) const { + if (role == CommunitiesModel::Roles::Muted) { + if (index.row() == 0) + return mutedTagIds_.contains(QStringLiteral("global")); + else + return mutedTagIds_.contains(data(index, CommunitiesModel::Roles::Id).toString()); + } + if (index.row() == 0) { switch (role) { case CommunitiesModel::Roles::AvatarUrl: @@ -333,7 +343,6 @@ CommunitiesModel::initializeSidebar() for (const auto &t : ts) tags_.push_back(QString::fromStdString(t)); - hiddenTagIds_ = UserSettings::instance()->hiddenTags(); spaceOrder_.restoreCollapsed(); endResetModel(); @@ -485,13 +494,11 @@ CommunitiesModel::setCurrentTagId(const QString &tagId) void CommunitiesModel::toggleTagId(QString tagId) { - if (hiddenTagIds_.contains(tagId)) { + if (hiddenTagIds_.contains(tagId)) hiddenTagIds_.removeOne(tagId); - UserSettings::instance()->setHiddenTags(hiddenTagIds_); - } else { + else hiddenTagIds_.push_back(tagId); - UserSettings::instance()->setHiddenTags(hiddenTagIds_); - } + UserSettings::instance()->setHiddenTags(hiddenTagIds_); if (tagId.startsWith(QLatin1String("tag:"))) { auto idx = tags_.indexOf(tagId.mid(4)); @@ -509,6 +516,34 @@ CommunitiesModel::toggleTagId(QString tagId) emit hiddenTagsChanged(); } +void +CommunitiesModel::toggleTagMute(QString tagId) +{ + if (tagId.isEmpty()) + tagId = QStringLiteral("global"); + + if (mutedTagIds_.contains(tagId)) + mutedTagIds_.removeOne(tagId); + else + mutedTagIds_.push_back(tagId); + UserSettings::instance()->setMutedTags(mutedTagIds_); + + if (tagId.startsWith(QLatin1String("tag:"))) { + auto idx = tags_.indexOf(tagId.mid(4)); + if (idx != -1) + emit dataChanged(index(idx + 1 + spaceOrder_.size()), + index(idx + 1 + spaceOrder_.size())); + } else if (tagId.startsWith(QLatin1String("space:"))) { + auto idx = spaceOrder_.indexOf(tagId.mid(6)); + if (idx != -1) + emit dataChanged(index(idx + 1), index(idx + 1)); + } else if (tagId == QLatin1String("dm")) { + emit dataChanged(index(1), index(1)); + } else if (tagId == QLatin1String("global")) { + emit dataChanged(index(0), index(0)); + } +} + FilteredCommunitiesModel::FilteredCommunitiesModel(CommunitiesModel *model, QObject *parent) : QSortFilterProxyModel(parent) { diff --git a/src/timeline/CommunitiesModel.h b/src/timeline/CommunitiesModel.h index bcb487e7..5a659751 100644 --- a/src/timeline/CommunitiesModel.h +++ b/src/timeline/CommunitiesModel.h @@ -50,6 +50,7 @@ public: Id, UnreadMessages, HasLoudNotification, + Muted, IsDirect, }; @@ -141,6 +142,7 @@ public slots: return tagsWD; } void toggleTagId(QString tagId); + void toggleTagMute(QString tagId); FilteredCommunitiesModel *filtered() { return new FilteredCommunitiesModel(this, this); } signals: @@ -153,6 +155,7 @@ private: QStringList tags_; QString currentTagId_; QStringList hiddenTagIds_; + QStringList mutedTagIds_; FlatTree spaceOrder_; std::map spaces_; std::vector directMessages_; From f264941615e67100d5dda94bd7b8606e47ed0351 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 28 Apr 2022 17:16:47 -0400 Subject: [PATCH 12/16] make license --- resources/qml/components/NotificationBubble.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/resources/qml/components/NotificationBubble.qml b/resources/qml/components/NotificationBubble.qml index 07dd76a8..ca0ae6cb 100644 --- a/resources/qml/components/NotificationBubble.qml +++ b/resources/qml/components/NotificationBubble.qml @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2022 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + import QtQuick 2.15 import QtQuick.Controls 2.15 import im.nheko 1.0 From 885a790b1fa16524058a5c8aacf3ec8e9641709d Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 6 Jun 2022 12:07:25 -0400 Subject: [PATCH 13/16] Simplify space notification options --- resources/qml/CommunitiesList.qml | 36 ++----------------------------- resources/qml/RoomList.qml | 4 ++-- src/UserSettingsPage.cpp | 34 ++++++++--------------------- src/UserSettingsPage.h | 19 +++++----------- 4 files changed, 18 insertions(+), 75 deletions(-) diff --git a/resources/qml/CommunitiesList.qml b/resources/qml/CommunitiesList.qml index 5bc20dbe..ec9ef940 100644 --- a/resources/qml/CommunitiesList.qml +++ b/resources/qml/CommunitiesList.qml @@ -185,23 +185,7 @@ Page { bubbleBackgroundColor: communityItem.bubbleBackground bubbleTextColor: communityItem.bubbleText font.pixelSize: fontMetrics.font.pixelSize * 0.6 - mayBeVisible: { - if (!communitySidebar.collapsed) - return false - else if (communityItem.muted) - return false - else if (Settings.spaceNotifications === Settings.SpaceNotificationsOff) - return false - else if (Settings.spaceNotifications === Settings.SidebarHiddenRooms) - { - if (communityItem.hidden) - return true - else - return false - } - else - return true - } + mayBeVisible: communitySidebar.collapsed && !communityItem.muted && Settings.spaceNotifications anchors.right: avatar.right anchors.bottom: avatar.bottom anchors.margins: -Nheko.paddingSmall @@ -228,23 +212,7 @@ Page { hasLoudNotification: communityItem.hasLoudNotification bubbleBackgroundColor: communityItem.bubbleBackground bubbleTextColor: communityItem.bubbleText - mayBeVisible: { - if (communitySidebar.collapsed) - return false - else if (communityItem.muted) - return false - else if (Settings.spaceNotification === Settings.SpaceNotificationsOff) - return false - else if (Settings.spaceNotifications === Settings.SidebarHiddenRooms) - { - if (communityItem.hidden) - return true - else - return false - } - else - return true - } + mayBeVisible: !communitySidebar.collapsed && !communityItem.muted && Settings.spaceNotifications Layout.alignment: Qt.AlignRight Layout.leftMargin: Nheko.paddingSmall } diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index b34a94a1..ea066876 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -316,7 +316,7 @@ Page { anchors.right: parent.right anchors.bottom: parent.bottom anchors.margins: -Nheko.paddingSmall - mayBeVisible: collapsed && (isSpace ? Settings.spaceNotifications === Settings.SidebarAndRoomlist : true) + mayBeVisible: collapsed && (isSpace ? Settings.spaceNotifications : true) } } @@ -345,7 +345,7 @@ Page { Layout.leftMargin: Nheko.paddingSmall Layout.preferredWidth: implicitWidth Layout.preferredHeight: implicitHeight - mayBeVisible: !collapsed && (isSpace ? Settings.spaceNotifications === Settings.SidebarAndRoomlist : true) + mayBeVisible: !collapsed && (isSpace ? Settings.spaceNotifications : true) } RowLayout { diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 2c6dc782..b850d2e5 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -88,14 +88,7 @@ UserSettings::load(std::optional profile) openImageExternal_ = settings.value(QStringLiteral("user/open_image_external"), false).toBool(); openVideoExternal_ = settings.value(QStringLiteral("user/open_video_external"), false).toBool(); decryptSidebar_ = settings.value(QStringLiteral("user/decrypt_sidebar"), true).toBool(); - auto tempSpaceNotifs = settings.value(QStringLiteral("user/space_notifications"), QString{}) - .toString() - .toStdString(); - auto spaceNotifsValue = - QMetaEnum::fromType().keyToValue(tempSpaceNotifs.c_str()); - if (spaceNotifsValue < 0) - spaceNotifsValue = 0; - spaceNotifications_ = static_cast(spaceNotifsValue); + spaceNotifications_ = settings.value(QStringLiteral("user/space_notifications"), true).toBool(); privacyScreen_ = settings.value(QStringLiteral("user/privacy_screen"), false).toBool(); privacyScreenTimeout_ = settings.value(QStringLiteral("user/privacy_screen_timeout"), 0).toInt(); @@ -433,7 +426,7 @@ UserSettings::setDecryptSidebar(bool state) } void -UserSettings::setSpaceNotifications(SpaceNotificationOptions state) +UserSettings::setSpaceNotifications(bool state) { if (state == spaceNotifications_) return; @@ -803,9 +796,7 @@ UserSettings::save() settings.setValue(QStringLiteral("avatar_circles"), avatarCircles_); settings.setValue(QStringLiteral("decrypt_sidebar"), decryptSidebar_); - settings.setValue(QStringLiteral("space_notifications"), - QString::fromUtf8(QMetaEnum::fromType().valueToKey( - static_cast(spaceNotifications_)))); + settings.setValue(QStringLiteral("space_notifications"), spaceNotifications_); settings.setValue(QStringLiteral("privacy_screen"), privacyScreen_); settings.setValue(QStringLiteral("privacy_screen_timeout"), privacyScreenTimeout_); settings.setValue(QStringLiteral("mobile_mode"), mobileMode_); @@ -1086,7 +1077,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const case DecryptSidebar: return i->decryptSidebar(); case SpaceNotifications: - return static_cast(i->spaceNotifications()); + return i->spaceNotifications(); case PrivacyScreen: return i->privacyScreen(); case PrivacyScreenTimeout: @@ -1320,7 +1311,6 @@ UserSettingsModel::data(const QModelIndex &index, int role) const case CameraResolution: case CameraFrameRate: case Ringtone: - case SpaceNotifications: return Options; case TimelineMaxWidth: case PrivacyScreenTimeout: @@ -1355,6 +1345,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const case ShareKeysWithTrustedUsers: case UseOnlineKeyBackup: case ExposeDBusApi: + case SpaceNotifications: return Toggle; case Profile: case UserId: @@ -1457,11 +1448,6 @@ UserSettingsModel::data(const QModelIndex &index, int role) const l.push_back(i->ringtone()); return l; } - case SpaceNotifications: - return QStringList{QStringLiteral("Sidebar and room list"), - QStringLiteral("Sidebar"), - QStringLiteral("Sidebar (hidden spaces and tags only)"), - QStringLiteral("Off")}; } } else if (role == Good) { switch (index.row()) { @@ -1669,13 +1655,11 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int } return i->decryptSidebar(); case SpaceNotifications: { - if (value.toInt() > - static_cast(UserSettings::SpaceNotificationOptions::SpaceNotificationsOff) || - value.toInt() < 0) + if (value.userType() == QMetaType::Bool) { + i->setSpaceNotifications(value.toBool()); + return true; + } else return false; - - i->setSpaceNotifications(value.value()); - return true; } case PrivacyScreen: { if (value.userType() == QMetaType::Bool) { diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index db12b1e6..7bb722f1 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -58,7 +58,7 @@ class UserSettings : public QObject bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged) Q_PROPERTY( bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY decryptSidebarChanged) - Q_PROPERTY(SpaceNotificationOptions spaceNotifications READ spaceNotifications WRITE + Q_PROPERTY(bool spaceNotifications READ spaceNotifications WRITE setSpaceNotifications NOTIFY spaceNotificationsChanged) Q_PROPERTY( bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged) @@ -136,15 +136,6 @@ public: }; Q_ENUM(Presence) - enum class SpaceNotificationOptions - { - SidebarAndRoomlist = 0, - Sidebar, - SidebarHiddenRooms, - SpaceNotificationsOff, - }; - Q_ENUM(SpaceNotificationOptions) - void save(); void load(std::optional profile); void applyTheme(); @@ -173,7 +164,7 @@ public: void setAlertOnNotification(bool state); void setAvatarCircles(bool state); void setDecryptSidebar(bool state); - void setSpaceNotifications(SpaceNotificationOptions state); + void setSpaceNotifications(bool state); void setPrivacyScreen(bool state); void setPrivacyScreenTimeout(int state); void setPresence(Presence state); @@ -215,7 +206,7 @@ public: bool groupView() const { return groupView_; } bool avatarCircles() const { return avatarCircles_; } bool decryptSidebar() const { return decryptSidebar_; } - SpaceNotificationOptions spaceNotifications() const { return spaceNotifications_; } + bool spaceNotifications() const { return spaceNotifications_; } bool privacyScreen() const { return privacyScreen_; } int privacyScreenTimeout() const { return privacyScreenTimeout_; } bool markdown() const { return markdown_; } @@ -293,7 +284,7 @@ signals: void alertOnNotificationChanged(bool state); void avatarCirclesChanged(bool state); void decryptSidebarChanged(bool state); - void spaceNotificationsChanged(SpaceNotificationOptions state); + void spaceNotificationsChanged(bool state); void privacyScreenChanged(bool state); void privacyScreenTimeoutChanged(int state); void timelineMaxWidthChanged(int state); @@ -356,7 +347,7 @@ private: bool hasAlertOnNotification_; bool avatarCircles_; bool decryptSidebar_; - SpaceNotificationOptions spaceNotifications_; + bool spaceNotifications_; bool privacyScreen_; int privacyScreenTimeout_; bool shareKeysWithTrustedUsers_; From e2e5e4926e1838918bad50a6576eddc5e0471cb9 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Wed, 29 Jun 2022 22:05:47 -0400 Subject: [PATCH 14/16] make lint --- src/UserSettingsPage.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index 7bb722f1..34a792eb 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -58,8 +58,8 @@ class UserSettings : public QObject bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged) Q_PROPERTY( bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY decryptSidebarChanged) - Q_PROPERTY(bool spaceNotifications READ spaceNotifications WRITE - setSpaceNotifications NOTIFY spaceNotificationsChanged) + Q_PROPERTY(bool spaceNotifications READ spaceNotifications WRITE setSpaceNotifications NOTIFY + spaceNotificationsChanged) Q_PROPERTY( bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged) Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout From f76fd4ca837ab7dcf17d7889cc555b20e5b9d654 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 15 Jul 2022 16:19:06 +0200 Subject: [PATCH 15/16] WIP --- src/timeline/CommunitiesModel.cpp | 142 +++++++++++++++++++----------- src/timeline/CommunitiesModel.h | 12 ++- src/timeline/TimelineModel.cpp | 24 ++--- src/timeline/TimelineModel.h | 3 +- 4 files changed, 107 insertions(+), 74 deletions(-) diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp index 91f7d555..0d47c64d 100644 --- a/src/timeline/CommunitiesModel.cpp +++ b/src/timeline/CommunitiesModel.cpp @@ -18,15 +18,7 @@ CommunitiesModel::CommunitiesModel(QObject *parent) : QAbstractListModel(parent) , hiddenTagIds_{UserSettings::instance()->hiddenTags()} , mutedTagIds_{UserSettings::instance()->mutedTags()} -{ - connect(ChatPage::instance(), &ChatPage::unreadMessages, this, [this](int) { - // Simply updating every space is easier than tracking which ones need updated. - if (!spaces_.empty()) - emit dataChanged(index(0, 0), - index(spaces_.size() + tags_.size() + 1, 0), - {Roles::UnreadMessages, Roles::HasLoudNotification}); - }); -} +{} QHash CommunitiesModel::roleNames() const @@ -92,17 +84,10 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return 0; case CommunitiesModel::Roles::Id: return ""; - case CommunitiesModel::Roles::UnreadMessages: { - int total{0}; - for (const auto &[id, info] : cache::getRoomInfo(cache::joinedRooms())) - total += info.notification_count; - return total; - } + case CommunitiesModel::Roles::UnreadMessages: + return (int)globalUnreads.notification_count; case CommunitiesModel::Roles::HasLoudNotification: - for (const auto &[id, info] : cache::getRoomInfo(cache::joinedRooms())) - if (info.highlight_count > 0) - return true; - return false; + return globalUnreads.highlight_count > 0; } } else if (index.row() == 1) { switch (role) { @@ -124,17 +109,10 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return 0; case CommunitiesModel::Roles::Id: return "dm"; - case CommunitiesModel::Roles::UnreadMessages: { - int total{0}; - for (const auto &[id, info] : cache::getRoomInfo(directMessages_)) - total += info.notification_count; - return total; - } + case CommunitiesModel::Roles::UnreadMessages: + return (int)dmUnreads.notification_count; case CommunitiesModel::Roles::HasLoudNotification: - for (const auto &[id, info] : cache::getRoomInfo(directMessages_)) - if (info.highlight_count > 0) - return true; - return false; + return dmUnreads.highlight_count > 0; } } else if (index.row() - 2 < spaceOrder_.size()) { auto id = spaceOrder_.tree.at(index.row() - 2).id; @@ -162,10 +140,20 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return spaceOrder_.tree.at(index.row() - 2).depth; case CommunitiesModel::Roles::Id: return "space:" + id; - case CommunitiesModel::Roles::UnreadMessages: - return utils::getChildNotificationsForSpace(id).first; - case CommunitiesModel::Roles::HasLoudNotification: - return utils::getChildNotificationsForSpace(id).second > 0; + case CommunitiesModel::Roles::UnreadMessages: { + int count = 0; + auto end = spaceOrder_.lastChild(index.row() - 2); + for (int i = index.row() - 2; i <= end; i++) + count += spaceOrder_.tree[i].notificationCounts.notification_count; + return count; + } + case CommunitiesModel::Roles::HasLoudNotification: { + auto end = spaceOrder_.lastChild(index.row() - 2); + for (int i = index.row() - 2; i <= end; i++) + if (spaceOrder_.tree[i].notificationCounts.highlight_count > 0) + return true; + return false; + } } } else if (index.row() - 2 < tags_.size() + spaceOrder_.size()) { auto tag = tags_.at(index.row() - 2 - spaceOrder_.size()); @@ -219,24 +207,10 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return 0; case CommunitiesModel::Roles::Id: return "tag:" + tag; - case CommunitiesModel::Roles::UnreadMessages: { - int total{0}; - auto rooms{cache::joinedRooms()}; - for (const auto &[roomid, info] : cache::getRoomInfo(rooms)) - if (std::find(std::begin(info.tags), std::end(info.tags), tag.toStdString()) != - std::end(info.tags)) - total += info.notification_count; - return total; - } - case CommunitiesModel::Roles::HasLoudNotification: { - auto rooms{cache::joinedRooms()}; - for (const auto &[roomid, info] : cache::getRoomInfo(rooms)) - if (std::find(std::begin(info.tags), std::end(info.tags), tag.toStdString()) != - std::end(info.tags)) - if (info.highlight_count > 0) - return true; - return false; - } + case CommunitiesModel::Roles::UnreadMessages: + return (int)tagNotificationCache.at(tag).notification_count; + case CommunitiesModel::Roles::HasLoudNotification: + return (int)tagNotificationCache.at(tag).highlight_count > 0; } } return QVariant(); @@ -438,6 +412,72 @@ CommunitiesModel::sync(const mtx::responses::Sync &sync_) e)) { tagsUpdated = true; } + + auto roomId = QString::fromStdString(roomid); + auto oldUnreads = roomNotificationCache[roomId]; + int notificationCDiff = -static_cast(oldUnreads.highlight_count) + + static_cast(room.unread_notifications.highlight_count); + int highlightCDiff = -static_cast(oldUnreads.highlight_count) + + static_cast(room.unread_notifications.highlight_count); + if (highlightCDiff || notificationCDiff) { + // bool hidden = hiddenTagIds_.contains(roomId); + globalUnreads.notification_count += notificationCDiff; + globalUnreads.highlight_count += highlightCDiff; + emit dataChanged(index(0), + index(0), + { + UnreadMessages, + HasLoudNotification, + }); + if (std::find(begin(directMessages_), end(directMessages_), roomid) != + end(directMessages_)) { + dmUnreads.notification_count += notificationCDiff; + dmUnreads.highlight_count += highlightCDiff; + emit dataChanged(index(1), + index(1), + { + UnreadMessages, + HasLoudNotification, + }); + } + + auto spaces = cache::client()->getParentRoomIds(roomid); + auto tags = cache::singleRoomInfo(roomid).tags; + + for (const auto &t : tags) { + auto tagId = QString::fromStdString(t); + auto &tNs = tagNotificationCache[tagId]; + tNs.notification_count += notificationCDiff; + tNs.highlight_count += highlightCDiff; + int idx = tags_.indexOf(tagId) + 2 + spaceOrder_.size(); + ; + emit dataChanged(index(idx), + index(idx), + { + UnreadMessages, + HasLoudNotification, + }); + } + + for (const auto &s : spaces) { + auto spaceId = QString::fromStdString(s); + + for (int i = 0; i < spaceOrder_.size(); i++) { + spaceOrder_.tree[i].notificationCounts.notification_count += notificationCDiff; + spaceOrder_.tree[i].notificationCounts.highlight_count += highlightCDiff; + + int idx = i; + do { + emit dataChanged(index(idx + 2), + index(idx + 2), + { + UnreadMessages, + HasLoudNotification, + }); + } while (idx != -1); + } + } + } } for (const auto &[roomid, room] : sync_.rooms.leave) { (void)room; diff --git a/src/timeline/CommunitiesModel.h b/src/timeline/CommunitiesModel.h index 5a659751..08269e21 100644 --- a/src/timeline/CommunitiesModel.h +++ b/src/timeline/CommunitiesModel.h @@ -22,7 +22,7 @@ class FilteredCommunitiesModel : public QSortFilterProxyModel Q_OBJECT public: - FilteredCommunitiesModel(CommunitiesModel *model, QObject *parent = nullptr); + explicit FilteredCommunitiesModel(CommunitiesModel *model, QObject *parent = nullptr); bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override; }; @@ -59,7 +59,10 @@ public: struct Elem { QString id; - int depth = 0; + int depth = 0; + + mtx::responses::UnreadNotifications notificationCounts = {0, 0}; + bool collapsed = false; }; @@ -160,5 +163,10 @@ private: std::map spaces_; std::vector directMessages_; + std::unordered_map roomNotificationCache; + std::unordered_map tagNotificationCache; + mtx::responses::UnreadNotifications globalUnreads{}; + mtx::responses::UnreadNotifications dmUnreads{}; + friend class FilteredCommunitiesModel; }; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index db56ac52..9b48a878 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -364,25 +364,11 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj { this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString()); - auto roomInfo = cache::singleRoomInfo(room_id_.toStdString()); - this->isSpace_ = roomInfo.is_space; - this->notification_count = - isSpace_ ? utils::getChildNotificationsForSpace(room_id_).first : roomInfo.notification_count; - this->highlight_count = - isSpace_ ? utils::getChildNotificationsForSpace(room_id_).second : roomInfo.highlight_count; - lastMessage_.timestamp = roomInfo.approximate_last_modification_ts; - - // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it - // needs to be - connect(this, &TimelineModel::roomNameChanged, this, &TimelineModel::plainRoomNameChanged); - - if (isSpace_) - connect(ChatPage::instance(), &ChatPage::unreadMessages, this, [this](int) { - auto temp{utils::getChildNotificationsForSpace(room_id_)}; - notification_count = temp.first; - highlight_count = temp.second; - emit notificationsChanged(); - }); + auto roomInfo = cache::singleRoomInfo(room_id_.toStdString()); + this->isSpace_ = roomInfo.is_space; + this->notification_count = roomInfo.notification_count; + this->highlight_count = roomInfo.highlight_count; + lastMessage_.timestamp = roomInfo.approximate_last_modification_ts; connect( this, diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 6d424981..47fd27f1 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -182,7 +182,7 @@ class TimelineModel : public QAbstractListModel bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged) Q_PROPERTY(QString roomId READ roomId CONSTANT) Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) - Q_PROPERTY(QString plainRoomName READ plainRoomName NOTIFY plainRoomNameChanged) + Q_PROPERTY(QString plainRoomName READ plainRoomName NOTIFY roomNameChanged) Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged) Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged) Q_PROPERTY(QStringList pinnedMessages READ pinnedMessages NOTIFY pinnedMessagesChanged) @@ -429,7 +429,6 @@ signals: void encryptionChanged(); void trustlevelChanged(); void roomNameChanged(); - void plainRoomNameChanged(); void roomTopicChanged(); void pinnedMessagesChanged(); void widgetLinksChanged(); From 7b33d14277c3a3ca3db9bf1415c1a7372645b8e1 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 16 Jul 2022 03:07:00 +0200 Subject: [PATCH 16/16] Make notification count calculation more efficient --- resources/qml/CommunitiesList.qml | 60 +++++++++------------- src/Utils.cpp | 18 ------- src/Utils.h | 5 -- src/timeline/CommunitiesModel.cpp | 84 ++++++++++++++++++++++++------- src/timeline/RoomlistModel.cpp | 17 ++++--- src/timeline/RoomlistModel.h | 8 +-- 6 files changed, 103 insertions(+), 89 deletions(-) diff --git a/resources/qml/CommunitiesList.qml b/resources/qml/CommunitiesList.qml index ec9ef940..ca63bffd 100644 --- a/resources/qml/CommunitiesList.qml +++ b/resources/qml/CommunitiesList.qml @@ -71,30 +71,20 @@ Page { property color unimportantText: Nheko.colors.buttonText property color bubbleBackground: Nheko.colors.highlight property color bubbleText: Nheko.colors.highlightedText - required property string avatarUrl - required property string displayName - required property string tooltip - required property bool collapsed - required property bool collapsible - required property bool hidden - required property int depth - required property string id - required property int unreadMessages - required property bool hasLoudNotification - required property bool muted + required property var model height: avatarSize + 2 * Nheko.paddingMedium width: ListView.view.width state: "normal" ToolTip.visible: hovered && collapsed - ToolTip.text: communityItem.tooltip + ToolTip.text: model.tooltip ToolTip.delay: Nheko.tooltipDelay - onClicked: Communities.setCurrentTagId(communityItem.id) - onPressAndHold: communityContextMenu.show(communityItem.id, communityItem.hidden, communityItem.muted) + onClicked: Communities.setCurrentTagId(model.id) + onPressAndHold: communityContextMenu.show(model.id, model.hidden, model.muted) states: [ State { name: "highlight" - when: (communityItem.hovered || communityItem.hidden) && !(Communities.currentTagId === communityItem.id) + when: (communityItem.hovered || model.hidden) && !(Communities.currentTagId === model.id) PropertyChanges { target: communityItem @@ -108,7 +98,7 @@ Page { }, State { name: "selected" - when: Communities.currentTagId == communityItem.id + when: Communities.currentTagId == model.id PropertyChanges { target: communityItem @@ -127,7 +117,7 @@ Page { TapHandler { acceptedButtons: Qt.RightButton - onSingleTapped: communityContextMenu.show(communityItem.id, communityItem.hidden, communityItem.muted) + onSingleTapped: communityContextMenu.show(model.id, model.hidden, model.muted) gesturePolicy: TapHandler.ReleaseWithinBounds acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad } @@ -139,27 +129,27 @@ Page { spacing: Nheko.paddingMedium anchors.fill: parent anchors.margins: Nheko.paddingMedium - anchors.leftMargin: Nheko.paddingMedium + (communitySidebar.collapsed ? 0 : (fontMetrics.lineSpacing * communityItem.depth)) + anchors.leftMargin: Nheko.paddingMedium + (communitySidebar.collapsed ? 0 : (fontMetrics.lineSpacing * model.depth)) ImageButton { - visible: !communitySidebar.collapsed && communityItem.collapsible + visible: !communitySidebar.collapsed && model.collapsible Layout.preferredHeight: fontMetrics.lineSpacing Layout.preferredWidth: fontMetrics.lineSpacing Layout.alignment: Qt.AlignVCenter height: fontMetrics.lineSpacing width: fontMetrics.lineSpacing - image: communityItem.collapsed ? ":/icons/icons/ui/collapsed.svg" : ":/icons/icons/ui/expanded.svg" + image: model.collapsed ? ":/icons/icons/ui/collapsed.svg" : ":/icons/icons/ui/expanded.svg" ToolTip.visible: hovered ToolTip.delay: Nheko.tooltipDelay - ToolTip.text: communityItem.collapsed ? qsTr("Expand") : qsTr("Collapse") + ToolTip.text: model.collapsed ? qsTr("Expand") : qsTr("Collapse") hoverEnabled: true - onClicked: communityItem.collapsed = !communityItem.collapsed + onClicked: model.collapsed = !model.collapsed } Item { Layout.preferredWidth: fontMetrics.lineSpacing - visible: !communitySidebar.collapsed && !communityItem.collapsible && Communities.containsSubspaces + visible: !communitySidebar.collapsed && !model.collapsible && Communities.containsSubspaces } Avatar { @@ -170,22 +160,22 @@ Page { height: avatarSize width: avatarSize url: { - if (communityItem.avatarUrl.startsWith("mxc://")) - return communityItem.avatarUrl.replace("mxc://", "image://MxcImage/"); + if (model.avatarUrl.startsWith("mxc://")) + return model.avatarUrl.replace("mxc://", "image://MxcImage/"); else - return "image://colorimage/" + communityItem.avatarUrl + "?" + communityItem.unimportantText; + return "image://colorimage/" + model.avatarUrl + "?" + communityItem.unimportantText; } - roomid: communityItem.id - displayName: communityItem.displayName + roomid: model.id + displayName: model.displayName color: communityItem.backgroundColor NotificationBubble { - notificationCount: communityItem.unreadMessages - hasLoudNotification: communityItem.hasLoudNotification + notificationCount: model.unreadMessages + hasLoudNotification: model.hasLoudNotification bubbleBackgroundColor: communityItem.bubbleBackground bubbleTextColor: communityItem.bubbleText font.pixelSize: fontMetrics.font.pixelSize * 0.6 - mayBeVisible: communitySidebar.collapsed && !communityItem.muted && Settings.spaceNotifications + mayBeVisible: communitySidebar.collapsed && !model.muted && Settings.spaceNotifications anchors.right: avatar.right anchors.bottom: avatar.bottom anchors.margins: -Nheko.paddingSmall @@ -199,7 +189,7 @@ Page { color: communityItem.importantText Layout.fillWidth: true elideWidth: width - fullText: communityItem.displayName + fullText: model.displayName textFormat: Text.PlainText } @@ -208,11 +198,11 @@ Page { } NotificationBubble { - notificationCount: communityItem.unreadMessages - hasLoudNotification: communityItem.hasLoudNotification + notificationCount: model.unreadMessages + hasLoudNotification: model.hasLoudNotification bubbleBackgroundColor: communityItem.bubbleBackground bubbleTextColor: communityItem.bubbleText - mayBeVisible: !communitySidebar.collapsed && !communityItem.muted && Settings.spaceNotifications + mayBeVisible: !communitySidebar.collapsed && !model.muted && Settings.spaceNotifications Layout.alignment: Qt.AlignRight Layout.leftMargin: Nheko.paddingSmall } diff --git a/src/Utils.cpp b/src/Utils.cpp index 3a90bd50..d98669e0 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -881,21 +881,3 @@ utils::markRoomAsDirect(QString roomid, std::vector members) }); }); } - -QPair -utils::getChildNotificationsForSpace(const QString &spaceId) -{ - auto children = cache::getRoomInfo(cache::client()->getChildRoomIds(spaceId.toStdString())); - QPair retVal; - for (const auto &[childId, child] : children) { - if (child.is_space) { - auto temp{utils::getChildNotificationsForSpace(childId)}; - retVal.first += temp.first; - retVal.second += temp.second; - } else { - retVal.first += child.notification_count; - retVal.second += child.highlight_count; - } - } - return retVal; -} diff --git a/src/Utils.h b/src/Utils.h index bdd56d55..0b6034ac 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -311,9 +311,4 @@ removeDirectFromRoom(QString roomid); void markRoomAsDirect(QString roomid, std::vector members); - -//! Returns a pair of integers representing the unread notifications in a space and how many of them -//! are loud notifications, respectively. -QPair -getChildNotificationsForSpace(const QString &spaceId); } diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp index 0d47c64d..c75f4265 100644 --- a/src/timeline/CommunitiesModel.cpp +++ b/src/timeline/CommunitiesModel.cpp @@ -208,9 +208,15 @@ CommunitiesModel::data(const QModelIndex &index, int role) const case CommunitiesModel::Roles::Id: return "tag:" + tag; case CommunitiesModel::Roles::UnreadMessages: - return (int)tagNotificationCache.at(tag).notification_count; + if (auto it = tagNotificationCache.find(tag); it != tagNotificationCache.end()) + return (int)it->second.notification_count; + else + return 0; case CommunitiesModel::Roles::HasLoudNotification: - return (int)tagNotificationCache.at(tag).highlight_count > 0; + if (auto it = tagNotificationCache.find(tag); it != tagNotificationCache.end()) + return it->second.highlight_count > 0; + else + return 0; } } return QVariant(); @@ -265,6 +271,21 @@ CommunitiesModel::initializeSidebar() tags_.clear(); spaceOrder_.tree.clear(); spaces_.clear(); + tagNotificationCache.clear(); + globalUnreads.notification_count = {}; + dmUnreads.notification_count = {}; + + auto e = cache::client()->getAccountData(mtx::events::EventType::Direct); + if (e) { + if (auto event = + std::get_if>( + &e.value())) { + directMessages_.clear(); + for (const auto &[userId, roomIds] : event->content.user_to_rooms) + for (const auto &roomId : roomIds) + directMessages_.push_back(roomId); + } + } std::set ts; @@ -284,6 +305,19 @@ CommunitiesModel::initializeSidebar() } } } + + for (const auto &t : it->tags) { + auto tagId = QString::fromStdString(t); + auto &tNs = tagNotificationCache[tagId]; + tNs.notification_count += it->notification_count; + tNs.highlight_count += it->highlight_count; + } + + auto &e = roomNotificationCache[it.key()]; + e.highlight_count = it->highlight_count; + e.notification_count = it->notification_count; + globalUnreads.notification_count += it->notification_count; + globalUnreads.highlight_count += it->highlight_count; } // NOTE(Nico): We build a forrest from the Directed Cyclic(!) Graph of spaces. To do that we @@ -319,6 +353,14 @@ CommunitiesModel::initializeSidebar() spaceOrder_.restoreCollapsed(); + for (auto &space : spaceOrder_.tree) { + for (const auto &c : cache::client()->getChildRoomIds(space.id.toStdString())) { + const auto &counts = roomNotificationCache[QString::fromStdString(c)]; + space.notificationCounts.highlight_count += counts.highlight_count; + space.notificationCounts.notification_count += counts.notification_count; + } + } + endResetModel(); emit tagsChanged(); @@ -413,16 +455,21 @@ CommunitiesModel::sync(const mtx::responses::Sync &sync_) tagsUpdated = true; } - auto roomId = QString::fromStdString(roomid); - auto oldUnreads = roomNotificationCache[roomId]; - int notificationCDiff = -static_cast(oldUnreads.highlight_count) + - static_cast(room.unread_notifications.highlight_count); - int highlightCDiff = -static_cast(oldUnreads.highlight_count) + - static_cast(room.unread_notifications.highlight_count); + auto roomId = QString::fromStdString(roomid); + auto &oldUnreads = roomNotificationCache[roomId]; + auto notificationCDiff = -static_cast(oldUnreads.notification_count) + + static_cast(room.unread_notifications.notification_count); + auto highlightCDiff = -static_cast(oldUnreads.highlight_count) + + static_cast(room.unread_notifications.highlight_count); + + auto applyDiff = [notificationCDiff, + highlightCDiff](mtx::responses::UnreadNotifications &n) { + n.highlight_count = static_cast(n.highlight_count) + highlightCDiff; + n.notification_count = static_cast(n.notification_count) + notificationCDiff; + }; if (highlightCDiff || notificationCDiff) { // bool hidden = hiddenTagIds_.contains(roomId); - globalUnreads.notification_count += notificationCDiff; - globalUnreads.highlight_count += highlightCDiff; + applyDiff(globalUnreads); emit dataChanged(index(0), index(0), { @@ -431,8 +478,7 @@ CommunitiesModel::sync(const mtx::responses::Sync &sync_) }); if (std::find(begin(directMessages_), end(directMessages_), roomid) != end(directMessages_)) { - dmUnreads.notification_count += notificationCDiff; - dmUnreads.highlight_count += highlightCDiff; + applyDiff(dmUnreads); emit dataChanged(index(1), index(1), { @@ -446,11 +492,8 @@ CommunitiesModel::sync(const mtx::responses::Sync &sync_) for (const auto &t : tags) { auto tagId = QString::fromStdString(t); - auto &tNs = tagNotificationCache[tagId]; - tNs.notification_count += notificationCDiff; - tNs.highlight_count += highlightCDiff; + applyDiff(tagNotificationCache[tagId]); int idx = tags_.indexOf(tagId) + 2 + spaceOrder_.size(); - ; emit dataChanged(index(idx), index(idx), { @@ -463,8 +506,10 @@ CommunitiesModel::sync(const mtx::responses::Sync &sync_) auto spaceId = QString::fromStdString(s); for (int i = 0; i < spaceOrder_.size(); i++) { - spaceOrder_.tree[i].notificationCounts.notification_count += notificationCDiff; - spaceOrder_.tree[i].notificationCounts.highlight_count += highlightCDiff; + if (spaceOrder_.tree[i].id != spaceId) + continue; + + applyDiff(spaceOrder_.tree[i].notificationCounts); int idx = i; do { @@ -474,10 +519,13 @@ CommunitiesModel::sync(const mtx::responses::Sync &sync_) UnreadMessages, HasLoudNotification, }); + idx = spaceOrder_.parent(idx); } while (idx != -1); } } } + + roomNotificationCache[roomId] = room.unread_notifications; } for (const auto &[roomid, room] : sync_.rooms.leave) { (void)room; diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index 3b46c053..1869d2e0 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -642,15 +642,18 @@ RoomlistModel::clear() } void -RoomlistModel::joinPreview(QString roomid, QString parentSpace) +RoomlistModel::joinPreview(QString roomid) { if (previewedRooms.contains(roomid)) { - auto child = cache::client()->getStateEvent( - parentSpace.toStdString(), roomid.toStdString()); - ChatPage::instance()->joinRoomVia( - roomid.toStdString(), - (child && child->content.via) ? child->content.via.value() : std::vector{}, - false); + std::vector vias; + auto parents = cache::client()->getParentRoomIds(roomid.toStdString()); + for (const auto &p : parents) { + auto child = cache::client()->getStateEvent( + p, roomid.toStdString()); + if (child && child->content.via) + vias.insert(vias.end(), child->content.via->begin(), child->content.via->end()); + } + ChatPage::instance()->joinRoomVia(roomid.toStdString(), vias, false); } } void diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h index cf2b45d8..61bf2e7c 100644 --- a/src/timeline/RoomlistModel.h +++ b/src/timeline/RoomlistModel.h @@ -105,7 +105,7 @@ public slots: return -1; } - void joinPreview(QString roomid, QString parentSpace); + void joinPreview(QString roomid); void acceptInvite(QString roomid); void declineInvite(QString roomid); void leave(QString roomid, QString reason = ""); @@ -169,11 +169,7 @@ public slots: { return mapFromSource(roomlistmodel->index(roomlistmodel->roomidToIndex(roomid))).row(); } - void joinPreview(QString roomid) - { - roomlistmodel->joinPreview(roomid, - filterType == FilterBy::Space ? filterStr : QLatin1String("")); - } + void joinPreview(QString roomid) { roomlistmodel->joinPreview(roomid); } void acceptInvite(QString roomid) { roomlistmodel->acceptInvite(roomid); } void declineInvite(QString roomid) { roomlistmodel->declineInvite(roomid); } void leave(QString roomid, QString reason = "") { roomlistmodel->leave(roomid, reason); }