From 8767ea181db6fb2ae9c51a316814c3b4640e08f3 Mon Sep 17 00:00:00 2001 From: Konstantinos Sideris Date: Thu, 13 Sep 2018 19:15:58 +0300 Subject: [PATCH] Mark unread rooms as such in the room list fixes #313 --- src/Cache.cpp | 55 ++++++++++++++++++++++++++++++++--- src/Cache.h | 9 +++++- src/ChatPage.cpp | 5 ++++ src/RoomInfoListItem.cpp | 22 +++++++++++--- src/RoomInfoListItem.h | 10 ++++++- src/RoomList.cpp | 13 +++++++++ src/RoomList.h | 1 + src/Utils.cpp | 1 + src/Utils.h | 1 + src/timeline/TimelineItem.cpp | 22 ++++++++++---- src/timeline/TimelineItem.h | 6 ++-- 11 files changed, 127 insertions(+), 18 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 4965167..ba32cae 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -825,8 +825,10 @@ Cache::updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Recei } void -Cache::notifyForReadReceipts(lmdb::txn &txn, const std::string &room_id) +Cache::notifyForReadReceipts(const std::string &room_id) { + auto txn = lmdb::txn::begin(env_); + QSettings settings; auto local_user = settings.value("auth/user_id").toString(); @@ -839,6 +841,47 @@ Cache::notifyForReadReceipts(lmdb::txn &txn, const std::string &room_id) if (!matches.empty()) emit newReadReceipts(QString::fromStdString(room_id), matches); + + txn.commit(); +} + +void +Cache::calculateRoomReadStatus() +{ + const auto joined_rooms = joinedRooms(); + + std::map readStatus; + + for (const auto &room : joined_rooms) + readStatus.emplace(QString::fromStdString(room), calculateRoomReadStatus(room)); + + emit roomReadStatus(readStatus); +} + +bool +Cache::calculateRoomReadStatus(const std::string &room_id) +{ + auto txn = lmdb::txn::begin(env_); + + // Get last event id on the room. + const auto last_event_id = getLastMessageInfo(txn, room_id).event_id; + const auto localUser = utils::localUser().toStdString(); + + txn.commit(); + + // Retrieve all read receipts for that event. + const auto receipts = readReceipts(last_event_id, QString::fromStdString(room_id)); + + if (receipts.size() == 0) + return true; + + // Check if the local user has a read receipt for it. + for (auto it = receipts.cbegin(); it != receipts.cend(); it++) { + if (it->second == localUser) + return false; + } + + return true; } void @@ -880,11 +923,15 @@ Cache::saveState(const mtx::responses::Sync &res) txn.commit(); + std::map readStatus; + for (const auto &room : res.rooms.join) { - auto tmpTxn = lmdb::txn::begin(env_); - notifyForReadReceipts(tmpTxn, room.first); - tmpTxn.commit(); + notifyForReadReceipts(room.first); + readStatus.emplace(QString::fromStdString(room.first), + calculateRoomReadStatus(room.first)); } + + emit roomReadStatus(readStatus); } void diff --git a/src/Cache.h b/src/Cache.h index ce53105..f9a9f9c 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -89,6 +89,7 @@ from_json(const json &j, ReadReceiptKey &key) struct DescInfo { + QString event_id; QString username; QString userid; QString body; @@ -356,7 +357,7 @@ public: void removePendingReceipt(lmdb::txn &txn, const std::string &room_id, const std::string &event_id); - void notifyForReadReceipts(lmdb::txn &txn, const std::string &room_id); + void notifyForReadReceipts(const std::string &room_id); std::vector pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id); QByteArray image(const QString &url) const; @@ -376,6 +377,11 @@ public: return getRoomInfo(roomsWithStateUpdates(sync)); } + //! Calculates which the read status of a room. + //! Whether all the events in the timeline have been read. + bool calculateRoomReadStatus(const std::string &room_id); + void calculateRoomReadStatus(); + QVector searchUsers(const std::string &room_id, const std::string &query, std::uint8_t max_items = 5); @@ -444,6 +450,7 @@ public: signals: void newReadReceipts(const QString &room_id, const std::vector &event_ids); + void roomReadStatus(const std::map &status); private: //! Save an invited room. diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 49e37b9..6640da0 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -677,6 +677,9 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token) view_manager_, &TimelineViewManager::updateReadReceipts); + connect( + cache::client(), &Cache::roomReadStatus, room_list_, &RoomList::updateReadStatus); + const bool isInitialized = cache::client()->isInitialized(); const bool isValid = cache::client()->isFormatValid(); @@ -794,6 +797,8 @@ ChatPage::loadStateFromCache() emit initializeEmptyViews(cache::client()->roomMessages()); emit initializeRoomList(cache::client()->roomInfo()); + cache::client()->calculateRoomReadStatus(); + } catch (const mtx::crypto::olm_exception &e) { nhlog::crypto()->critical("failed to restore olm account: {}", e.what()); emit dropToLoginPageCb( diff --git a/src/RoomInfoListItem.cpp b/src/RoomInfoListItem.cpp index 4edd7b8..a0d35b0 100644 --- a/src/RoomInfoListItem.cpp +++ b/src/RoomInfoListItem.cpp @@ -31,9 +31,11 @@ constexpr int MaxUnreadCountDisplayed = 99; -constexpr int Padding = 9; -constexpr int IconSize = 44; -constexpr int MaxHeight = IconSize + 2 * Padding; +constexpr int Padding = 9; +constexpr int UnreadLineWidth = 4; +constexpr int UnreadLineOffset = 4; +constexpr int IconSize = 44; +constexpr int MaxHeight = IconSize + 2 * Padding; constexpr int InviteBtnX = IconSize + 2 * Padding; constexpr int InviteBtnY = IconSize / 2 + Padding + Padding / 3; @@ -89,6 +91,8 @@ RoomInfoListItem::RoomInfoListItem(QString room_id, RoomInfo info, QWidget *pare { init(parent); + QString emptyEventId; + // HACK // We use fake message info with an old date to pin // the invite events to the top. @@ -96,7 +100,8 @@ RoomInfoListItem::RoomInfoListItem(QString room_id, RoomInfo info, QWidget *pare // State events in invited rooms don't contain timestamp info, // so we can't use them for sorting. if (roomType_ == RoomType::Invited) - lastMsgInfo_ = {"-", "-", "-", "-", QDateTime::currentDateTime().addYears(10)}; + lastMsgInfo_ = { + emptyEventId, "-", "-", "-", "-", QDateTime::currentDateTime().addYears(10)}; } void @@ -305,6 +310,15 @@ RoomInfoListItem::paintEvent(QPaintEvent *event) p.setBrush(Qt::NoBrush); p.drawText(r.translated(0, -0.5), Qt::AlignCenter, countTxt); } + + if (!isPressed_ && hasUnreadMessages_) { + QPen pen; + pen.setWidth(UnreadLineWidth); + pen.setColor(highlightedBackgroundColor_); + + p.setPen(pen); + p.drawLine(0, UnreadLineOffset, 0, height() - UnreadLineOffset); + } } void diff --git a/src/RoomInfoListItem.h b/src/RoomInfoListItem.h index 95db1d7..f08e715 100644 --- a/src/RoomInfoListItem.h +++ b/src/RoomInfoListItem.h @@ -121,6 +121,13 @@ public: } bool isInvite() { return roomType_ == RoomType::Invited; } + void setReadState(bool hasUnreadMessages) + { + if (hasUnreadMessages_ != hasUnreadMessages) { + hasUnreadMessages_ = hasUnreadMessages; + update(); + } + } signals: void clicked(const QString &room_id); @@ -164,7 +171,8 @@ private: Menu *menu_; QAction *leaveRoom_; - bool isPressed_ = false; + bool isPressed_ = false; + bool hasUnreadMessages_ = true; int unreadMsgCount_ = 0; diff --git a/src/RoomList.cpp b/src/RoomList.cpp index 8c13a7a..a262dc2 100644 --- a/src/RoomList.cpp +++ b/src/RoomList.cpp @@ -455,3 +455,16 @@ RoomList::firstRoom() const return std::pair>(firstRoom->first, firstRoom->second); } + +void +RoomList::updateReadStatus(const std::map &status) +{ + for (const auto &room : status) { + if (roomExists(room.first)) { + auto item = rooms_.at(room.first); + + if (item) + item->setReadState(room.second); + } + } +} diff --git a/src/RoomList.h b/src/RoomList.h index 9370767..dc90d61 100644 --- a/src/RoomList.h +++ b/src/RoomList.h @@ -72,6 +72,7 @@ public slots: void updateUnreadMessageCount(const QString &roomid, int count); void updateRoomDescription(const QString &roomid, const DescInfo &info); void closeJoinRoomDialog(bool isJoining, QString roomAlias); + void updateReadStatus(const std::map &status); protected: void paintEvent(QPaintEvent *event) override; diff --git a/src/Utils.cpp b/src/Utils.cpp index 2bad72b..82959b4 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -126,6 +126,7 @@ utils::getMessageDescription(const TimelineEvent &event, info.userid = sender; info.body = QString(" %1").arg(messageDescription()); info.timestamp = utils::descriptiveTime(ts); + info.event_id = QString::fromStdString(msg.event_id); info.datetime = ts; return info; diff --git a/src/Utils.h b/src/Utils.h index dc68884..1d97600 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -109,6 +109,7 @@ createDescriptionInfo(const Event &event, const QString &localUser, const QStrin bool isEmote = std::is_same::value; return DescInfo{ + QString::fromStdString(msg.event_id), isEmote ? "" : (sender == localUser ? "You" : username), sender, (isText || isEmote) diff --git a/src/timeline/TimelineItem.cpp b/src/timeline/TimelineItem.cpp index c2bd98d..1859848 100644 --- a/src/timeline/TimelineItem.cpp +++ b/src/timeline/TimelineItem.cpp @@ -317,16 +317,23 @@ TimelineItem::TimelineItem(mtx::events::MessageType ty, if (formatted_body == body.trimmed().toHtmlEscaped()) formatted_body = body.toHtmlEscaped(); + QString emptyEventId; + if (ty == mtx::events::MessageType::Emote) { formatted_body = QString("%1").arg(formatted_body); - descriptionMsg_ = {"", + descriptionMsg_ = {emptyEventId, + "", userid, QString("* %1 %2").arg(displayName).arg(body), utils::descriptiveTime(timestamp), timestamp}; } else { - descriptionMsg_ = { - "You: ", userid, body, utils::descriptiveTime(timestamp), timestamp}; + descriptionMsg_ = {emptyEventId, + "You: ", + userid, + body, + utils::descriptiveTime(timestamp), + timestamp}; } formatted_body = utils::linkifyMessage(formatted_body); @@ -496,7 +503,8 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent auto displayName = Cache::displayName(room_id_, sender); formatted_body = QString("%1").arg(formatted_body); - descriptionMsg_ = {"", + descriptionMsg_ = {event_id_, + "", sender, QString("* %1 %2").arg(displayName).arg(body), utils::descriptiveTime(timestamp), @@ -592,7 +601,8 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent auto displayName = Cache::displayName(room_id_, sender); QSettings settings; - descriptionMsg_ = {sender == settings.value("auth/user_id") ? "You" : displayName, + descriptionMsg_ = {event_id_, + sender == settings.value("auth/user_id") ? "You" : displayName, sender, QString(": %1").arg(body), utils::descriptiveTime(timestamp), diff --git a/src/timeline/TimelineItem.h b/src/timeline/TimelineItem.h index 32d586f..b98dd14 100644 --- a/src/timeline/TimelineItem.h +++ b/src/timeline/TimelineItem.h @@ -319,7 +319,8 @@ TimelineItem::setupLocalWidgetLayout(Widget *widget, const QString &userid, bool auto displayName = Cache::displayName(room_id_, userid); auto timestamp = QDateTime::currentDateTime(); - descriptionMsg_ = {"You", + descriptionMsg_ = {"", // No event_id up until this point. + "You", userid, QString(" %1").arg(utils::messageDescription()), utils::descriptiveTime(timestamp), @@ -358,7 +359,8 @@ TimelineItem::setupWidgetLayout(Widget *widget, const Event &event, bool withSen auto displayName = Cache::displayName(room_id_, sender); QSettings settings; - descriptionMsg_ = {sender == settings.value("auth/user_id") ? "You" : displayName, + descriptionMsg_ = {event_id_, + sender == settings.value("auth/user_id") ? "You" : displayName, sender, QString(" %1").arg(utils::messageDescription()), utils::descriptiveTime(timestamp),