mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 11:00:48 +03:00
Display unread notifications for spaces
This commit is contained in:
parent
b6bbbdeae7
commit
2df2046d1d
4 changed files with 118 additions and 18 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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_;
|
||||||
|
|
Loading…
Reference in a new issue