Make roomlist look nice

This commit is contained in:
Nicolas Werner 2021-05-21 21:19:03 +02:00
parent 10fd2752f9
commit cd67046f60
No known key found for this signature in database
GPG Key ID: C8D75E610773F2D9
10 changed files with 265 additions and 45 deletions

View File

@ -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
}
},
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 {
//id: userInfoGrid
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
}
}

View File

@ -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
{

View File

@ -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);

View File

@ -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;
}

View File

@ -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 {};
}
@ -64,10 +81,38 @@ 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(),

View File

@ -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;
};

View File

@ -723,6 +723,20 @@ TimelineModel::fetchMore(const QModelIndex &)
events.fetchMore();
}
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)
{
@ -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))));
if (description != lastMessage_) {
lastMessage_ = description;
emit manager_->updateRoomsLastMessage(room_id_, description);
emit lastMessageChanged();
}
return;
}
}

View File

@ -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>

View File

@ -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");
}
}

View File

@ -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_;
};