diff --git a/src/controller.cpp b/src/controller.cpp index ec46bf74d..0e5a00e91 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -45,7 +45,6 @@ #include #ifdef QUOTIENT_07 -#include #include #endif @@ -120,8 +119,8 @@ Controller::Controller(QObject *parent) connect(&Accounts, &AccountRegistry::accountCountChanged, this, [this]() { if (Accounts.size() > oldAccountCount) { auto connection = Accounts.accounts()[Accounts.size() - 1]; - connect(connection, &Connection::syncDone, this, [this, connection]() { - handleNotifications(connection); + connect(connection, &Connection::syncDone, this, [connection]() { + NotificationsManager::instance().handleNotifications(connection); }); } oldAccountCount = Accounts.size(); @@ -129,81 +128,6 @@ Controller::Controller(QObject *parent) #endif } -#ifdef QUOTIENT_07 -void Controller::handleNotifications(QPointer connection) -{ - static QStringList initial; - static QStringList oldNotifications; - auto job = connection->callApi(); - - connect(job, &BaseJob::success, this, [job, connection]() { - const auto notifications = job->jsonData()["notifications"].toArray(); - if (!initial.contains(connection->user()->id())) { - initial.append(connection->user()->id()); - for (const auto &n : notifications) { - oldNotifications += n.toObject()["event"].toObject()["event_id"].toString(); - } - return; - } - for (const auto &n : notifications) { - const auto notification = n.toObject(); - if (notification["read"].toBool()) { - continue; - } - if (oldNotifications.contains(notification["event"].toObject()["event_id"].toString())) { - continue; - } - oldNotifications += notification["event"].toObject()["event_id"].toString(); - auto room = connection->room(notification["room_id"].toString()); - - // If room exists, room is NOT active OR the application is NOT active, show notification - if (room - && !(RoomManager::instance().currentRoom() && room->id() == RoomManager::instance().currentRoom()->id() - && QGuiApplication::applicationState() == Qt::ApplicationActive)) { - // The room might have been deleted (for example rejected invitation). - auto sender = room->user(notification["event"].toObject()["sender"].toString()); - - QString body; - - if (notification["event"].toObject()["type"].toString() == "org.matrix.msc3381.poll.start") { - body = notification["event"] - .toObject()["content"] - .toObject()["org.matrix.msc3381.poll.start"] - .toObject()["question"] - .toObject()["body"] - .toString(); - } else { - body = notification["event"].toObject()["content"].toObject()["body"].toString(); - } - - if (notification["event"]["type"] == "m.room.encrypted") { -#ifdef Quotient_E2EE_ENABLED - auto decrypted = connection->decryptNotification(notification); - body = decrypted["content"].toObject()["body"].toString(); -#endif - if (body.isEmpty()) { - body = i18n("Encrypted Message"); - } - } - - QImage avatar_image; - if (!sender->avatarUrl(room).isEmpty()) { - avatar_image = sender->avatar(128, room); - } else { - avatar_image = room->avatar(128); - } - NotificationsManager::instance().postNotification(dynamic_cast(room), - sender->displayname(room), - body, - avatar_image, - notification["event"].toObject()["event_id"].toString(), - true); - } - } - }); -} -#endif - Controller &Controller::instance() { static Controller _instance; diff --git a/src/controller.h b/src/controller.h index 73725059b..f29fe2c80 100644 --- a/src/controller.h +++ b/src/controller.h @@ -230,9 +230,6 @@ private: QMap m_notificationCounts; bool hasWindowSystem() const; -#ifdef QUOTIENT_07 - void handleNotifications(QPointer connection); -#endif private Q_SLOTS: void invokeLogin(); diff --git a/src/notificationsmanager.cpp b/src/notificationsmanager.cpp index baf6d50a5..5f96467b8 100644 --- a/src/notificationsmanager.cpp +++ b/src/notificationsmanager.cpp @@ -5,7 +5,7 @@ #include -#include +#include #include #include @@ -18,6 +18,7 @@ #endif #include +#include #include #include #include @@ -48,6 +49,148 @@ NotificationsManager::NotificationsManager(QObject *parent) }); } +#ifdef QUOTIENT_07 +void NotificationsManager::handleNotifications(QPointer connection) +{ + if (!m_connActiveJob.contains(connection->user()->id())) { + auto job = connection->callApi(); + m_connActiveJob.append(connection->user()->id()); + connect(job, &BaseJob::success, this, [this, job, connection]() { + m_connActiveJob.removeAll(connection->user()->id()); + processNotificationJob(connection, job, !m_oldNotifications.contains(connection->user()->id())); + }); + } +} +#endif + +void NotificationsManager::processNotificationJob(QPointer connection, Quotient::GetNotificationsJob *job, bool initialization) +{ + if (job == nullptr) { + return; + } + if (connection == nullptr) { + qWarning() << QStringLiteral("No connection for GetNotificationsJob %1").arg(job->objectName()); + return; + } + + const auto connectionId = connection->user()->id(); + + // If pagination has occurred set off the next job + auto nextToken = job->jsonData()["next_token"].toString(); + if (!nextToken.isEmpty()) { + auto nextJob = connection->callApi(nextToken); + m_connActiveJob.append(connectionId); + connect(nextJob, &BaseJob::success, this, [this, nextJob, connection, initialization]() { + m_connActiveJob.removeAll(connection->user()->id()); + processNotificationJob(connection, nextJob, initialization); + }); + } + + const auto notifications = job->jsonData()["notifications"].toArray(); + if (initialization) { + m_oldNotifications[connectionId] = QStringList(); + for (const auto &n : notifications) { + if (!m_initialTimestamp.contains(connectionId)) { + m_initialTimestamp[connectionId] = n.toObject()["ts"].toDouble(); + } else { + qint64 timestamp = n.toObject()["ts"].toDouble(); + if (timestamp > m_initialTimestamp[connectionId]) { + m_initialTimestamp[connectionId] = timestamp; + } + } + + auto connectionNotifications = m_oldNotifications.value(connectionId); + connectionNotifications += n.toObject()["event"].toObject()["event_id"].toString(); + m_oldNotifications[connectionId] = connectionNotifications; + } + return; + } + for (const auto &n : notifications) { + const auto notification = n.toObject(); + if (notification["read"].toBool()) { + continue; + } + auto connectionNotifications = m_oldNotifications.value(connectionId); + if (connectionNotifications.contains(notification["event"].toObject()["event_id"].toString())) { + continue; + } + connectionNotifications += notification["event"].toObject()["event_id"].toString(); + m_oldNotifications[connectionId] = connectionNotifications; + + auto room = connection->room(notification["room_id"].toString()); + if (shouldPostNotification(connection, n)) { + // The room might have been deleted (for example rejected invitation). + auto sender = room->user(notification["event"].toObject()["sender"].toString()); + + QString body; + + if (notification["event"].toObject()["type"].toString() == "org.matrix.msc3381.poll.start") { + body = notification["event"] + .toObject()["content"] + .toObject()["org.matrix.msc3381.poll.start"] + .toObject()["question"] + .toObject()["body"] + .toString(); + } else { + body = notification["event"].toObject()["content"].toObject()["body"].toString(); + } + + if (notification["event"]["type"] == "m.room.encrypted") { +#ifdef Quotient_E2EE_ENABLED + auto decrypted = connection->decryptNotification(notification); + body = decrypted["content"].toObject()["body"].toString(); +#endif + if (body.isEmpty()) { + body = i18n("Encrypted Message"); + } + } + + QImage avatar_image; + if (!sender->avatarUrl(room).isEmpty()) { + avatar_image = sender->avatar(128, room); + } else { + avatar_image = room->avatar(128); + } + postNotification(dynamic_cast(room), + sender->displayname(room), + body, + avatar_image, + notification["event"].toObject()["event_id"].toString(), + true); + } + } +} + +bool NotificationsManager::shouldPostNotification(QPointer connection, const QJsonValue ¬ification) +{ + if (connection == nullptr) { + return false; + } + + auto room = connection->room(notification["room_id"].toString()); + if (room == nullptr) { + return false; + } + + // If the room is the current room and the application is active the notification + // should not be shown. + // This is setup so that if the application is inactive the notification will + // always be posted, even if the room is the current room. + bool isCurrentRoom = RoomManager::instance().currentRoom() && room->id() == RoomManager::instance().currentRoom()->id(); + if (isCurrentRoom && QGuiApplication::applicationState() == Qt::ApplicationActive) { + return false; + } + + // If the notification timestamp is earlier than the initial timestamp assume + // the notification is old and shouldn't be posted. + qint64 timestamp = notification["ts"].toDouble(); + if (timestamp < m_initialTimestamp[connection->user()->id()]) { + return false; + } + + return true; +} + void NotificationsManager::postNotification(NeoChatRoom *room, const QString &sender, const QString &text, diff --git a/src/notificationsmanager.h b/src/notificationsmanager.h index 2b11c011d..dd4fb33be 100644 --- a/src/notificationsmanager.h +++ b/src/notificationsmanager.h @@ -4,11 +4,18 @@ #pragma once #include +#include #include #include #include #include -#include +#include +#include + +namespace Quotient +{ +class Connection; +} class KNotification; class NeoChatRoom; @@ -181,9 +188,23 @@ public: */ QVector getKeywordNotificationActions(); +#ifdef QUOTIENT_07 + /** + * @brief Handle the notifications for the given connection. + */ + void handleNotifications(QPointer connection); +#endif + private: NotificationsManager(QObject *parent = nullptr); + QHash m_initialTimestamp; + QHash m_oldNotifications; + + QStringList m_connActiveJob; + + bool shouldPostNotification(QPointer connection, const QJsonValue ¬ification); + QHash m_notifications; QHash> m_invitations; @@ -218,6 +239,8 @@ private: QVector toActions(PushNotificationAction::Action action, const QString &sound = "default"); private Q_SLOTS: + void processNotificationJob(QPointer connection, Quotient::GetNotificationsJob *job, bool initialization); + void updateNotificationRules(const QString &type); Q_SIGNALS: