diff --git a/src/Cache.cc b/src/Cache.cc
index 92c86322..8b00a828 100644
--- a/src/Cache.cc
+++ b/src/Cache.cc
@@ -48,6 +48,7 @@ static constexpr const char *MEDIA_DB = "media";
static constexpr const char *SYNC_STATE_DB = "sync_state";
//! Read receipts per room/event.
static constexpr const char *READ_RECEIPTS_DB = "read_receipts";
+static constexpr const char *NOTIFICATIONS_DB = "sent_notifications";
using CachedReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
@@ -60,6 +61,7 @@ Cache::Cache(const QString &userId, QObject *parent)
, invitesDb_{0}
, mediaDb_{0}
, readReceiptsDb_{0}
+ , notificationsDb_{0}
, localUserId_{userId}
{}
@@ -112,12 +114,13 @@ Cache::setup()
env_.open(statePath.toStdString().c_str());
}
- auto txn = lmdb::txn::begin(env_);
- syncStateDb_ = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE);
- roomsDb_ = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE);
- invitesDb_ = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE);
- mediaDb_ = lmdb::dbi::open(txn, MEDIA_DB, MDB_CREATE);
- readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
+ auto txn = lmdb::txn::begin(env_);
+ syncStateDb_ = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE);
+ roomsDb_ = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE);
+ invitesDb_ = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE);
+ mediaDb_ = lmdb::dbi::open(txn, MEDIA_DB, MDB_CREATE);
+ readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
+ notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE);
txn.commit();
qRegisterMetaType<RoomInfo>();
@@ -1087,6 +1090,36 @@ Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_
return members;
}
+void
+Cache::markSentNotification(const std::string &event_id)
+{
+ auto txn = lmdb::txn::begin(env_);
+ lmdb::dbi_put(txn, notificationsDb_, lmdb::val(event_id), lmdb::val(std::string("")));
+ txn.commit();
+}
+
+void
+Cache::removeReadNotification(const std::string &event_id)
+{
+ auto txn = lmdb::txn::begin(env_);
+
+ lmdb::dbi_del(txn, notificationsDb_, lmdb::val(event_id), nullptr);
+
+ txn.commit();
+}
+
+bool
+Cache::isNotificationSent(const std::string &event_id)
+{
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+
+ lmdb::val value;
+ bool res = lmdb::dbi_get(txn, notificationsDb_, lmdb::val(event_id), value);
+ txn.commit();
+
+ return res;
+}
+
QHash<QString, QString> Cache::DisplayNames;
QHash<QString, QString> Cache::AvatarUrls;
diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index ee338c2d..4750e67a 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cc
@@ -36,6 +36,7 @@
#include "TypingDisplay.h"
#include "UserInfoWidget.h"
#include "UserSettingsPage.h"
+#include "Utils.h"
#include "dialogs/ReadReceipts.h"
#include "timeline/TimelineViewManager.h"
@@ -339,6 +340,10 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
connect(client_.data(), &MatrixClient::redactionFailed, this, [this](const QString &error) {
emit showNotification(QString("Message redaction failed: %1").arg(error));
});
+ connect(client_.data(),
+ &MatrixClient::notificationsRetrieved,
+ this,
+ &ChatPage::sendDesktopNotifications);
showContentTimer_ = new QTimer(this);
showContentTimer_->setSingleShot(true);
@@ -420,13 +425,20 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
view_manager_->initialize(rooms);
removeLeftRooms(rooms.leave);
+ bool hasNotifications = false;
for (const auto &room : rooms.join) {
auto room_id = QString::fromStdString(room.first);
updateTypingUsers(room_id, room.second.ephemeral.typing);
updateRoomNotificationCount(
room_id, room.second.unread_notifications.notification_count);
+
+ if (room.second.unread_notifications.notification_count > 0)
+ hasNotifications = true;
}
+
+ if (hasNotifications)
+ client_->getNotifications();
});
connect(this, &ChatPage::syncRoomlist, room_list_, &RoomList::sync);
@@ -838,3 +850,29 @@ ChatPage::updateRoomNotificationCount(const QString &room_id, uint16_t notificat
{
room_list_->updateUnreadMessageCount(room_id, notification_count);
}
+
+void
+ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
+{
+ for (const auto &item : res.notifications) {
+ const auto event_id = utils::event_id(item.event);
+
+ try {
+ if (item.read) {
+ cache_->removeReadNotification(event_id);
+ continue;
+ }
+
+ if (!cache_->isNotificationSent(event_id)) {
+ // TODO: send desktop notification
+ // qDebug() << "sender" << utils::event_sender(item.event);
+ // qDebug() << "body" << utils::event_body(item.event);
+
+ // We should only sent one notification per event.
+ // cache_->markSentNotification(event_id);
+ }
+ } catch (const lmdb::error &e) {
+ qWarning() << e.what();
+ }
+ }
+}
diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
index de930fc3..54756c7c 100644
--- a/src/MatrixClient.cc
+++ b/src/MatrixClient.cc
@@ -1310,3 +1310,41 @@ MatrixClient::redactEvent(const QString &room_id, const QString &event_id)
}
});
}
+
+void
+MatrixClient::getNotifications() noexcept
+{
+ QUrlQuery query;
+ query.addQueryItem("limit", "5");
+
+ QUrl endpoint(server_);
+ endpoint.setQuery(query);
+ endpoint.setPath(clientApiUrl_ + "/notifications");
+
+ QNetworkRequest request(QString(endpoint.toEncoded()));
+ setupAuth(request);
+
+ auto reply = get(request);
+ connect(reply, &QNetworkReply::finished, this, [reply, this]() {
+ reply->deleteLater();
+
+ int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+ auto data = reply->readAll();
+
+ if (status == 0 || status >= 400) {
+ try {
+ mtx::errors::Error res = nlohmann::json::parse(data);
+ std::cout << nlohmann::json::parse(data).dump(2) << '\n';
+ // TODO: Response with an error signal
+ return;
+ } catch (const std::exception &) {
+ }
+ }
+
+ try {
+ emit notificationsRetrieved(nlohmann::json::parse(data));
+ } catch (const std::exception &e) {
+ qWarning() << "failed to parse /notifications response" << e.what();
+ }
+ });
+}
diff --git a/src/Utils.cc b/src/Utils.cc
index d9b06b52..14620145 100644
--- a/src/Utils.cc
+++ b/src/Utils.cc
@@ -111,3 +111,30 @@ utils::levenshtein_distance(const std::string &s1, const std::string &s2)
return *std::min_element(row1.begin(), row1.end());
}
+
+QString
+utils::event_body(const mtx::events::collections::TimelineEvents &event)
+{
+ using namespace mtx::events;
+ using namespace mtx::events::msg;
+
+ if (mpark::holds_alternative<RoomEvent<Audio>>(event)) {
+ return message_body<RoomEvent<Audio>>(event);
+ } else if (mpark::holds_alternative<RoomEvent<Emote>>(event)) {
+ return message_body<RoomEvent<Emote>>(event);
+ } else if (mpark::holds_alternative<RoomEvent<File>>(event)) {
+ return message_body<RoomEvent<File>>(event);
+ } else if (mpark::holds_alternative<RoomEvent<Image>>(event)) {
+ return message_body<RoomEvent<Image>>(event);
+ } else if (mpark::holds_alternative<RoomEvent<Notice>>(event)) {
+ return message_body<RoomEvent<Notice>>(event);
+ } else if (mpark::holds_alternative<Sticker>(event)) {
+ return message_body<Sticker>(event);
+ } else if (mpark::holds_alternative<RoomEvent<Text>>(event)) {
+ return message_body<RoomEvent<Text>>(event);
+ } else if (mpark::holds_alternative<RoomEvent<Video>>(event)) {
+ return message_body<RoomEvent<Video>>(event);
+ }
+
+ return QString();
+}
diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc
index 8781f90e..5b433674 100644
--- a/src/timeline/TimelineView.cc
+++ b/src/timeline/TimelineView.cc
@@ -727,12 +727,6 @@ TimelineView::event(QEvent *event)
return QWidget::event(event);
}
-QString
-TimelineView::getEventSender(const mtx::events::collections::TimelineEvents &event) const
-{
- return mpark::visit([](auto msg) { return QString::fromStdString(msg.sender); }, event);
-}
-
void
TimelineView::toggleScrollDownButton()
{
@@ -826,8 +820,8 @@ TimelineView::relativeWidget(TimelineItem *item, int dt) const
TimelineEvent
TimelineView::findFirstViewableEvent(const std::vector<TimelineEvent> &events)
{
- auto it = std::find_if(events.begin(), events.end(), [this](const auto &event) {
- return mtx::events::EventType::RoomMessage == getEventType(event);
+ auto it = std::find_if(events.begin(), events.end(), [](const auto &event) {
+ return mtx::events::EventType::RoomMessage == utils::event_type(event);
});
return (it == std::end(events)) ? events.front() : *it;
@@ -836,19 +830,13 @@ TimelineView::findFirstViewableEvent(const std::vector<TimelineEvent> &events)
TimelineEvent
TimelineView::findLastViewableEvent(const std::vector<TimelineEvent> &events)
{
- auto it = std::find_if(events.rbegin(), events.rend(), [this](const auto &event) {
- return mtx::events::EventType::RoomMessage == getEventType(event);
+ auto it = std::find_if(events.rbegin(), events.rend(), [](const auto &event) {
+ return mtx::events::EventType::RoomMessage == utils::event_type(event);
});
return (it == std::rend(events)) ? events.back() : *it;
}
-inline mtx::events::EventType
-TimelineView::getEventType(const mtx::events::collections::TimelineEvents &event) const
-{
- return mpark::visit([](auto msg) { return msg.type; }, event);
-}
-
void
TimelineView::saveMessageInfo(const QString &sender,
uint64_t origin_server_ts,
|