From 2df2046d1dc5d3e4133f89f634efa6a1726a2f30 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 12 Apr 2022 21:49:21 -0400 Subject: [PATCH] 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_;