NotificationManager rework

Rework notifications manager to no longer be a singleton, but a component of controller.

The dependency on it for neochat room and connection is also removed.
This commit is contained in:
James Graham
2024-10-06 17:14:18 +00:00
parent 71468e453c
commit 1237e9d4bd
9 changed files with 107 additions and 91 deletions

View File

@@ -18,6 +18,7 @@
#include <Quotient/settings.h> #include <Quotient/settings.h>
#include "neochatconfig.h" #include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "notificationsmanager.h" #include "notificationsmanager.h"
#include "proxycontroller.h" #include "proxycontroller.h"
@@ -168,6 +169,10 @@ void Controller::addConnection(NeoChatConnection *c)
dropConnection(c); dropConnection(c);
}); });
connect(c, &NeoChatConnection::badgeNotificationCountChanged, this, &Controller::updateBadgeNotificationCount); connect(c, &NeoChatConnection::badgeNotificationCountChanged, this, &Controller::updateBadgeNotificationCount);
connect(c, &NeoChatConnection::syncDone, this, [this, c]() {
m_notificationsManager.handleNotifications(c);
});
connect(c, &NeoChatConnection::showInviteNotification, &m_notificationsManager, &NotificationsManager::postInviteNotification);
c->sync(); c->sync();
@@ -178,6 +183,8 @@ void Controller::dropConnection(NeoChatConnection *c)
{ {
Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection"); Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection");
c->disconnect(this);
c->disconnect(&m_notificationsManager);
m_accountRegistry.drop(c); m_accountRegistry.drop(c);
Q_EMIT connectionDropped(c); Q_EMIT connectionDropped(c);
} }
@@ -327,6 +334,7 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
if (m_connection != nullptr) { if (m_connection != nullptr) {
m_connection->disconnect(this); m_connection->disconnect(this);
m_connection->disconnect(&m_notificationsManager);
} }
m_connection = connection; m_connection = connection;
@@ -350,7 +358,7 @@ void Controller::listenForNotifications()
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit); connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) { connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
NotificationsManager::instance().postPushNotification(data); m_notificationsManager.postPushNotification(data);
timer->stop(); timer->stop();
}); });
@@ -362,6 +370,11 @@ void Controller::listenForNotifications()
#endif #endif
} }
void Controller::clearInvitationNotification(const QString &roomId)
{
m_notificationsManager.clearInvitationNotification(roomId);
}
void Controller::updateBadgeNotificationCount(NeoChatConnection *connection, int count) void Controller::updateBadgeNotificationCount(NeoChatConnection *connection, int count)
{ {
if (connection == m_connection) { if (connection == m_connection) {

View File

@@ -7,6 +7,7 @@
#include <QQmlEngine> #include <QQmlEngine>
#include "neochatconnection.h" #include "neochatconnection.h"
#include "notificationsmanager.h"
#include <Quotient/accountregistry.h> #include <Quotient/accountregistry.h>
class TrayIcon; class TrayIcon;
@@ -88,6 +89,13 @@ public:
*/ */
static void listenForNotifications(); static void listenForNotifications();
/**
* @brief Clear an existing invite notification for the given room.
*
* Nothing happens if the given room doesn't have an invite notification.
*/
Q_INVOKABLE void clearInvitationNotification(const QString &roomId);
Q_INVOKABLE QString loadFileContent(const QString &path) const; Q_INVOKABLE QString loadFileContent(const QString &path) const;
Quotient::AccountRegistry &accounts(); Quotient::AccountRegistry &accounts();
@@ -123,6 +131,8 @@ private:
QString m_endpoint; QString m_endpoint;
QStringList m_shownImages; QStringList m_shownImages;
NotificationsManager m_notificationsManager;
private Q_SLOTS: private Q_SLOTS:
void invokeLogin(); void invokeLogin();
void setQuitOnLastWindowClosed(); void setQuitOnLastWindowClosed();

View File

@@ -10,7 +10,6 @@
#include "jobs/neochatdeactivateaccountjob.h" #include "jobs/neochatdeactivateaccountjob.h"
#include "neochatconfig.h" #include "neochatconfig.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "notificationsmanager.h"
#include "spacehierarchycache.h" #include "spacehierarchycache.h"
#include <Quotient/jobs/basejob.h> #include <Quotient/jobs/basejob.h>
@@ -66,10 +65,6 @@ void NeoChatConnection::connectSignals()
}); });
connect(this, &NeoChatConnection::syncDone, this, [this] { connect(this, &NeoChatConnection::syncDone, this, [this] {
setIsOnline(true); setIsOnline(true);
connect(this, &NeoChatConnection::syncDone, this, [this]() {
NotificationsManager::instance().handleNotifications(this);
});
}); });
connect(this, &NeoChatConnection::networkError, this, [this]() { connect(this, &NeoChatConnection::networkError, this, [this]() {
setIsOnline(false); setIsOnline(false);
@@ -114,6 +109,10 @@ void NeoChatConnection::connectSignals()
Q_EMIT homeHaveHighlightNotificationsChanged(); Q_EMIT homeHaveHighlightNotificationsChanged();
}); });
}); });
connect(this, &NeoChatConnection::invitedRoom, this, [this](Quotient::Room *room) {
auto r = dynamic_cast<NeoChatRoom *>(room);
connect(r, &NeoChatRoom::showInviteNotification, this, &NeoChatConnection::showInviteNotification);
});
connect(this, &NeoChatConnection::leftRoom, this, [this](Room *room, Room *prev) { connect(this, &NeoChatConnection::leftRoom, this, [this](Room *room, Room *prev) {
Q_UNUSED(room) Q_UNUSED(room)
if (prev && prev->isDirectChat()) { if (prev && prev->isDirectChat()) {

View File

@@ -209,6 +209,11 @@ Q_SIGNALS:
*/ */
void errorOccured(const QString &error); void errorOccured(const QString &error);
/**
* @brief Request a notification be shown for an invite to this room.
*/
void showInviteNotification(NeoChatRoom *room);
private: private:
bool m_isOnline = true; bool m_isOnline = true;
void setIsOnline(bool isOnline); void setIsOnline(bool isOnline);

View File

@@ -40,9 +40,7 @@
#include "events/joinrulesevent.h" #include "events/joinrulesevent.h"
#include "events/pollevent.h" #include "events/pollevent.h"
#include "filetransferpseudojob.h" #include "filetransferpseudojob.h"
#include "jobs/neochatgetcommonroomsjob.h"
#include "neochatconfig.h" #include "neochatconfig.h"
#include "notificationsmanager.h"
#include "roomlastmessageprovider.h" #include "roomlastmessageprovider.h"
#include "spacehierarchycache.h" #include "spacehierarchycache.h"
#include "texthandler.h" #include "texthandler.h"
@@ -125,45 +123,8 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
updatePushNotificationState(QStringLiteral("m.push_rules")); updatePushNotificationState(QStringLiteral("m.push_rules"));
Q_EMIT canEncryptRoomChanged(); Q_EMIT canEncryptRoomChanged();
if (this->joinState() != JoinState::Invite) { if (this->joinState() == JoinState::Invite) {
return; Q_EMIT showInviteNotification(this);
}
auto roomMemberEvent = currentState().get<RoomMemberEvent>(localMember().id());
auto showNotification = [this, roomMemberEvent] {
QImage avatar_image;
if (roomMemberEvent && !member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) {
#if Quotient_VERSION_MINOR > 8
avatar_image = member(roomMemberEvent->senderId()).avatar(128, 128, {});
#else
avatar_image = memberAvatar(roomMemberEvent->senderId()).get(this->connection(), 128, [] {});
#endif
} else {
qWarning() << "using this room's avatar";
avatar_image = avatar(128);
}
NotificationsManager::instance().postInviteNotification(this,
displayName(),
member(roomMemberEvent->senderId()).htmlSafeDisplayName(),
avatar_image);
};
if (NeoChatConfig::rejectUnknownInvites()) {
auto job = this->connection()->callApi<NeochatGetCommonRoomsJob>(roomMemberEvent->senderId());
connect(job, &BaseJob::result, this, [this, job, showNotification] {
QJsonObject replyData = job->jsonData();
if (replyData.contains(QStringLiteral("joined"))) {
const bool inAnyOfOurRooms = !replyData[QStringLiteral("joined")].toArray().isEmpty();
if (inAnyOfOurRooms) {
showNotification();
} else {
leaveRoom();
}
}
});
} else {
showNotification();
} }
}, },
Qt::SingleShotConnection); Qt::SingleShotConnection);
@@ -946,11 +907,6 @@ QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QStri
} }
} }
void NeoChatRoom::clearInvitationNotification()
{
NotificationsManager::instance().clearInvitationNotification(id());
}
bool NeoChatRoom::hasParent() const bool NeoChatRoom::hasParent() const
{ {
return currentState().eventsOfType("m.space.parent"_ls).size() > 0; return currentState().eventsOfType("m.space.parent"_ls).size() > 0;

View File

@@ -418,8 +418,6 @@ public:
bool readOnly() const; bool readOnly() const;
Q_INVOKABLE void clearInvitationNotification();
[[nodiscard]] QString joinRule() const; [[nodiscard]] QString joinRule() const;
/** /**
@@ -664,6 +662,14 @@ Q_SIGNALS:
*/ */
void showMessage(MessageType::Type messageType, const QString &message); void showMessage(MessageType::Type messageType, const QString &message);
/**
* @brief Request a notification be shown for an invite to this room.
*
* @note This may later be blocked if there are any rules on where invites can
* come from, but this is not NeoChatRoom's responsibility.
*/
void showInviteNotification(NeoChatRoom *room);
public Q_SLOTS: public Q_SLOTS:
/** /**
* @brief Upload a file to the matrix server and post the file to the room. * @brief Upload a file to the matrix server and post the file to the room.

View File

@@ -15,6 +15,7 @@
#include <QPainter> #include <QPainter>
#include <Quotient/accountregistry.h> #include <Quotient/accountregistry.h>
#include <Quotient/csapi/pushrules.h> #include <Quotient/csapi/pushrules.h>
#include <Quotient/events/roommemberevent.h>
#include <Quotient/user.h> #include <Quotient/user.h>
#ifdef HAVE_KIO #ifdef HAVE_KIO
@@ -22,6 +23,8 @@
#endif #endif
#include "controller.h" #include "controller.h"
#include "jobs/neochatgetcommonroomsjob.h"
#include "neochatconfig.h"
#include "neochatconnection.h" #include "neochatconnection.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "roommanager.h" #include "roommanager.h"
@@ -30,12 +33,6 @@
using namespace Quotient; using namespace Quotient;
NotificationsManager &NotificationsManager::instance()
{
static NotificationsManager _instance;
return _instance;
}
NotificationsManager::NotificationsManager(QObject *parent) NotificationsManager::NotificationsManager(QObject *parent)
: QObject(parent) : QObject(parent)
{ {
@@ -249,15 +246,57 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
notification->sendEvent(); notification->sendEvent();
} }
void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom, const QString &title, const QString &sender, const QImage &icon) void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom)
{ {
QPointer room(rawRoom); QPointer room(rawRoom);
QPixmap img;
img.convertFromImage(icon); const auto roomMemberEvent = room->currentState().get<RoomMemberEvent>(room->localMember().id());
if (roomMemberEvent == nullptr) {
return;
}
if (NeoChatConfig::rejectUnknownInvites()) {
auto job = room->connection()->callApi<NeochatGetCommonRoomsJob>(roomMemberEvent->senderId());
connect(job, &BaseJob::result, this, [this, job, room] {
QJsonObject replyData = job->jsonData();
if (replyData.contains(QStringLiteral("joined"))) {
const bool inAnyOfOurRooms = !replyData[QStringLiteral("joined")].toArray().isEmpty();
if (inAnyOfOurRooms) {
doPostInviteNotification(room);
} else {
room->leaveRoom();
}
}
});
} else {
doPostInviteNotification(room);
}
}
void NotificationsManager::doPostInviteNotification(QPointer<NeoChatRoom> room)
{
const auto roomMemberEvent = room->currentState().get<RoomMemberEvent>(room->localMember().id());
if (roomMemberEvent == nullptr) {
return;
}
const auto sender = room->member(roomMemberEvent->senderId());
QImage avatar_image;
if (roomMemberEvent && !room->member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) {
#if Quotient_VERSION_MINOR > 8
avatar_image = room->member(roomMemberEvent->senderId()).avatar(128, 128, {});
#else
avatar_image = room->memberAvatar(roomMemberEvent->senderId()).get(room->connection(), 128, [] {});
#endif
} else {
qWarning() << "using this room's avatar";
avatar_image = room->avatar(128);
}
KNotification *notification = new KNotification(QStringLiteral("invite")); KNotification *notification = new KNotification(QStringLiteral("invite"));
notification->setText(i18n("%1 invited you to a room", sender)); notification->setText(i18n("%1 invited you to a room", sender.htmlSafeDisplayName()));
notification->setTitle(title); notification->setTitle(room->displayName());
notification->setPixmap(createNotificationImage(icon, nullptr)); notification->setPixmap(createNotificationImage(avatar_image, nullptr));
auto defaultAction = notification->addDefaultAction(i18n("Open this invitation in NeoChat")); auto defaultAction = notification->addDefaultAction(i18n("Open this invitation in NeoChat"));
connect(defaultAction, &KNotificationAction::activated, this, [notification, room]() { connect(defaultAction, &KNotificationAction::activated, this, [notification, room]() {
if (!room) { if (!room) {

View File

@@ -34,31 +34,14 @@ class NotificationsManager : public QObject
{ {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_SINGLETON
public: public:
static NotificationsManager &instance(); explicit NotificationsManager(QObject *parent = nullptr);
static NotificationsManager *create(QQmlEngine *engine, QJSEngine *)
{
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
return &instance();
}
/**
* @brief Display a native notification for an message.
*/
Q_INVOKABLE void postNotification(NeoChatRoom *room,
const QString &sender,
const QString &text,
const QImage &icon,
const QString &replyEventId,
bool canReply,
qint64 timestamp);
/** /**
* @brief Display a native notification for an invite. * @brief Display a native notification for an invite.
*/ */
void postInviteNotification(NeoChatRoom *room, const QString &title, const QString &sender, const QImage &icon); void postInviteNotification(NeoChatRoom *room);
/** /**
* @brief Clear an existing invite notification for the given room. * @brief Clear an existing invite notification for the given room.
@@ -78,21 +61,26 @@ public:
void handleNotifications(QPointer<NeoChatConnection> connection); void handleNotifications(QPointer<NeoChatConnection> connection);
private: private:
explicit NotificationsManager(QObject *parent = nullptr);
QHash<QString, qint64> m_initialTimestamp; QHash<QString, qint64> m_initialTimestamp;
QHash<QString, QStringList> m_oldNotifications; QHash<QString, QStringList> m_oldNotifications;
QStringList m_connActiveJob; QStringList m_connActiveJob;
QPixmap createNotificationImage(const QImage &icon, NeoChatRoom *room);
bool shouldPostNotification(QPointer<NeoChatConnection> connection, const QJsonValue &notification); bool shouldPostNotification(QPointer<NeoChatConnection> connection, const QJsonValue &notification);
void postNotification(NeoChatRoom *room,
const QString &sender,
const QString &text,
const QImage &icon,
const QString &replyEventId,
bool canReply,
qint64 timestamp);
void doPostInviteNotification(QPointer<NeoChatRoom> room);
QHash<QString, std::pair<qint64, KNotification *>> m_notifications; QHash<QString, std::pair<qint64, KNotification *>> m_notifications;
QHash<QString, QPointer<KNotification>> m_invitations; QHash<QString, QPointer<KNotification>> m_invitations;
private Q_SLOTS: private Q_SLOTS:
void processNotificationJob(QPointer<NeoChatConnection> connection, Quotient::GetNotificationsJob *job, bool initialization); void processNotificationJob(QPointer<NeoChatConnection> connection, Quotient::GetNotificationsJob *job, bool initialization);
private:
QPixmap createNotificationImage(const QImage &icon, NeoChatRoom *room);
}; };

View File

@@ -186,7 +186,7 @@ Kirigami.Page {
target: RoomManager target: RoomManager
function onCurrentRoomChanged() { function onCurrentRoomChanged() {
if (root.currentRoom && root.currentRoom.isInvite) { if (root.currentRoom && root.currentRoom.isInvite) {
root.currentRoom.clearInvitationNotification(); Controller.clearInvitationNotification(root.currentRoom.id);
} }
} }