mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 19:08:58 +03:00
Mark own read messages with a double checkmark (#377)
This commit is contained in:
parent
40facd116e
commit
e4dedbcaba
12 changed files with 248 additions and 1 deletions
BIN
resources/icons/ui/double-tick-indicator.png
Normal file
BIN
resources/icons/ui/double-tick-indicator.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 577 B |
BIN
resources/icons/ui/double-tick-indicator@2x.png
Normal file
BIN
resources/icons/ui/double-tick-indicator@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 700 B |
|
@ -1,5 +1,7 @@
|
||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="/icons">
|
<qresource prefix="/icons">
|
||||||
|
<file>icons/ui/double-tick-indicator.png</file>
|
||||||
|
<file>icons/ui/double-tick-indicator@2x.png</file>
|
||||||
<file>icons/ui/lock.png</file>
|
<file>icons/ui/lock.png</file>
|
||||||
<file>icons/ui/lock@2x.png</file>
|
<file>icons/ui/lock@2x.png</file>
|
||||||
<file>icons/ui/clock.png</file>
|
<file>icons/ui/clock.png</file>
|
||||||
|
|
111
src/Cache.cpp
111
src/Cache.cpp
|
@ -649,6 +649,70 @@ Cache::setCurrentFormat()
|
||||||
txn.commit();
|
txn.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<QString>
|
||||||
|
Cache::pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id)
|
||||||
|
{
|
||||||
|
auto db = getPendingReceiptsDb(txn);
|
||||||
|
|
||||||
|
std::string key, unused;
|
||||||
|
std::vector<QString> pending;
|
||||||
|
|
||||||
|
auto cursor = lmdb::cursor::open(txn, db);
|
||||||
|
while (cursor.get(key, unused, MDB_NEXT)) {
|
||||||
|
ReadReceiptKey receipt;
|
||||||
|
try {
|
||||||
|
receipt = json::parse(key);
|
||||||
|
} catch (const nlohmann::json::exception &e) {
|
||||||
|
nhlog::db()->warn("pendingReceiptsEvents: {}", e.what());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receipt.room_id == room_id)
|
||||||
|
pending.emplace_back(QString::fromStdString(receipt.event_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.close();
|
||||||
|
|
||||||
|
return pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Cache::removePendingReceipt(lmdb::txn &txn, const std::string &room_id, const std::string &event_id)
|
||||||
|
{
|
||||||
|
auto db = getPendingReceiptsDb(txn);
|
||||||
|
|
||||||
|
ReadReceiptKey receipt_key{event_id, room_id};
|
||||||
|
auto key = json(receipt_key).dump();
|
||||||
|
|
||||||
|
try {
|
||||||
|
lmdb::dbi_del(txn, db, lmdb::val(key.data(), key.size()), nullptr);
|
||||||
|
} catch (const lmdb::error &e) {
|
||||||
|
nhlog::db()->critical("removePendingReceipt: {}", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Cache::addPendingReceipt(const QString &room_id, const QString &event_id)
|
||||||
|
{
|
||||||
|
auto txn = lmdb::txn::begin(env_);
|
||||||
|
auto db = getPendingReceiptsDb(txn);
|
||||||
|
|
||||||
|
ReadReceiptKey receipt_key{event_id.toStdString(), room_id.toStdString()};
|
||||||
|
auto key = json(receipt_key).dump();
|
||||||
|
std::string empty;
|
||||||
|
|
||||||
|
try {
|
||||||
|
lmdb::dbi_put(txn,
|
||||||
|
db,
|
||||||
|
lmdb::val(key.data(), key.size()),
|
||||||
|
lmdb::val(empty.data(), empty.size()));
|
||||||
|
} catch (const lmdb::error &e) {
|
||||||
|
nhlog::db()->critical("addPendingReceipt: {}", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
|
|
||||||
CachedReceipts
|
CachedReceipts
|
||||||
Cache::readReceipts(const QString &event_id, const QString &room_id)
|
Cache::readReceipts(const QString &event_id, const QString &room_id)
|
||||||
{
|
{
|
||||||
|
@ -684,6 +748,30 @@ Cache::readReceipts(const QString &event_id, const QString &room_id)
|
||||||
return receipts;
|
return receipts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<QString>
|
||||||
|
Cache::filterReadEvents(const QString &room_id,
|
||||||
|
const std::vector<QString> &event_ids,
|
||||||
|
const std::string &excluded_user)
|
||||||
|
{
|
||||||
|
std::vector<QString> read_events;
|
||||||
|
|
||||||
|
for (const auto &event : event_ids) {
|
||||||
|
auto receipts = readReceipts(event, room_id);
|
||||||
|
|
||||||
|
if (receipts.size() == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (receipts.size() == 1) {
|
||||||
|
if (receipts.begin()->second == excluded_user)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
read_events.emplace_back(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return read_events;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Cache::updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts)
|
Cache::updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts)
|
||||||
{
|
{
|
||||||
|
@ -733,6 +821,23 @@ Cache::updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Recei
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Cache::notifyForReadReceipts(lmdb::txn &txn, const std::string &room_id)
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
auto local_user = settings.value("auth/user_id").toString();
|
||||||
|
|
||||||
|
auto matches = filterReadEvents(QString::fromStdString(room_id),
|
||||||
|
pendingReceiptsEvents(txn, room_id),
|
||||||
|
local_user.toStdString());
|
||||||
|
|
||||||
|
for (const auto &m : matches)
|
||||||
|
removePendingReceipt(txn, room_id, m.toStdString());
|
||||||
|
|
||||||
|
if (!matches.empty())
|
||||||
|
emit newReadReceipts(QString::fromStdString(room_id), matches);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Cache::saveState(const mtx::responses::Sync &res)
|
Cache::saveState(const mtx::responses::Sync &res)
|
||||||
{
|
{
|
||||||
|
@ -771,6 +876,12 @@ Cache::saveState(const mtx::responses::Sync &res)
|
||||||
removeLeftRooms(txn, res.rooms.leave);
|
removeLeftRooms(txn, res.rooms.leave);
|
||||||
|
|
||||||
txn.commit();
|
txn.commit();
|
||||||
|
|
||||||
|
for (const auto &room : res.rooms.join) {
|
||||||
|
auto txn = lmdb::txn::begin(env_);
|
||||||
|
notifyForReadReceipts(txn, room.first);
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
20
src/Cache.h
20
src/Cache.h
|
@ -347,6 +347,18 @@ public:
|
||||||
using UserReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
|
using UserReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
|
||||||
UserReceipts readReceipts(const QString &event_id, const QString &room_id);
|
UserReceipts readReceipts(const QString &event_id, const QString &room_id);
|
||||||
|
|
||||||
|
//! Filter the events that have at least one read receipt.
|
||||||
|
std::vector<QString> filterReadEvents(const QString &room_id,
|
||||||
|
const std::vector<QString> &event_ids,
|
||||||
|
const std::string &excluded_user);
|
||||||
|
//! Add event for which we are expecting some read receipts.
|
||||||
|
void addPendingReceipt(const QString &room_id, const QString &event_id);
|
||||||
|
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);
|
||||||
|
std::vector<QString> pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id);
|
||||||
|
|
||||||
QByteArray image(const QString &url) const;
|
QByteArray image(const QString &url) const;
|
||||||
QByteArray image(lmdb::txn &txn, const std::string &url) const;
|
QByteArray image(lmdb::txn &txn, const std::string &url) const;
|
||||||
QByteArray image(const std::string &url) const
|
QByteArray image(const std::string &url) const
|
||||||
|
@ -421,6 +433,9 @@ public:
|
||||||
|
|
||||||
OlmSessionStorage session_storage;
|
OlmSessionStorage session_storage;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//! Save an invited room.
|
//! Save an invited room.
|
||||||
void saveInvite(lmdb::txn &txn,
|
void saveInvite(lmdb::txn &txn,
|
||||||
|
@ -582,6 +597,11 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lmdb::dbi getPendingReceiptsDb(lmdb::txn &txn)
|
||||||
|
{
|
||||||
|
return lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
lmdb::dbi getMessagesDb(lmdb::txn &txn, const std::string &room_id)
|
lmdb::dbi getMessagesDb(lmdb::txn &txn, const std::string &room_id)
|
||||||
{
|
{
|
||||||
auto db =
|
auto db =
|
||||||
|
|
|
@ -685,6 +685,11 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
|
||||||
try {
|
try {
|
||||||
cache::init(userid);
|
cache::init(userid);
|
||||||
|
|
||||||
|
connect(cache::client(),
|
||||||
|
&Cache::newReadReceipts,
|
||||||
|
view_manager_,
|
||||||
|
&TimelineViewManager::updateReadReceipts);
|
||||||
|
|
||||||
const bool isInitialized = cache::client()->isInitialized();
|
const bool isInitialized = cache::client()->isInitialized();
|
||||||
const bool isValid = cache::client()->isFormatValid();
|
const bool isValid = cache::client()->isFormatValid();
|
||||||
|
|
||||||
|
@ -700,6 +705,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
|
||||||
loadStateFromCache();
|
loadStateFromCache();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (const lmdb::error &e) {
|
} catch (const lmdb::error &e) {
|
||||||
nhlog::db()->critical("failure during boot: {}", e.what());
|
nhlog::db()->critical("failure during boot: {}", e.what());
|
||||||
cache::client()->deleteData();
|
cache::client()->deleteData();
|
||||||
|
|
|
@ -42,6 +42,7 @@ StatusIndicator::StatusIndicator(QWidget *parent)
|
||||||
lockIcon_.addFile(":/icons/icons/ui/lock.png");
|
lockIcon_.addFile(":/icons/icons/ui/lock.png");
|
||||||
clockIcon_.addFile(":/icons/icons/ui/clock.png");
|
clockIcon_.addFile(":/icons/icons/ui/clock.png");
|
||||||
checkmarkIcon_.addFile(":/icons/icons/ui/checkmark.png");
|
checkmarkIcon_.addFile(":/icons/icons/ui/checkmark.png");
|
||||||
|
doubleCheckmarkIcon_.addFile(":/icons/icons/ui/double-tick-indicator.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -79,6 +80,10 @@ StatusIndicator::paintEvent(QPaintEvent *)
|
||||||
paintIcon(p, checkmarkIcon_);
|
paintIcon(p, checkmarkIcon_);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case StatusIndicatorState::Read: {
|
||||||
|
paintIcon(p, doubleCheckmarkIcon_);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case StatusIndicatorState::Empty:
|
case StatusIndicatorState::Empty:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -302,6 +307,8 @@ TimelineItem::TimelineItem(ImageItem *image,
|
||||||
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Image>, ImageItem>(
|
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Image>, ImageItem>(
|
||||||
image, event, with_sender);
|
image, event, with_sender);
|
||||||
|
|
||||||
|
markOwnMessagesAsReceived(event.sender);
|
||||||
|
|
||||||
addSaveImageAction(image);
|
addSaveImageAction(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,6 +322,8 @@ TimelineItem::TimelineItem(StickerItem *image,
|
||||||
{
|
{
|
||||||
setupWidgetLayout<mtx::events::Sticker, StickerItem>(image, event, with_sender);
|
setupWidgetLayout<mtx::events::Sticker, StickerItem>(image, event, with_sender);
|
||||||
|
|
||||||
|
markOwnMessagesAsReceived(event.sender);
|
||||||
|
|
||||||
addSaveImageAction(image);
|
addSaveImageAction(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,6 +337,8 @@ TimelineItem::TimelineItem(FileItem *file,
|
||||||
{
|
{
|
||||||
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::File>, FileItem>(
|
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::File>, FileItem>(
|
||||||
file, event, with_sender);
|
file, event, with_sender);
|
||||||
|
|
||||||
|
markOwnMessagesAsReceived(event.sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineItem::TimelineItem(AudioItem *audio,
|
TimelineItem::TimelineItem(AudioItem *audio,
|
||||||
|
@ -340,6 +351,8 @@ TimelineItem::TimelineItem(AudioItem *audio,
|
||||||
{
|
{
|
||||||
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Audio>, AudioItem>(
|
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Audio>, AudioItem>(
|
||||||
audio, event, with_sender);
|
audio, event, with_sender);
|
||||||
|
|
||||||
|
markOwnMessagesAsReceived(event.sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineItem::TimelineItem(VideoItem *video,
|
TimelineItem::TimelineItem(VideoItem *video,
|
||||||
|
@ -352,6 +365,8 @@ TimelineItem::TimelineItem(VideoItem *video,
|
||||||
{
|
{
|
||||||
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Video>, VideoItem>(
|
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Video>, VideoItem>(
|
||||||
video, event, with_sender);
|
video, event, with_sender);
|
||||||
|
|
||||||
|
markOwnMessagesAsReceived(event.sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -367,6 +382,8 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice
|
||||||
init();
|
init();
|
||||||
addReplyAction();
|
addReplyAction();
|
||||||
|
|
||||||
|
markOwnMessagesAsReceived(event.sender);
|
||||||
|
|
||||||
event_id_ = QString::fromStdString(event.event_id);
|
event_id_ = QString::fromStdString(event.event_id);
|
||||||
const auto sender = QString::fromStdString(event.sender);
|
const auto sender = QString::fromStdString(event.sender);
|
||||||
const auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
|
const auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
|
||||||
|
@ -413,6 +430,8 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote>
|
||||||
init();
|
init();
|
||||||
addReplyAction();
|
addReplyAction();
|
||||||
|
|
||||||
|
markOwnMessagesAsReceived(event.sender);
|
||||||
|
|
||||||
event_id_ = QString::fromStdString(event.event_id);
|
event_id_ = QString::fromStdString(event.event_id);
|
||||||
const auto sender = QString::fromStdString(event.sender);
|
const auto sender = QString::fromStdString(event.sender);
|
||||||
|
|
||||||
|
@ -455,6 +474,8 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text>
|
||||||
init();
|
init();
|
||||||
addReplyAction();
|
addReplyAction();
|
||||||
|
|
||||||
|
markOwnMessagesAsReceived(event.sender);
|
||||||
|
|
||||||
event_id_ = QString::fromStdString(event.event_id);
|
event_id_ = QString::fromStdString(event.event_id);
|
||||||
const auto sender = QString::fromStdString(event.sender);
|
const auto sender = QString::fromStdString(event.sender);
|
||||||
|
|
||||||
|
@ -495,6 +516,21 @@ TimelineItem::markSent()
|
||||||
statusIndicator_->setState(StatusIndicatorState::Sent);
|
statusIndicator_->setState(StatusIndicatorState::Sent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineItem::markOwnMessagesAsReceived(const std::string &sender)
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
if (sender == settings.value("auth/user_id").toString().toStdString())
|
||||||
|
statusIndicator_->setState(StatusIndicatorState::Received);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineItem::markRead()
|
||||||
|
{
|
||||||
|
if (statusIndicator_->state() != StatusIndicatorState::Encrypted)
|
||||||
|
statusIndicator_->setState(StatusIndicatorState::Read);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
TimelineItem::markReceived(bool isEncrypted)
|
TimelineItem::markReceived(bool isEncrypted)
|
||||||
{
|
{
|
||||||
|
|
|
@ -50,6 +50,8 @@ enum class StatusIndicatorState
|
||||||
Encrypted,
|
Encrypted,
|
||||||
//! The plaintext message was received by the server.
|
//! The plaintext message was received by the server.
|
||||||
Received,
|
Received,
|
||||||
|
//! At least one of the participants has read the message.
|
||||||
|
Read,
|
||||||
//! The client sent the message. Not yet received.
|
//! The client sent the message. Not yet received.
|
||||||
Sent,
|
Sent,
|
||||||
//! When the message is loaded from cache or backfill.
|
//! When the message is loaded from cache or backfill.
|
||||||
|
@ -66,6 +68,7 @@ class StatusIndicator : public QWidget
|
||||||
public:
|
public:
|
||||||
explicit StatusIndicator(QWidget *parent);
|
explicit StatusIndicator(QWidget *parent);
|
||||||
void setState(StatusIndicatorState state);
|
void setState(StatusIndicatorState state);
|
||||||
|
StatusIndicatorState state() const { return state_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *event) override;
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
@ -76,6 +79,7 @@ private:
|
||||||
QIcon lockIcon_;
|
QIcon lockIcon_;
|
||||||
QIcon clockIcon_;
|
QIcon clockIcon_;
|
||||||
QIcon checkmarkIcon_;
|
QIcon checkmarkIcon_;
|
||||||
|
QIcon doubleCheckmarkIcon_;
|
||||||
|
|
||||||
QColor iconColor_ = QColor("#999");
|
QColor iconColor_ = QColor("#999");
|
||||||
|
|
||||||
|
@ -234,6 +238,7 @@ public:
|
||||||
QString eventId() const { return event_id_; }
|
QString eventId() const { return event_id_; }
|
||||||
void setEventId(const QString &event_id) { event_id_ = event_id; }
|
void setEventId(const QString &event_id) { event_id_ = event_id; }
|
||||||
void markReceived(bool isEncrypted);
|
void markReceived(bool isEncrypted);
|
||||||
|
void markRead();
|
||||||
void markSent();
|
void markSent();
|
||||||
bool isReceived() { return isReceived_; };
|
bool isReceived() { return isReceived_; };
|
||||||
void setRoomId(QString room_id) { room_id_ = room_id; }
|
void setRoomId(QString room_id) { room_id_ = room_id; }
|
||||||
|
@ -252,6 +257,8 @@ protected:
|
||||||
void contextMenuEvent(QContextMenuEvent *event) override;
|
void contextMenuEvent(QContextMenuEvent *event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
//! If we are the sender of the message the event wil be marked as received by the server.
|
||||||
|
void markOwnMessagesAsReceived(const std::string &sender);
|
||||||
void init();
|
void init();
|
||||||
//! Add a context menu option to save the image of the timeline item.
|
//! Add a context menu option to save the image of the timeline item.
|
||||||
void addSaveImageAction(ImageItem *image);
|
void addSaveImageAction(ImageItem *image);
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
#include "Cache.h"
|
#include "Cache.h"
|
||||||
#include "ChatPage.h"
|
#include "ChatPage.h"
|
||||||
|
@ -352,6 +353,27 @@ TimelineView::parseEncryptedEvent(const mtx::events::EncryptedEvent<mtx::events:
|
||||||
return {dummy, false};
|
return {dummy, false};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineView::displayReadReceipts(std::vector<TimelineEvent> events)
|
||||||
|
{
|
||||||
|
QtConcurrent::run(
|
||||||
|
[events = std::move(events), room_id = room_id_, local_user = local_user_, this]() {
|
||||||
|
std::vector<QString> event_ids;
|
||||||
|
|
||||||
|
for (const auto &e : events) {
|
||||||
|
if (utils::event_sender(e) == local_user)
|
||||||
|
event_ids.emplace_back(
|
||||||
|
QString::fromStdString(utils::event_id(e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto readEvents =
|
||||||
|
cache::client()->filterReadEvents(room_id, event_ids, local_user.toStdString());
|
||||||
|
|
||||||
|
if (!readEvents.empty())
|
||||||
|
emit markReadEvents(readEvents);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
TimelineView::renderBottomEvents(const std::vector<TimelineEvent> &events)
|
TimelineView::renderBottomEvents(const std::vector<TimelineEvent> &events)
|
||||||
{
|
{
|
||||||
|
@ -373,6 +395,8 @@ TimelineView::renderBottomEvents(const std::vector<TimelineEvent> &events)
|
||||||
|
|
||||||
lastMessageDirection_ = TimelineDirection::Bottom;
|
lastMessageDirection_ = TimelineDirection::Bottom;
|
||||||
|
|
||||||
|
displayReadReceipts(events);
|
||||||
|
|
||||||
QApplication::processEvents();
|
QApplication::processEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,6 +431,8 @@ TimelineView::renderTopEvents(const std::vector<TimelineEvent> &events)
|
||||||
|
|
||||||
QApplication::processEvents();
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
displayReadReceipts(events);
|
||||||
|
|
||||||
// If this batch is the first being rendered (i.e the first and the last
|
// If this batch is the first being rendered (i.e the first and the last
|
||||||
// events originate from this batch), set the last sender.
|
// events originate from this batch), set the last sender.
|
||||||
if (lastSender_.isEmpty() && !items.empty()) {
|
if (lastSender_.isEmpty() && !items.empty()) {
|
||||||
|
@ -499,6 +525,23 @@ TimelineView::init()
|
||||||
connect(this, &TimelineView::messageFailed, this, &TimelineView::handleFailedMessage);
|
connect(this, &TimelineView::messageFailed, this, &TimelineView::handleFailedMessage);
|
||||||
connect(this, &TimelineView::messageSent, this, &TimelineView::updatePendingMessage);
|
connect(this, &TimelineView::messageSent, this, &TimelineView::updatePendingMessage);
|
||||||
|
|
||||||
|
connect(
|
||||||
|
this, &TimelineView::markReadEvents, this, [this](const std::vector<QString> &event_ids) {
|
||||||
|
for (const auto &event : event_ids) {
|
||||||
|
if (eventIds_.contains(event)) {
|
||||||
|
auto widget = eventIds_[event];
|
||||||
|
if (!widget)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto item = qobject_cast<TimelineItem *>(widget);
|
||||||
|
if (!item)
|
||||||
|
return;
|
||||||
|
|
||||||
|
item->markRead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
connect(scroll_area_->verticalScrollBar(),
|
connect(scroll_area_->verticalScrollBar(),
|
||||||
SIGNAL(valueChanged(int)),
|
SIGNAL(valueChanged(int)),
|
||||||
this,
|
this,
|
||||||
|
@ -615,6 +658,7 @@ TimelineView::updatePendingMessage(const std::string &txn_id, const QString &eve
|
||||||
// we've already marked the widget as received.
|
// we've already marked the widget as received.
|
||||||
if (!msg.widget->isReceived()) {
|
if (!msg.widget->isReceived()) {
|
||||||
msg.widget->markReceived(msg.is_encrypted);
|
msg.widget->markReceived(msg.is_encrypted);
|
||||||
|
cache::client()->addPendingReceipt(room_id_, event_id);
|
||||||
pending_sent_msgs_.append(msg);
|
pending_sent_msgs_.append(msg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -826,9 +870,14 @@ TimelineView::removePendingMessage(const std::string &txn_id)
|
||||||
}
|
}
|
||||||
for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); ++it) {
|
for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); ++it) {
|
||||||
if (it->txn_id == txn_id) {
|
if (it->txn_id == txn_id) {
|
||||||
if (it->widget)
|
if (it->widget) {
|
||||||
it->widget->markReceived(it->is_encrypted);
|
it->widget->markReceived(it->is_encrypted);
|
||||||
|
|
||||||
|
// TODO: update when a solution for encrypted messages is available.
|
||||||
|
if (!it->is_encrypted)
|
||||||
|
cache::client()->addPendingReceipt(room_id_, it->event_id);
|
||||||
|
}
|
||||||
|
|
||||||
nhlog::ui()->info("[{}] received sync before message response", txn_id);
|
nhlog::ui()->info("[{}] received sync before message response", txn_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,6 +156,7 @@ signals:
|
||||||
void messagesRetrieved(const mtx::responses::Messages &res);
|
void messagesRetrieved(const mtx::responses::Messages &res);
|
||||||
void messageFailed(const std::string &txn_id);
|
void messageFailed(const std::string &txn_id);
|
||||||
void messageSent(const std::string &txn_id, const QString &event_id);
|
void messageSent(const std::string &txn_id, const QString &event_id);
|
||||||
|
void markReadEvents(const std::vector<QString> &event_ids);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *event) override;
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
@ -165,6 +166,9 @@ protected:
|
||||||
private:
|
private:
|
||||||
using TimelineEvent = mtx::events::collections::TimelineEvents;
|
using TimelineEvent = mtx::events::collections::TimelineEvents;
|
||||||
|
|
||||||
|
//! Mark our own widgets as read if they have more than one receipt.
|
||||||
|
void displayReadReceipts(std::vector<TimelineEvent> events);
|
||||||
|
|
||||||
QWidget *relativeWidget(QWidget *item, int dt) const;
|
QWidget *relativeWidget(QWidget *item, int dt) const;
|
||||||
|
|
||||||
DecryptionResult parseEncryptedEvent(
|
DecryptionResult parseEncryptedEvent(
|
||||||
|
|
|
@ -36,6 +36,17 @@ TimelineViewManager::TimelineViewManager(QWidget *parent)
|
||||||
setStyleSheet("border: none;");
|
setStyleSheet("border: none;");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineViewManager::updateReadReceipts(const QString &room_id,
|
||||||
|
const std::vector<QString> &event_ids)
|
||||||
|
{
|
||||||
|
if (timelineViewExists(room_id)) {
|
||||||
|
auto view = views_[room_id];
|
||||||
|
if (view)
|
||||||
|
emit view->markReadEvents(event_ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
TimelineViewManager::removeTimelineEvent(const QString &room_id, const QString &event_id)
|
TimelineViewManager::removeTimelineEvent(const QString &room_id, const QString &event_id)
|
||||||
{
|
{
|
||||||
|
|
|
@ -57,6 +57,7 @@ signals:
|
||||||
void updateRoomsLastMessage(const QString &user, const DescInfo &info);
|
void updateRoomsLastMessage(const QString &user, const DescInfo &info);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|
||||||
void removeTimelineEvent(const QString &room_id, const QString &event_id);
|
void removeTimelineEvent(const QString &room_id, const QString &event_id);
|
||||||
void initWithMessages(const std::map<QString, mtx::responses::Timeline> &msgs);
|
void initWithMessages(const std::map<QString, mtx::responses::Timeline> &msgs);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue