diff --git a/include/AvatarProvider.h b/include/AvatarProvider.h
index 906f2593..44bf1ad2 100644
--- a/include/AvatarProvider.h
+++ b/include/AvatarProvider.h
@@ -36,7 +36,7 @@ class AvatarProvider : public QObject
public:
static void init(QSharedPointer<MatrixClient> client);
- static void resolve(const QString &userId, TimelineItem *item);
+ static void resolve(const QString &userId, std::function<void(QImage)> callback);
static void setAvatarUrl(const QString &userId, const QUrl &url);
static void clear();
@@ -48,5 +48,5 @@ private:
using UserID = QString;
static QMap<UserID, AvatarData> avatars_;
- static QMap<UserID, QList<TimelineItem *>> toBeResolved_;
+ static QMap<UserID, QList<std::function<void(QImage)>>> toBeResolved_;
};
diff --git a/include/Cache.h b/include/Cache.h
index 1f6c59f0..ae58e418 100644
--- a/include/Cache.h
+++ b/include/Cache.h
@@ -18,11 +18,52 @@
#pragma once
#include <QDir>
+#include <json.hpp>
#include <lmdb++.h>
#include <mtx/responses.hpp>
class RoomState;
+//! Used to uniquely identify a list of read receipts.
+struct ReadReceiptKey
+{
+ std::string event_id;
+ std::string room_id;
+};
+
+inline void
+to_json(json &j, const ReadReceiptKey &key)
+{
+ j = json{{"event_id", key.event_id}, {"room_id", key.room_id}};
+}
+
+inline void
+from_json(const json &j, ReadReceiptKey &key)
+{
+ key.event_id = j.at("event_id").get<std::string>();
+ key.room_id = j.at("room_id").get<std::string>();
+}
+
+//! Decribes a read receipt stored in cache.
+struct ReadReceiptValue
+{
+ std::string user_id;
+ uint64_t ts;
+};
+
+inline void
+to_json(json &j, const ReadReceiptValue &value)
+{
+ j = json{{"user_id", value.user_id}, {"ts", value.ts}};
+}
+
+inline void
+from_json(const json &j, ReadReceiptValue &value)
+{
+ value.user_id = j.at("user_id").get<std::string>();
+ value.ts = j.at("ts").get<uint64_t>();
+}
+
class Cache
{
public:
@@ -48,6 +89,19 @@ public:
bool isFormatValid();
void setCurrentFormat();
+ //! Adds a user to the read list for the given event.
+ //!
+ //! There should be only one user id present in a receipt list per room.
+ //! The user id should be removed from any other lists.
+ using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
+ void updateReadReceipt(const std::string &room_id, const Receipts &receipts);
+
+ //! Retrieve all the read receipts for the given event id and room.
+ //!
+ //! Returns a map of user ids and the time of the read receipt in milliseconds.
+ using UserReceipts = std::multimap<uint64_t, std::string>;
+ UserReceipts readReceipts(const QString &event_id, const QString &room_id);
+
QByteArray image(const QString &url) const;
void saveImage(const QString &url, const QByteArray &data);
@@ -60,6 +114,7 @@ private:
lmdb::dbi roomDb_;
lmdb::dbi invitesDb_;
lmdb::dbi imagesDb_;
+ lmdb::dbi readReceiptsDb_;
bool isMounted_;
diff --git a/include/ChatPage.h b/include/ChatPage.h
index 24fc6a25..584424c0 100644
--- a/include/ChatPage.h
+++ b/include/ChatPage.h
@@ -42,6 +42,10 @@ class TypingDisplay;
class UserInfoWidget;
class UserSettings;
+namespace dialogs {
+class ReadReceipts;
+}
+
constexpr int CONSENSUS_TIMEOUT = 1000;
constexpr int SHOW_CONTENT_TIMEOUT = 3000;
constexpr int TYPING_REFRESH_TIMEOUT = 10000;
@@ -59,6 +63,9 @@ public:
// Initialize all the components of the UI.
void bootstrap(QString userid, QString homeserver, QString token);
void showQuickSwitcher();
+ void showReadReceipts(const QString &event_id);
+
+ static ChatPage *instance() { return instance_; }
signals:
void contentLoaded();
@@ -84,6 +91,8 @@ private slots:
void removeInvite(const QString &room_id);
private:
+ static ChatPage *instance_;
+
using UserID = QString;
using RoomStates = QMap<UserID, RoomState>;
using Membership = mtx::events::StateEvent<mtx::events::state::Member>;
@@ -150,6 +159,9 @@ private:
QSharedPointer<QuickSwitcher> quickSwitcher_;
QSharedPointer<OverlayModal> quickSwitcherModal_;
+ QSharedPointer<dialogs::ReadReceipts> receiptsDialog_;
+ QSharedPointer<OverlayModal> receiptsModal_;
+
// Matrix Client API provider.
QSharedPointer<MatrixClient> client_;
diff --git a/include/Config.h b/include/Config.h
index 7d35094e..5492e5fb 100644
--- a/include/Config.h
+++ b/include/Config.h
@@ -15,6 +15,10 @@ static constexpr int emojiSize = 14;
static constexpr int headerFontSize = 21;
static constexpr int typingNotificationFontSize = 11;
+namespace receipts {
+static constexpr int font = 12;
+}
+
namespace dialogs {
static constexpr int labelSize = 15;
}
diff --git a/include/MainWindow.h b/include/MainWindow.h
index 2d047b51..d7c5e41d 100644
--- a/include/MainWindow.h
+++ b/include/MainWindow.h
@@ -42,7 +42,7 @@ public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
- static MainWindow *instance();
+ static MainWindow *instance() { return instance_; };
void saveCurrentWindowSize();
protected:
diff --git a/include/dialogs/ReadReceipts.h b/include/dialogs/ReadReceipts.h
new file mode 100644
index 00000000..42a9e1b7
--- /dev/null
+++ b/include/dialogs/ReadReceipts.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include <QDateTime>
+#include <QFrame>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QListWidget>
+#include <QVBoxLayout>
+
+class Avatar;
+
+namespace dialogs {
+
+class ReceiptItem : public QWidget
+{
+ Q_OBJECT
+
+public:
+ ReceiptItem(QWidget *parent, const QString &user_id, uint64_t timestamp);
+
+private:
+ QString dateFormat(const QDateTime &then) const;
+
+ QHBoxLayout *topLayout_;
+ QVBoxLayout *textLayout_;
+
+ Avatar *avatar_;
+
+ QLabel *userName_;
+ QLabel *timestamp_;
+};
+
+class ReadReceipts : public QFrame
+{
+ Q_OBJECT
+public:
+ explicit ReadReceipts(QWidget *parent = nullptr);
+
+public slots:
+ void addUsers(const std::multimap<uint64_t, std::string> &users);
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+
+private:
+ QLabel *topLabel_;
+
+ QListWidget *userList_;
+};
+} // dialogs
diff --git a/include/timeline/TimelineItem.h b/include/timeline/TimelineItem.h
index f1498d1b..78fb95c9 100644
--- a/include/timeline/TimelineItem.h
+++ b/include/timeline/TimelineItem.h
@@ -87,6 +87,7 @@ public:
protected:
void paintEvent(QPaintEvent *event) override;
+ void contextMenuEvent(QContextMenuEvent *event) override;
private:
void init();
@@ -116,6 +117,9 @@ private:
DescInfo descriptionMsg_;
+ QMenu *receiptsMenu_;
+ QAction *showReadReceipts_;
+
QHBoxLayout *topLayout_;
QVBoxLayout *sideLayout_; // Avatar or Timestamp
QVBoxLayout *mainLayout_; // Header & Message body
@@ -156,7 +160,7 @@ TimelineItem::setupLocalWidgetLayout(Widget *widget,
setupAvatarLayout(displayName);
mainLayout_->addLayout(headerLayout_);
- AvatarProvider::resolve(userid, this);
+ AvatarProvider::resolve(userid, [=](const QImage &img) { setUserAvatar(img); });
} else {
setupSimpleLayout();
}
@@ -199,7 +203,7 @@ TimelineItem::setupWidgetLayout(Widget *widget,
mainLayout_->addLayout(headerLayout_);
- AvatarProvider::resolve(sender, this);
+ AvatarProvider::resolve(sender, [=](const QImage &img) { setUserAvatar(img); });
} else {
setupSimpleLayout();
}
diff --git a/include/ui/OverlayModal.h b/include/ui/OverlayModal.h
index 167ba71a..5f6b6eee 100644
--- a/include/ui/OverlayModal.h
+++ b/include/ui/OverlayModal.h
@@ -18,6 +18,7 @@
#pragma once
#include <QGraphicsOpacityEffect>
+#include <QKeyEvent>
#include <QPaintEvent>
#include <QPropertyAnimation>
@@ -37,6 +38,7 @@ public:
protected:
void paintEvent(QPaintEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
private:
int duration_;
|