Display unread notifications for spaces

This commit is contained in:
Loren Burkholder 2022-04-12 21:49:21 -04:00
parent b6bbbdeae7
commit 2df2046d1d
4 changed files with 118 additions and 18 deletions

View file

@ -153,6 +153,42 @@ Page {
roomid: model.id roomid: model.id
displayName: model.displayName displayName: model.displayName
color: communityItem.backgroundColor 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 { ElidedLabel {
@ -169,6 +205,40 @@ Page {
Layout.fillWidth: true 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 { background: Rectangle {

View file

@ -294,9 +294,6 @@ Page {
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
Avatar { Avatar {
// In the future we could show an online indicator by setting the userid for the avatar
//userid: Nheko.currentUser.userid
id: avatar id: avatar
enabled: false enabled: false

View file

@ -9,12 +9,19 @@
#include "Cache.h" #include "Cache.h"
#include "Cache_p.h" #include "Cache_p.h"
#include "ChatPage.h"
#include "Logging.h" #include "Logging.h"
#include "UserSettingsPage.h" #include "UserSettingsPage.h"
CommunitiesModel::CommunitiesModel(QObject *parent) CommunitiesModel::CommunitiesModel(QObject *parent)
: QAbstractListModel(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<int, QByteArray> QHash<int, QByteArray>
CommunitiesModel::roleNames() const CommunitiesModel::roleNames() const
@ -28,6 +35,7 @@ CommunitiesModel::roleNames() const
{Hidden, "hidden"}, {Hidden, "hidden"},
{Depth, "depth"}, {Depth, "depth"},
{Id, "id"}, {Id, "id"},
{UnreadMessages, "unreadMessages"},
}; };
} }
@ -70,6 +78,8 @@ CommunitiesModel::data(const QModelIndex &index, int role) const
return 0; return 0;
case CommunitiesModel::Roles::Id: case CommunitiesModel::Roles::Id:
return ""; return "";
case CommunitiesModel::Roles::UnreadMessages:
return 0;
} }
} else if (index.row() == 1) { } else if (index.row() == 1) {
switch (role) { switch (role) {
@ -91,9 +101,11 @@ CommunitiesModel::data(const QModelIndex &index, int role) const
return 0; return 0;
case CommunitiesModel::Roles::Id: case CommunitiesModel::Roles::Id:
return "dm"; return "dm";
case CommunitiesModel::Roles::UnreadMessages:
return 0;
} }
} else if (index.row() - 2 < spaceOrder_.size()) { } 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) { switch (role) {
case CommunitiesModel::Roles::AvatarUrl: case CommunitiesModel::Roles::AvatarUrl:
return QString::fromStdString(spaces_.at(id).avatar_url); 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); return hiddentTagIds_.contains("space:" + id);
case CommunitiesModel::Roles::Parent: { case CommunitiesModel::Roles::Parent: {
if (auto p = spaceOrder_.parent(index.row() - 2); p >= 0) if (auto p = spaceOrder_.parent(index.row() - 2); p >= 0)
return spaceOrder_.tree[p].name; return spaceOrder_.tree[p].id;
return ""; return "";
} }
@ -118,6 +130,8 @@ CommunitiesModel::data(const QModelIndex &index, int role) const
return spaceOrder_.tree.at(index.row() - 2).depth; return spaceOrder_.tree.at(index.row() - 2).depth;
case CommunitiesModel::Roles::Id: case CommunitiesModel::Roles::Id:
return "space:" + id; return "space:" + id;
case CommunitiesModel::Roles::UnreadMessages:
return getChildNotifications(id);
} }
} else if (index.row() - 2 < tags_.size() + spaceOrder_.size()) { } else if (index.row() - 2 < tags_.size() + spaceOrder_.size()) {
auto tag = tags_.at(index.row() - 2 - 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; return 0;
case CommunitiesModel::Roles::Id: case CommunitiesModel::Roles::Id:
return "tag:" + tag; return "tag:" + tag;
case CommunitiesModel::Roles::UnreadMessages:
return 0;
} }
} }
return QVariant(); return QVariant();
@ -279,6 +295,7 @@ CommunitiesModel::initializeSidebar()
hiddentTagIds_ = UserSettings::instance()->hiddenTags(); hiddentTagIds_ = UserSettings::instance()->hiddenTags();
spaceOrder_.restoreCollapsed(); spaceOrder_.restoreCollapsed();
endResetModel(); endResetModel();
emit tagsChanged(); emit tagsChanged();
@ -298,12 +315,12 @@ CommunitiesModel::FlatTree::storeCollapsed()
for (const auto &e : tree) { for (const auto &e : tree) {
if (e.depth > depth) { if (e.depth > depth) {
current.push_back(e.name); current.push_back(e.id);
} else if (e.depth == depth) { } else if (e.depth == depth) {
current.back() = e.name; current.back() = e.id;
} else { } else {
current.pop_back(); current.pop_back();
current.back() = e.name; current.back() = e.id;
} }
if (e.collapsed) if (e.collapsed)
@ -323,12 +340,12 @@ CommunitiesModel::FlatTree::restoreCollapsed()
for (auto &e : tree) { for (auto &e : tree) {
if (e.depth > depth) { if (e.depth > depth) {
current.push_back(e.name); current.push_back(e.id);
} else if (e.depth == depth) { } else if (e.depth == depth) {
current.back() = e.name; current.back() = e.id;
} else { } else {
current.pop_back(); current.pop_back();
current.back() = e.name; current.back() = e.id;
} }
if (elements.contains(current)) if (elements.contains(current))
@ -353,7 +370,6 @@ CommunitiesModel::sync(const mtx::responses::Sync &sync_)
bool tagsUpdated = false; bool tagsUpdated = false;
for (const auto &[roomid, room] : sync_.rooms.join) { for (const auto &[roomid, room] : sync_.rooms.join) {
(void)roomid;
for (const auto &e : room.account_data.events) for (const auto &e : room.account_data.events)
if (std::holds_alternative< if (std::holds_alternative<
mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) { mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) {
@ -392,7 +408,7 @@ CommunitiesModel::sync(const mtx::responses::Sync &sync_)
} }
void void
CommunitiesModel::setCurrentTagId(QString tagId) CommunitiesModel::setCurrentTagId(const QString &tagId)
{ {
if (tagId.startsWith(QLatin1String("tag:"))) { if (tagId.startsWith(QLatin1String("tag:"))) {
auto tag = tagId.mid(4); auto tag = tagId.mid(4);
@ -406,7 +422,7 @@ CommunitiesModel::setCurrentTagId(QString tagId)
} else if (tagId.startsWith(QLatin1String("space:"))) { } else if (tagId.startsWith(QLatin1String("space:"))) {
auto tag = tagId.mid(6); auto tag = tagId.mid(6);
for (const auto &t : spaceOrder_.tree) { for (const auto &t : spaceOrder_.tree) {
if (t.name == tag) { if (t.id == tag) {
this->currentTagId_ = tagId; this->currentTagId_ = tagId;
emit currentTagIdChanged(currentTagId_); emit currentTagIdChanged(currentTagId_);
return; return;
@ -449,6 +465,20 @@ CommunitiesModel::toggleTagId(QString tagId)
emit hiddenTagsChanged(); 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) FilteredCommunitiesModel::FilteredCommunitiesModel(CommunitiesModel *model, QObject *parent)
: QSortFilterProxyModel(parent) : QSortFilterProxyModel(parent)
{ {

View file

@ -48,13 +48,14 @@ public:
Parent, Parent,
Depth, Depth,
Id, Id,
UnreadMessages,
}; };
struct FlatTree struct FlatTree
{ {
struct Elem struct Elem
{ {
QString name; QString id;
int depth = 0; int depth = 0;
bool collapsed = false; bool collapsed = false;
}; };
@ -65,7 +66,7 @@ public:
int indexOf(const QString &s) const int indexOf(const QString &s) const
{ {
for (int i = 0; i < size(); i++) for (int i = 0; i < size(); i++)
if (tree[i].name == s) if (tree[i].id == s)
return i; return i;
return -1; return -1;
} }
@ -121,7 +122,7 @@ public slots:
void sync(const mtx::responses::Sync &sync_); void sync(const mtx::responses::Sync &sync_);
void clear(); void clear();
QString currentTagId() const { return currentTagId_; } QString currentTagId() const { return currentTagId_; }
void setCurrentTagId(QString tagId); void setCurrentTagId(const QString &tagId);
void resetCurrentTagId() void resetCurrentTagId()
{ {
currentTagId_.clear(); currentTagId_.clear();
@ -147,6 +148,8 @@ signals:
void containsSubspacesChanged(); void containsSubspacesChanged();
private: private:
int getChildNotifications(const QString &space_id) const;
QStringList tags_; QStringList tags_;
QString currentTagId_; QString currentTagId_;
QStringList hiddentTagIds_; QStringList hiddentTagIds_;