diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml
index 87a27517..bb8deda6 100644
--- a/resources/qml/RoomList.qml
+++ b/resources/qml/RoomList.qml
@@ -2,13 +2,15 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
-import QtQuick 2.9
+import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.3
import im.nheko 1.0
Page {
ListView {
+ id: roomlist
+
anchors.left: parent.left
anchors.right: parent.right
height: parent.height
@@ -20,26 +22,80 @@ Page {
enabled: !Settings.mobileMode
}
+ Connections {
+ onActiveTimelineChanged: {
+ roomlist.positionViewAtIndex(Rooms.roomidToIndex(TimelineManager.timeline.roomId()), ListView.Contain);
+ console.log("Test" + TimelineManager.timeline.roomId() + " " + Rooms.roomidToIndex(TimelineManager.timeline.roomId));
+ }
+ target: TimelineManager
+ }
+
delegate: Rectangle {
- color: Nheko.colors.window
- height: fontMetrics.lineSpacing * 2.5 + Nheko.paddingMedium * 2
+ id: roomItem
+
+ property color background: Nheko.colors.window
+ property color importantText: Nheko.colors.text
+ property color unimportantText: Nheko.colors.buttonText
+ property color bubbleBackground: Nheko.colors.highlight
+ property color bubbleText: Nheko.colors.highlightedText
+
+ color: background
+ height: Math.ceil(fontMetrics.lineSpacing * 2.3 + Nheko.paddingMedium * 2)
width: ListView.view.width
+ state: "normal"
+ states: [
+ State {
+ name: "highlight"
+ when: hovered.hovered
+
+ PropertyChanges {
+ target: roomItem
+ background: Nheko.colors.dark
+ importantText: Nheko.colors.brightText
+ unimportantText: Nheko.colors.brightText
+ bubbleBackground: Nheko.colors.highlight
+ bubbleText: Nheko.colors.highlightedText
+ }
- RowLayout {
- //id: userInfoGrid
+ },
+ State {
+ name: "selected"
+ when: TimelineManager.timeline && model.roomId == TimelineManager.timeline.roomId()
+
+ PropertyChanges {
+ target: roomItem
+ background: Nheko.colors.highlight
+ importantText: Nheko.colors.highlightedText
+ unimportantText: Nheko.colors.highlightedText
+ bubbleBackground: Nheko.colors.highlightedText
+ bubbleText: Nheko.colors.highlight
+ }
+
+ }
+ ]
+
+ HoverHandler {
+ id: hovered
+ }
+ TapHandler {
+ onSingleTapped: TimelineManager.setHistoryView(model.roomId)
+ }
+
+ RowLayout {
spacing: Nheko.paddingMedium
anchors.fill: parent
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
Layout.alignment: Qt.AlignVCenter
- Layout.preferredWidth: fontMetrics.lineSpacing * 2.5
- Layout.preferredHeight: fontMetrics.lineSpacing * 2.5
+ height: Math.ceil(fontMetrics.lineSpacing * 2.3)
+ width: Math.ceil(fontMetrics.lineSpacing * 2.3)
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: model.roomName
}
@@ -52,7 +108,7 @@ Page {
Layout.minimumWidth: 100
width: parent.width - avatar.width
Layout.preferredWidth: parent.width - avatar.width
- spacing: 0
+ spacing: Nheko.paddingSmall
RowLayout {
Layout.fillWidth: true
@@ -60,9 +116,9 @@ Page {
ElidedLabel {
Layout.alignment: Qt.AlignBottom
- color: Nheko.colors.text
+ color: roomItem.importantText
elideWidth: textContent.width - timestamp.width - Nheko.paddingMedium
- fullText: model.roomName + ": " + model.notificationCount
+ fullText: model.roomName
}
Item {
@@ -74,8 +130,8 @@ Page {
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
font.pixelSize: fontMetrics.font.pixelSize * 0.9
- color: Nheko.colors.buttonText
- text: "14:32"
+ color: roomItem.unimportantText
+ text: model.timestamp
}
}
@@ -85,10 +141,10 @@ Page {
spacing: 0
ElidedLabel {
- color: Nheko.colors.buttonText
+ color: roomItem.unimportantText
font.weight: Font.Thin
font.pixelSize: fontMetrics.font.pixelSize * 0.9
- elideWidth: textContent.width - notificationBubble.width
+ elideWidth: textContent.width - (notificationBubble.visible ? notificationBubble.width : 0) - Nheko.paddingSmall
fullText: model.lastMessage
}
@@ -99,19 +155,24 @@ Page {
Rectangle {
id: notificationBubble
+ visible: model.notificationCount > 0
Layout.alignment: Qt.AlignRight
- height: fontMetrics.font.pixelSize * 1.3
+ height: fontMetrics.averageCharacterWidth * 3
width: height
radius: height / 2
- color: Nheko.colors.highlight
+ color: model.hasLoudNotification ? Nheko.theme.red : roomItem.bubbleBackground
Label {
- anchors.fill: parent
+ anchors.centerIn: parent
+ width: parent.width * 0.8
+ height: parent.height * 0.8
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
fontSizeMode: Text.Fit
- color: Nheko.colors.highlightedText
- text: model.notificationCount
+ font.bold: true
+ font.pixelSize: fontMetrics.font.pixelSize * 0.8
+ color: model.hasLoudNotification ? "white" : roomItem.bubbleText
+ text: model.notificationCount > 99 ? "99+" : model.notificationCount
}
}
diff --git a/src/CacheStructs.h b/src/CacheStructs.h
index c449f013..f7d6f0e2 100644
--- a/src/CacheStructs.h
+++ b/src/CacheStructs.h
@@ -50,6 +50,19 @@ struct DescInfo
QDateTime datetime;
};
+inline bool
+operator==(const DescInfo &a, const DescInfo &b)
+{
+ return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) ==
+ std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime);
+}
+inline bool
+operator!=(const DescInfo &a, const DescInfo &b)
+{
+ return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) !=
+ std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime);
+}
+
//! UI info associated with a room.
struct RoomInfo
{
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index c5199ff1..58b76174 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -233,11 +233,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
room_list_,
&RoomList::updateRoomDescription);
- connect(room_list_,
- SIGNAL(totalUnreadMessageCountUpdated(int)),
- this,
- SIGNAL(unreadMessages(int)));
-
connect(
this, &ChatPage::updateGroupsInfo, communitiesList_, &CommunitiesList::setCommunities);
diff --git a/src/RoomList.cpp b/src/RoomList.cpp
index 8a807e71..5c41a7a1 100644
--- a/src/RoomList.cpp
+++ b/src/RoomList.cpp
@@ -305,8 +305,6 @@ void
RoomList::updateRoomAvatar(const QString &roomid, const QString &img)
{
if (!roomExists(roomid)) {
- nhlog::ui()->warn("avatar update on non-existent room_id: {}",
- roomid.toStdString());
return;
}
@@ -320,9 +318,6 @@ void
RoomList::updateRoomDescription(const QString &roomid, const DescInfo &info)
{
if (!roomExists(roomid)) {
- nhlog::ui()->warn("description update on non-existent room_id: {}, {}",
- roomid.toStdString(),
- info.body.toStdString());
return;
}
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index 6a1fc3c5..5fc4dc65 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -4,6 +4,7 @@
#include "RoomlistModel.h"
+#include "Cache_p.h"
#include "ChatPage.h"
#include "MatrixClient.h"
#include "MxcImageProvider.h"
@@ -26,6 +27,11 @@ RoomlistModel::RoomlistModel(TimelineViewManager *parent)
}
}
});
+
+ connect(this,
+ &RoomlistModel::totalUnreadMessageCountUpdated,
+ ChatPage::instance(),
+ &ChatPage::unreadMessages);
}
QHash<int, QByteArray>
@@ -34,8 +40,11 @@ RoomlistModel::roleNames() const
return {
{AvatarUrl, "avatarUrl"},
{RoomName, "roomName"},
+ {RoomId, "roomId"},
{LastMessage, "lastMessage"},
+ {Timestamp, "timestamp"},
{HasUnreadMessages, "hasUnreadMessages"},
+ {HasLoudNotification, "hasLoudNotification"},
{NotificationCount, "notificationCount"},
};
}
@@ -44,18 +53,26 @@ QVariant
RoomlistModel::data(const QModelIndex &index, int role) const
{
if (index.row() >= 0 && static_cast<size_t>(index.row()) < roomids.size()) {
- auto room = models.value(roomids.at(index.row()));
+ auto roomid = roomids.at(index.row());
+ auto room = models.value(roomid);
switch (role) {
case Roles::AvatarUrl:
return room->roomAvatarUrl();
case Roles::RoomName:
return room->roomName();
+ case Roles::RoomId:
+ return room->roomId();
case Roles::LastMessage:
- return QString("Nico: Hahaha, this is funny!");
+ return room->lastMessage().body;
+ case Roles::Timestamp:
+ return room->lastMessage().descriptiveTime;
case Roles::HasUnreadMessages:
- return true;
+ return this->roomReadStatus.count(roomid) &&
+ this->roomReadStatus.at(roomid);
+ case Roles::HasLoudNotification:
+ return room->hasMentions();
case Roles::NotificationCount:
- return 5;
+ return room->notificationCount();
default:
return {};
}
@@ -65,9 +82,37 @@ RoomlistModel::data(const QModelIndex &index, int role) const
}
void
+RoomlistModel::updateReadStatus(const std::map<QString, bool> roomReadStatus_)
+{
+ std::vector<int> roomsToUpdate;
+ roomsToUpdate.resize(roomReadStatus_.size());
+ for (const auto &[roomid, roomUnread] : roomReadStatus_) {
+ if (roomUnread != roomReadStatus[roomid]) {
+ roomsToUpdate.push_back(this->roomidToIndex(roomid));
+ }
+ }
+
+ this->roomReadStatus = roomReadStatus_;
+
+ for (auto idx : roomsToUpdate) {
+ emit dataChanged(index(idx),
+ index(idx),
+ {
+ Roles::HasUnreadMessages,
+ });
+ }
+};
+void
RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification)
{
if (!models.contains(room_id)) {
+ // ensure we get read status updates and are only connected once
+ connect(cache::client(),
+ &Cache::roomReadStatus,
+ this,
+ &RoomlistModel::updateReadStatus,
+ Qt::UniqueConnection);
+
QSharedPointer<TimelineModel> newRoom(new TimelineModel(manager, room_id));
newRoom->setDecryptDescription(
ChatPage::instance()->userSettings()->decryptSidebar());
@@ -80,6 +125,56 @@ RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification)
&TimelineModel::forwardToRoom,
manager,
&TimelineViewManager::forwardMessageToRoom);
+ connect(
+ newRoom.data(), &TimelineModel::lastMessageChanged, this, [room_id, this]() {
+ auto idx = this->roomidToIndex(room_id);
+ emit dataChanged(index(idx),
+ index(idx),
+ {
+ Roles::HasLoudNotification,
+ Roles::LastMessage,
+ Roles::Timestamp,
+ Roles::NotificationCount,
+ });
+ });
+ connect(
+ newRoom.data(), &TimelineModel::roomAvatarUrlChanged, this, [room_id, this]() {
+ auto idx = this->roomidToIndex(room_id);
+ emit dataChanged(index(idx),
+ index(idx),
+ {
+ Roles::AvatarUrl,
+ });
+ });
+ connect(newRoom.data(), &TimelineModel::roomNameChanged, this, [room_id, this]() {
+ auto idx = this->roomidToIndex(room_id);
+ emit dataChanged(index(idx),
+ index(idx),
+ {
+ Roles::RoomName,
+ });
+ });
+ connect(
+ newRoom.data(), &TimelineModel::notificationsChanged, this, [room_id, this]() {
+ auto idx = this->roomidToIndex(room_id);
+ emit dataChanged(index(idx),
+ index(idx),
+ {
+ Roles::HasLoudNotification,
+ Roles::NotificationCount,
+ });
+
+ int total_unread_msgs = 0;
+
+ for (const auto &room : models) {
+ if (!room.isNull())
+ total_unread_msgs += room->notificationCount();
+ }
+
+ emit totalUnreadMessageCountUpdated(total_unread_msgs);
+ });
+
+ newRoom->updateLastMessage();
if (!suppressInsertNotification)
beginInsertRows(QModelIndex(), (int)roomids.size(), (int)roomids.size());
@@ -97,8 +192,8 @@ RoomlistModel::sync(const mtx::responses::Rooms &rooms)
// addRoom will only add the room, if it doesn't exist
addRoom(QString::fromStdString(room_id));
const auto &room_model = models.value(QString::fromStdString(room_id));
- room_model->syncState(room.state);
- room_model->addEvents(room.timeline);
+ room_model->sync(room);
+ // room_model->addEvents(room.timeline);
connect(room_model.data(),
&TimelineModel::newCallEvent,
manager->callManager(),
diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index 44fcf032..c4c9d9ba 100644
--- a/src/timeline/RoomlistModel.h
+++ b/src/timeline/RoomlistModel.h
@@ -22,8 +22,11 @@ public:
{
AvatarUrl = Qt::UserRole,
RoomName,
+ RoomId,
LastMessage,
+ Timestamp,
HasUnreadMessages,
+ HasLoudNotification,
NotificationCount,
};
@@ -47,6 +50,21 @@ public slots:
void initializeRooms(const std::vector<QString> &roomids);
void sync(const mtx::responses::Rooms &rooms);
void clear();
+ int roomidToIndex(QString roomid)
+ {
+ for (int i = 0; i < (int)roomids.size(); i++) {
+ if (roomids[i] == roomid)
+ return i;
+ }
+
+ return -1;
+ }
+
+private slots:
+ void updateReadStatus(const std::map<QString, bool> roomReadStatus_);
+
+signals:
+ void totalUnreadMessageCountUpdated(int unreadMessages);
private:
void addRoom(const QString &room_id, bool suppressInsertNotification = false);
@@ -54,5 +72,5 @@ private:
TimelineViewManager *manager = nullptr;
std::vector<QString> roomids;
QHash<QString, QSharedPointer<TimelineModel>> models;
+ std::map<QString, bool> roomReadStatus;
};
-
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 8df17457..19c3fb30 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -724,6 +724,20 @@ TimelineModel::fetchMore(const QModelIndex &)
}
void
+TimelineModel::sync(const mtx::responses::JoinedRoom &room)
+{
+ this->syncState(room.state);
+ this->addEvents(room.timeline);
+
+ if (room.unread_notifications.highlight_count != highlight_count ||
+ room.unread_notifications.notification_count != notification_count) {
+ notification_count = room.unread_notifications.notification_count;
+ highlight_count = room.unread_notifications.highlight_count;
+ emit notificationsChanged();
+ }
+}
+
+void
TimelineModel::syncState(const mtx::responses::State &s)
{
using namespace mtx::events;
@@ -866,14 +880,18 @@ TimelineModel::updateLastMessage()
if (std::visit([](const auto &e) -> bool { return isYourJoin(e); }, *event)) {
auto time = mtx::accessors::origin_server_ts(*event);
uint64_t ts = time.toMSecsSinceEpoch();
- emit manager_->updateRoomsLastMessage(
- room_id_,
+ auto description =
DescInfo{QString::fromStdString(mtx::accessors::event_id(*event)),
QString::fromStdString(http::client()->user_id().to_string()),
tr("You joined this room."),
utils::descriptiveTime(time),
ts,
- time});
+ time};
+ if (description != lastMessage_) {
+ lastMessage_ = description;
+ emit manager_->updateRoomsLastMessage(room_id_, lastMessage_);
+ emit lastMessageChanged();
+ }
return;
}
if (!std::visit([](const auto &e) -> bool { return isMessage(e); }, *event))
@@ -884,7 +902,11 @@ TimelineModel::updateLastMessage()
QString::fromStdString(http::client()->user_id().to_string()),
cache::displayName(room_id_,
QString::fromStdString(mtx::accessors::sender(*event))));
- emit manager_->updateRoomsLastMessage(room_id_, description);
+ if (description != lastMessage_) {
+ lastMessage_ = description;
+ emit manager_->updateRoomsLastMessage(room_id_, description);
+ emit lastMessageChanged();
+ }
return;
}
}
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 92fccd2d..5c1065cb 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -14,6 +14,7 @@
#include <mtxclient/http/errors.hpp>
#include "CacheCryptoStructs.h"
+#include "CacheStructs.h"
#include "EventStore.h"
#include "InputBar.h"
#include "Permissions.h"
@@ -253,12 +254,15 @@ public:
}
void updateLastMessage();
+ void sync(const mtx::responses::JoinedRoom &room);
void addEvents(const mtx::responses::Timeline &events);
void syncState(const mtx::responses::State &state);
template<class T>
void sendMessageEvent(const T &content, mtx::events::EventType eventType);
RelatedInfo relatedInfo(QString id);
+ DescInfo lastMessage() const { return lastMessage_; }
+
public slots:
void setCurrentIndex(int index);
int currentIndex() const { return idToIndex(currentId); }
@@ -309,6 +313,9 @@ public slots:
QString roomAvatarUrl() const;
QString roomId() const { return room_id_; }
+ bool hasMentions() { return highlight_count > 0; }
+ int notificationCount() { return notification_count; }
+
QString scrollTarget() const;
private slots:
@@ -328,6 +335,9 @@ signals:
void newCallEvent(const mtx::events::collections::TimelineEvents &event);
void scrollToIndex(int index);
+ void lastMessageChanged();
+ void notificationsChanged();
+
void openRoomSettingsDialog(RoomSettings *settings);
void newMessageToSend(mtx::events::collections::TimelineEvents event);
@@ -372,7 +382,11 @@ private:
QString eventIdToShow;
int showEventTimerCounter = 0;
+ DescInfo lastMessage_;
+
friend struct SendMessageVisitor;
+
+ int notification_count = 0, highlight_count = 0;
};
template<class T>
diff --git a/src/ui/Theme.cpp b/src/ui/Theme.cpp
index ca2a4ce0..b6c9579a 100644
--- a/src/ui/Theme.cpp
+++ b/src/ui/Theme.cpp
@@ -16,14 +16,15 @@ Theme::paletteFromTheme(std::string_view theme)
/*windowText*/ QColor("#333"),
/*button*/ QColor("white"),
/*light*/ QColor(0xef, 0xef, 0xef),
- /*dark*/ QColor(110, 110, 110),
+ /*dark*/ QColor(70, 77, 93),
/*mid*/ QColor(220, 220, 220),
/*text*/ QColor("#333"),
- /*bright_text*/ QColor("#333"),
+ /*bright_text*/ QColor("#f2f5f8"),
/*base*/ QColor("#fff"),
/*window*/ QColor("white"));
lightActive.setColor(QPalette::AlternateBase, QColor("#eee"));
lightActive.setColor(QPalette::Highlight, QColor("#38a3d8"));
+ lightActive.setColor(QPalette::HighlightedText, QColor("#f4f4f5"));
lightActive.setColor(QPalette::ToolTipBase, lightActive.base().color());
lightActive.setColor(QPalette::ToolTipText, lightActive.text().color());
lightActive.setColor(QPalette::Link, QColor("#0077b5"));
@@ -34,14 +35,15 @@ Theme::paletteFromTheme(std::string_view theme)
/*windowText*/ QColor("#caccd1"),
/*button*/ QColor(0xff, 0xff, 0xff),
/*light*/ QColor("#caccd1"),
- /*dark*/ QColor(110, 110, 110),
+ /*dark*/ QColor(60, 70, 77),
/*mid*/ QColor("#202228"),
/*text*/ QColor("#caccd1"),
- /*bright_text*/ QColor(0xff, 0xff, 0xff),
+ /*bright_text*/ QColor("#f4f5f8"),
/*base*/ QColor("#202228"),
/*window*/ QColor("#2d3139"));
darkActive.setColor(QPalette::AlternateBase, QColor("#2d3139"));
darkActive.setColor(QPalette::Highlight, QColor("#38a3d8"));
+ darkActive.setColor(QPalette::HighlightedText, QColor("#f4f5f8"));
darkActive.setColor(QPalette::ToolTipBase, darkActive.base().color());
darkActive.setColor(QPalette::ToolTipText, darkActive.text().color());
darkActive.setColor(QPalette::Link, QColor("#38a3d8"));
@@ -58,9 +60,12 @@ Theme::Theme(std::string_view theme)
separator_ = p.mid().color();
if (theme == "light") {
sidebarBackground_ = QColor("#233649");
+ red_ = QColor("#a82353");
} else if (theme == "dark") {
sidebarBackground_ = QColor("#2d3139");
+ red_ = QColor("#a82353");
} else {
sidebarBackground_ = p.window().color();
+ red_ = QColor("red");
}
}
diff --git a/src/ui/Theme.h b/src/ui/Theme.h
index 64bc8273..834571c0 100644
--- a/src/ui/Theme.h
+++ b/src/ui/Theme.h
@@ -66,6 +66,7 @@ class Theme : public QPalette
Q_GADGET
Q_PROPERTY(QColor sidebarBackground READ sidebarBackground CONSTANT)
Q_PROPERTY(QColor separator READ separator CONSTANT)
+ Q_PROPERTY(QColor red READ red CONSTANT)
public:
Theme() {}
explicit Theme(std::string_view theme);
@@ -73,7 +74,8 @@ public:
QColor sidebarBackground() const { return sidebarBackground_; }
QColor separator() const { return separator_; }
+ QColor red() const { return red_; }
private:
- QColor sidebarBackground_, separator_;
+ QColor sidebarBackground_, separator_, red_;
};
|