From e15bec229545f295366bfa72dab9fe911647a495 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 29 Aug 2023 17:13:08 +0200 Subject: [PATCH] Introduce NeoChatConnection Previously, some functions that conceptually belong to the connection needed to be in the Controller, since we didn't have a place to put them. This fixes that by extending the Connection class in a similar way as we extend the Room class. --- src/CMakeLists.txt | 2 + src/controller.cpp | 176 +++---------------------- src/controller.h | 54 ++------ src/login.cpp | 4 +- src/login.h | 7 +- src/main.cpp | 3 +- src/neochatconnection.cpp | 134 +++++++++++++++++++ src/neochatconnection.h | 51 +++++++ src/notificationsmanager.cpp | 10 +- src/notificationsmanager.h | 12 +- src/qml/Dialog/ConfirmLogout.qml | 2 +- src/qml/Page/RoomList/UserInfo.qml | 2 +- src/qml/Settings/AccountEditorPage.qml | 12 +- src/qml/Settings/AccountsPage.qml | 14 +- src/registration.cpp | 2 +- 15 files changed, 250 insertions(+), 235 deletions(-) create mode 100644 src/neochatconnection.cpp create mode 100644 src/neochatconnection.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1012856a8..73693849c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -124,6 +124,8 @@ add_library(neochat STATIC pollhandler.cpp utils.h registration.cpp + neochatconnection.cpp + neochatconnection.h ) ecm_qt_declare_logging_category(neochat diff --git a/src/controller.cpp b/src/controller.cpp index 56a3f18b6..eb334d291 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -33,10 +32,8 @@ #include #include -#include #include #include -#include #include #include #include @@ -105,8 +102,8 @@ Controller::Controller(QObject *parent) static int oldAccountCount = 0; connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() { if (m_accountRegistry.size() > oldAccountCount) { - auto connection = m_accountRegistry.accounts()[m_accountRegistry.size() - 1]; - connect(connection, &Connection::syncDone, this, [connection]() { + auto connection = dynamic_cast(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]); + connect(connection, &NeoChatConnection::syncDone, this, [connection]() { NotificationsManager::instance().handleNotifications(connection); }); } @@ -141,38 +138,7 @@ void Controller::toggleWindow() } } -void Controller::logout(Connection *conn, bool serverSideLogout) -{ - if (!conn) { - qCritical() << "Attempt to logout null connection"; - return; - } - - SettingsGroup("Accounts"_ls).remove(conn->userId()); - - QKeychain::DeletePasswordJob job(qAppName()); - job.setAutoDelete(true); - job.setKey(conn->userId()); - QEventLoop loop; - QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); - job.start(); - loop.exec(); - - if (m_accountRegistry.count() > 1) { - // Only set the connection if the the account being logged out is currently active - if (conn == activeConnection()) { - setActiveConnection(m_accountRegistry.accounts()[0]); - } - } else { - setActiveConnection(nullptr); - } - if (!serverSideLogout) { - return; - } - conn->logout(); -} - -void Controller::addConnection(Connection *c) +void Controller::addConnection(NeoChatConnection *c) { Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection"); @@ -180,17 +146,17 @@ void Controller::addConnection(Connection *c) c->setLazyLoading(true); - connect(c, &Connection::syncDone, this, [this, c] { + connect(c, &NeoChatConnection::syncDone, this, [this, c] { Q_EMIT syncDone(); c->sync(30000); c->saveState(); }); - connect(c, &Connection::loggedOut, this, [this, c] { + connect(c, &NeoChatConnection::loggedOut, this, [this, c] { dropConnection(c); }); - connect(c, &Connection::requestFailed, this, [this](BaseJob *job) { + connect(c, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) { if (job->error() == BaseJob::UserConsentRequired) { Q_EMIT userConsentRequired(job->errorUrl()); } @@ -202,7 +168,7 @@ void Controller::addConnection(Connection *c) Q_EMIT accountCountChanged(); } -void Controller::dropConnection(Connection *c) +void Controller::dropConnection(NeoChatConnection *c) { Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection"); @@ -231,19 +197,19 @@ void Controller::invokeLogin() return; } - auto connection = new Connection(account.homeserver()); - connect(connection, &Connection::connected, this, [this, connection, id] { + auto connection = new NeoChatConnection(account.homeserver()); + connect(connection, &NeoChatConnection::connected, this, [this, connection, id] { connection->loadState(); addConnection(connection); if (connection->userId() == id) { setActiveConnection(connection); - connectSingleShot(connection, &Connection::syncDone, this, &Controller::initiated); + connectSingleShot(connection, &NeoChatConnection::syncDone, this, &Controller::initiated); } }); - connect(connection, &Connection::loginError, this, [this, connection](const QString &error, const QString &) { + connect(connection, &NeoChatConnection::loginError, this, [this, connection](const QString &error, const QString &) { if (error == "Unrecognised access token"_ls) { Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked")); - logout(connection, false); + connection->logout(false); } else if (error == "Connection closed"_ls) { Q_EMIT errorOccured(i18n("Login Failed: %1", error)); // Failed due to network connection issue. This might happen when the homeserver is @@ -251,11 +217,11 @@ void Controller::invokeLogin() // connect to the homeserver. In this case, we don't want to do logout(). } else { Q_EMIT errorOccured(i18n("Login Failed: %1", error)); - logout(connection, true); + connection->logout(true); } Q_EMIT initiated(); }); - connect(connection, &Connection::networkError, this, [this](const QString &error, const QString &, int, int) { + connect(connection, &NeoChatConnection::networkError, this, [this](const QString &error, const QString &, int, int) { Q_EMIT errorOccured(i18n("Network Error: %1", error)); }); connection->assumeIdentity(account.userId(), accessToken); @@ -321,22 +287,6 @@ bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const return true; } -void Controller::changeAvatar(Connection *conn, const QUrl &localFile) -{ - auto job = conn->uploadFile(localFile.toLocalFile()); - connect(job, &BaseJob::success, this, [conn, job] { - conn->callApi(conn->userId(), job->contentUri()); - }); -} - -void Controller::markAllMessagesAsRead(Connection *conn) -{ - const auto rooms = conn->allRooms(); - for (auto room : rooms) { - room->markAllMessagesAsRead(); - } -} - bool Controller::supportSystemTray() const { #ifdef Q_OS_ANDROID @@ -347,49 +297,6 @@ bool Controller::supportSystemTray() const #endif } -void Controller::changePassword(Connection *connection, const QString ¤tPassword, const QString &newPassword) -{ - NeochatChangePasswordJob *job = connection->callApi(newPassword, false); - connect(job, &BaseJob::result, this, [this, job, currentPassword, newPassword, connection] { - if (job->error() == 103) { - QJsonObject replyData = job->jsonData(); - QJsonObject authData; - authData["session"_ls] = replyData["session"_ls]; - authData["password"_ls] = currentPassword; - authData["type"_ls] = "m.login.password"_ls; - authData["user"_ls] = connection->user()->id(); - QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, connection->user()->id()}}; - authData["identifier"_ls] = identifier; - NeochatChangePasswordJob *innerJob = connection->callApi(newPassword, false, authData); - connect(innerJob, &BaseJob::success, this, [this]() { - Q_EMIT passwordStatus(PasswordStatus::Success); - }); - connect(innerJob, &BaseJob::failure, this, [innerJob, this]() { - if (innerJob->jsonData()["errcode"_ls] == "M_FORBIDDEN"_ls) { - Q_EMIT passwordStatus(PasswordStatus::Wrong); - } else { - Q_EMIT passwordStatus(PasswordStatus::Other); - } - }); - } - }); -} - -bool Controller::setAvatar(Connection *connection, const QUrl &avatarSource) -{ - User *localUser = connection->user(); - QString decoded = avatarSource.path(); - if (decoded.isEmpty()) { - connection->callApi(localUser->id(), avatarSource); - return true; - } - if (QImageReader(decoded).read().isNull()) { - return false; - } else { - return localUser->setAvatar(decoded); - } -} - NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable &auth) : BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), "/_matrix/client/r0/account/password") { @@ -425,7 +332,7 @@ void Controller::setQuitOnLastWindowClosed() #endif } -Connection *Controller::activeConnection() const +NeoChatConnection *Controller::activeConnection() const { if (m_connection.isNull()) { return nullptr; @@ -433,49 +340,43 @@ Connection *Controller::activeConnection() const return m_connection; } -void Controller::setActiveConnection(Connection *connection) +void Controller::setActiveConnection(NeoChatConnection *connection) { if (connection == m_connection) { return; } if (m_connection != nullptr) { - disconnect(m_connection, &Connection::syncError, this, nullptr); - disconnect(m_connection, &Connection::accountDataChanged, this, nullptr); + disconnect(m_connection, &NeoChatConnection::syncError, this, nullptr); + disconnect(m_connection, &NeoChatConnection::accountDataChanged, this, nullptr); } m_connection = connection; if (connection != nullptr) { NeoChatConfig::self()->setActiveConnection(connection->userId()); - connect(connection, &Connection::networkError, this, [this]() { + connect(connection, &NeoChatConnection::networkError, this, [this]() { if (!m_isOnline) { return; } m_isOnline = false; Q_EMIT isOnlineChanged(false); }); - connect(connection, &Connection::syncDone, this, [this] { + connect(connection, &NeoChatConnection::syncDone, this, [this] { if (m_isOnline) { return; } m_isOnline = true; Q_EMIT isOnlineChanged(true); }); - connect(connection, &Connection::requestFailed, this, [](BaseJob *job) { + connect(connection, &NeoChatConnection::requestFailed, this, [](BaseJob *job) { if (dynamic_cast(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) { RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support.")); } }); - connect(connection, &Connection::accountDataChanged, this, [this](const QString &type) { - if (type == QLatin1String("org.kde.neochat.account_label")) { - Q_EMIT activeAccountLabelChanged(); - } - }); } else { NeoChatConfig::self()->setActiveConnection(QString()); } NeoChatConfig::self()->save(); Q_EMIT activeConnectionChanged(); Q_EMIT activeConnectionIndexChanged(); - Q_EMIT activeAccountLabelChanged(); } PushRuleModel *Controller::pushRuleModel() const @@ -646,41 +547,6 @@ bool Controller::isFlatpak() const #endif } -void Controller::setActiveAccountLabel(const QString &label) -{ - if (!m_connection) { - return; - } - QJsonObject json{ - {"account_label"_ls, label}, - }; - m_connection->setAccountData("org.kde.neochat.account_label"_ls, json); -} - -QString Controller::activeAccountLabel() const -{ - if (!m_connection) { - return {}; - } - return m_connection->accountDataJson("org.kde.neochat.account_label"_ls)["account_label"_ls].toString(); -} - -QVariantList Controller::getSupportedRoomVersions(Quotient::Connection *connection) -{ - auto roomVersions = connection->availableRoomVersions(); - - QVariantList supportedRoomVersions; - for (const Quotient::Connection::SupportedRoomVersion &v : roomVersions) { - QVariantMap roomVersionMap; - roomVersionMap.insert("id"_ls, v.id); - roomVersionMap.insert("status"_ls, v.status); - roomVersionMap.insert("isStable"_ls, v.isStable()); - supportedRoomVersions.append(roomVersionMap); - } - - return supportedRoomVersions; -} - AccountRegistry &Controller::accounts() { return m_accountRegistry; diff --git a/src/controller.h b/src/controller.h index 49c2b3d8c..1d0f3ebe3 100644 --- a/src/controller.h +++ b/src/controller.h @@ -9,6 +9,7 @@ #include +#include "neochatconnection.h" #include #include #include @@ -20,7 +21,6 @@ class QQuickTextDocument; namespace Quotient { -class Connection; class Room; class User; } @@ -50,7 +50,7 @@ class Controller : public QObject /** * @brief The current connection for the rest of NeoChat to use. */ - Q_PROPERTY(Quotient::Connection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged) + Q_PROPERTY(NeoChatConnection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged) /** * @brief The PushRuleModel that has the active connection's push rules. @@ -62,16 +62,6 @@ class Controller : public QObject */ Q_PROPERTY(int activeConnectionIndex READ activeConnectionIndex NOTIFY activeConnectionIndexChanged) - /** - * @brief The account label for the active account. - * - * Account labels are a concept specific to NeoChat, allowing accounts to be - * labelled, e.g. for "Work", "Private", etc. - * - * Set to an empty string to remove the label. - */ - Q_PROPERTY(QString activeAccountLabel READ activeAccountLabel WRITE setActiveAccountLabel NOTIFY activeAccountLabelChanged) - /** * @brief Whether the OS NeoChat is running on supports sytem tray icons. */ @@ -109,46 +99,28 @@ public: [[nodiscard]] int accountCount() const; - void setActiveConnection(Quotient::Connection *connection); - [[nodiscard]] Quotient::Connection *activeConnection() const; + void setActiveConnection(NeoChatConnection *connection); + [[nodiscard]] NeoChatConnection *activeConnection() const; [[nodiscard]] PushRuleModel *pushRuleModel() const; /** * @brief Add a new connection to the account registry. */ - void addConnection(Quotient::Connection *c); + void addConnection(NeoChatConnection *c); /** * @brief Drop a connection from the account registry. */ - void dropConnection(Quotient::Connection *c); + void dropConnection(NeoChatConnection *c); int activeConnectionIndex() const; - [[nodiscard]] QString activeAccountLabel() const; - void setActiveAccountLabel(const QString &label); - /** * @brief Save an access token to the keychain for the given account. */ bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken); - /** - * @brief Change the password for an account. - * - * The function emits a passwordStatus signal with a PasswordStatus value when - * complete. - * - * @sa PasswordStatus, passwordStatus - */ - Q_INVOKABLE void changePassword(Quotient::Connection *connection, const QString ¤tPassword, const QString &newPassword); - - /** - * @brief Change the avatar for an account. - */ - Q_INVOKABLE bool setAvatar(Quotient::Connection *connection, const QUrl &avatarSource); - /** * @brief Create new room for a group chat. */ @@ -210,14 +182,12 @@ public: */ Q_INVOKABLE void forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item); - Q_INVOKABLE QVariantList getSupportedRoomVersions(Quotient::Connection *connection); - Quotient::AccountRegistry &accounts(); private: explicit Controller(QObject *parent = nullptr); - QPointer m_connection; + QPointer m_connection; TrayIcon *m_trayIcon = nullptr; QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account); @@ -244,8 +214,8 @@ Q_SIGNALS: /// Error occurred because of server or bug in NeoChat void globalErrorOccured(QString error, QString detail); void syncDone(); - void connectionAdded(Quotient::Connection *_t1); - void connectionDropped(Quotient::Connection *_t1); + void connectionAdded(NeoChatConnection *connection); + void connectionDropped(NeoChatConnection *connection); void accountCountChanged(); void initiated(); void notificationClicked(const QString &_t1, const QString &_t2); @@ -256,18 +226,14 @@ Q_SIGNALS: void userConsentRequired(QUrl url); void testConnectionResult(const QString &connection, bool usable); void isOnlineChanged(bool isOnline); - void keyVerificationRequest(int timeLeft, Quotient::Connection *connection, const QString &transactionId, const QString &deviceId); + void keyVerificationRequest(int timeLeft, NeoChatConnection *connection, const QString &transactionId, const QString &deviceId); void keyVerificationStart(); void keyVerificationAccept(const QString &commitment); void keyVerificationKey(const QString &sas); void activeConnectionIndexChanged(); void roomAdded(NeoChatRoom *room); - void activeAccountLabelChanged(); public Q_SLOTS: - void logout(Quotient::Connection *conn, bool serverSideLogout); - void changeAvatar(Quotient::Connection *conn, const QUrl &localFile); - static void markAllMessagesAsRead(Quotient::Connection *conn); void saveWindowGeometry(); }; diff --git a/src/login.cpp b/src/login.cpp index 84d0ee7a4..643fa57f8 100644 --- a/src/login.cpp +++ b/src/login.cpp @@ -22,7 +22,7 @@ Login::Login(QObject *parent) void Login::init() { m_homeserverReachable = false; - m_connection = new Connection(); + m_connection = new NeoChatConnection(); m_matrixId = QString(); m_password = QString(); m_deviceName = QStringLiteral("NeoChat %1 %2 %3 %4") @@ -51,7 +51,7 @@ void Login::init() m_testing = true; Q_EMIT testingChanged(); if (!m_connection) { - m_connection = new Connection(); + m_connection = new NeoChatConnection(); } m_connection->resolveServer(m_matrixId); connectSingleShot(m_connection, &Connection::loginFlowsChanged, this, [this]() { diff --git a/src/login.h b/src/login.h index d34664284..4916022b4 100644 --- a/src/login.h +++ b/src/login.h @@ -6,10 +6,7 @@ #include #include -namespace Quotient -{ -class Connection; -} +class NeoChatConnection; /** * @class Login @@ -135,7 +132,7 @@ private: QString m_deviceName; bool m_supportsSso = false; bool m_supportsPassword = false; - Quotient::Connection *m_connection = nullptr; + NeoChatConnection *m_connection = nullptr; QUrl m_ssoUrl; bool m_testing = false; bool m_isLoggingIn = false; diff --git a/src/main.cpp b/src/main.cpp index 49785332d..e495e7806 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -80,6 +80,7 @@ #include "models/userlistmodel.h" #include "models/webshortcutmodel.h" #include "neochatconfig.h" +#include "neochatconnection.h" #include "neochatroom.h" #include "notificationsmanager.h" #include "pollhandler.h" @@ -281,12 +282,12 @@ int main(int argc, char *argv[]) qmlRegisterUncreatableType("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM"_ls); qmlRegisterUncreatableType("org.kde.neochat", 1, 0, "User", {}); qmlRegisterUncreatableType("org.kde.neochat", 1, 0, "NeoChatRoom", {}); + qmlRegisterUncreatableType("org.kde.neochat", 1, 0, "NeoChatConnection", {}); qRegisterMetaType("User*"); qRegisterMetaType("const User*"); qRegisterMetaType("const Quotient::User*"); qRegisterMetaType("Room*"); - qRegisterMetaType("Connection*"); qRegisterMetaType("MessageEventType"); qRegisterMetaType("NeoChatRoom*"); qRegisterMetaType("User*"); diff --git a/src/neochatconnection.cpp b/src/neochatconnection.cpp new file mode 100644 index 000000000..41f11e9c6 --- /dev/null +++ b/src/neochatconnection.cpp @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: 2023 Tobias Fella +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "neochatconnection.h" + +#include + +#include "controller.h" + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +#include +#else +#include +#endif + +#include +#include +#include +#include + +using namespace Quotient; + +NeoChatConnection::NeoChatConnection(QObject *parent) + : Connection(parent) +{ + connect(this, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) { + if (type == QLatin1String("org.kde.neochat.account_label")) { + Q_EMIT labelChanged(); + } + }); +} + +NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent) + : Connection(server, parent) +{ + connect(this, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) { + if (type == QLatin1String("org.kde.neochat.account_label")) { + Q_EMIT labelChanged(); + } + }); +} + +void NeoChatConnection::logout(bool serverSideLogout) +{ + SettingsGroup(QStringLiteral("Accounts")).remove(userId()); + + QKeychain::DeletePasswordJob job(qAppName()); + job.setAutoDelete(true); + job.setKey(userId()); + QEventLoop loop; + QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); + job.start(); + loop.exec(); + + if (Controller::instance().accounts().count() > 1) { + // Only set the connection if the the account being logged out is currently active + if (this == Controller::instance().activeConnection()) { + Controller::instance().setActiveConnection(dynamic_cast(Controller::instance().accounts().accounts()[0])); + } + } else { + Controller::instance().setActiveConnection(nullptr); + } + if (!serverSideLogout) { + return; + } + Connection::logout(); +} + +bool NeoChatConnection::setAvatar(const QUrl &avatarSource) +{ + QString decoded = avatarSource.path(); + if (decoded.isEmpty()) { + callApi(user()->id(), avatarSource); + return true; + } + if (QImageReader(decoded).read().isNull()) { + return false; + } else { + return user()->setAvatar(decoded); + } +} + +QVariantList NeoChatConnection::getSupportedRoomVersions() const +{ + const auto &roomVersions = availableRoomVersions(); + QVariantList supportedRoomVersions; + for (const auto &v : roomVersions) { + QVariantMap roomVersionMap; + roomVersionMap.insert("id"_ls, v.id); + roomVersionMap.insert("status"_ls, v.status); + roomVersionMap.insert("isStable"_ls, v.isStable()); + supportedRoomVersions.append(roomVersionMap); + } + return supportedRoomVersions; +} + +void NeoChatConnection::changePassword(const QString ¤tPassword, const QString &newPassword) +{ + auto job = callApi(newPassword, false); + connect(job, &BaseJob::result, this, [this, job, currentPassword, newPassword] { + if (job->error() == 103) { + QJsonObject replyData = job->jsonData(); + QJsonObject authData; + authData["session"_ls] = replyData["session"_ls]; + authData["password"_ls] = currentPassword; + authData["type"_ls] = "m.login.password"_ls; + authData["user"_ls] = user()->id(); + QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, user()->id()}}; + authData["identifier"_ls] = identifier; + NeochatChangePasswordJob *innerJob = callApi(newPassword, false, authData); + connect(innerJob, &BaseJob::success, this, []() { + Q_EMIT Controller::instance().passwordStatus(Controller::PasswordStatus::Success); + }); + connect(innerJob, &BaseJob::failure, this, [innerJob]() { + Q_EMIT Controller::instance().passwordStatus(innerJob->jsonData()["errcode"_ls] == "M_FORBIDDEN"_ls ? Controller::PasswordStatus::Wrong + : Controller::PasswordStatus::Other); + }); + } + }); +} + +void NeoChatConnection::setLabel(const QString &label) +{ + QJsonObject json{ + {"account_label"_ls, label}, + }; + setAccountData("org.kde.neochat.account_label"_ls, json); + Q_EMIT labelChanged(); +} + +QString NeoChatConnection::label() const +{ + return accountDataJson("org.kde.neochat.account_label"_ls)["account_label"_ls].toString(); +} diff --git a/src/neochatconnection.h b/src/neochatconnection.h new file mode 100644 index 000000000..207ec1a7e --- /dev/null +++ b/src/neochatconnection.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2023 Tobias Fella +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include + +class NeoChatConnection : public Quotient::Connection +{ + Q_OBJECT + + /** + * @brief The account label for this account. + * + * Account labels are a concept specific to NeoChat, allowing accounts to be + * labelled, e.g. for "Work", "Private", etc. + * + * Set to an empty string to remove the label. + */ + Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged) + +public: + NeoChatConnection(QObject *parent = nullptr); + NeoChatConnection(const QUrl &server, QObject *parent = nullptr); + + Q_INVOKABLE void logout(bool serverSideLogout); + Q_INVOKABLE QVariantList getSupportedRoomVersions() const; + + /** + * @brief Change the password for an account. + * + * The function emits a passwordStatus signal with a PasswordStatus value when + * complete. + * + * @sa PasswordStatus, passwordStatus + */ + Q_INVOKABLE void changePassword(const QString ¤tPassword, const QString &newPassword); + + /** + * @brief Change the avatar for an account. + */ + Q_INVOKABLE bool setAvatar(const QUrl &avatarSource); + + [[nodiscard]] QString label() const; + void setLabel(const QString &label); + +Q_SIGNALS: + void labelChanged(); +}; diff --git a/src/notificationsmanager.cpp b/src/notificationsmanager.cpp index 0e2fcbc6e..454db3727 100644 --- a/src/notificationsmanager.cpp +++ b/src/notificationsmanager.cpp @@ -13,12 +13,12 @@ #include #include -#include #include #include #include "controller.h" #include "neochatconfig.h" +#include "neochatconnection.h" #include "neochatroom.h" #include "roommanager.h" #include "texthandler.h" @@ -37,7 +37,7 @@ NotificationsManager::NotificationsManager(QObject *parent) { } -void NotificationsManager::handleNotifications(QPointer connection) +void NotificationsManager::handleNotifications(QPointer connection) { if (!m_connActiveJob.contains(connection->user()->id())) { auto job = connection->callApi(); @@ -49,7 +49,7 @@ void NotificationsManager::handleNotifications(QPointer connection) } } -void NotificationsManager::processNotificationJob(QPointer connection, Quotient::GetNotificationsJob *job, bool initialization) +void NotificationsManager::processNotificationJob(QPointer connection, Quotient::GetNotificationsJob *job, bool initialization) { if (job == nullptr) { return; @@ -145,7 +145,7 @@ void NotificationsManager::processNotificationJob(QPointer } } -bool NotificationsManager::shouldPostNotification(QPointer connection, const QJsonValue ¬ification) +bool NotificationsManager::shouldPostNotification(QPointer connection, const QJsonValue ¬ification) { if (connection == nullptr) { return false; @@ -211,7 +211,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room, return; } if (room->localUser()->id() != Controller::instance().activeConnection()->userId()) { - Controller::instance().setActiveConnection(Controller::instance().accounts().get(room->localUser()->id())); + Controller::instance().setActiveConnection(dynamic_cast(Controller::instance().accounts().get(room->localUser()->id()))); } RoomManager::instance().enterRoom(room); }); diff --git a/src/notificationsmanager.h b/src/notificationsmanager.h index ac983ac7c..1f6fc0e32 100644 --- a/src/notificationsmanager.h +++ b/src/notificationsmanager.h @@ -12,11 +12,7 @@ #include #include -namespace Quotient -{ -class Connection; -} - +class NeoChatConnection; class KNotification; class NeoChatRoom; @@ -80,7 +76,7 @@ public: /** * @brief Handle the notifications for the given connection. */ - void handleNotifications(QPointer connection); + void handleNotifications(QPointer connection); private: explicit NotificationsManager(QObject *parent = nullptr); @@ -90,13 +86,13 @@ private: QStringList m_connActiveJob; - bool shouldPostNotification(QPointer connection, const QJsonValue ¬ification); + bool shouldPostNotification(QPointer connection, const QJsonValue ¬ification); QHash m_notifications; QHash> m_invitations; private Q_SLOTS: - void processNotificationJob(QPointer connection, Quotient::GetNotificationsJob *job, bool initialization); + void processNotificationJob(QPointer connection, Quotient::GetNotificationsJob *job, bool initialization); private: QPixmap createNotificationImage(const QImage &icon, NeoChatRoom *room); diff --git a/src/qml/Dialog/ConfirmLogout.qml b/src/qml/Dialog/ConfirmLogout.qml index cdcba4bc8..15eb1c7e8 100644 --- a/src/qml/Dialog/ConfirmLogout.qml +++ b/src/qml/Dialog/ConfirmLogout.qml @@ -37,7 +37,7 @@ QQC2.Dialog { text: i18n("Sign out") QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole onClicked: { - Controller.logout(root.connection, true); + root.connection.logout(true); root.close(); root.accepted(); } diff --git a/src/qml/Page/RoomList/UserInfo.qml b/src/qml/Page/RoomList/UserInfo.qml index bfe8722b3..75832fe00 100644 --- a/src/qml/Page/RoomList/UserInfo.qml +++ b/src/qml/Page/RoomList/UserInfo.qml @@ -193,7 +193,7 @@ QQC2.ToolBar { Layout.fillWidth: true } QQC2.Label { - text: (Controller.activeAccountLabel.length > 0 ? (Controller.activeAccountLabel + " ") : "") + Controller.activeConnection.localUser.id + text: (Controller.activeConnection.label.length > 0 ? (Controller.activeConnection.label + " ") : "") + Controller.activeConnection.localUser.id font.pointSize: displayNameLabel.font.pointSize * 0.8 opacity: 0.7 textFormat: Text.PlainText diff --git a/src/qml/Settings/AccountEditorPage.qml b/src/qml/Settings/AccountEditorPage.qml index 4671dc0bd..519d7e473 100644 --- a/src/qml/Settings/AccountEditorPage.qml +++ b/src/qml/Settings/AccountEditorPage.qml @@ -16,7 +16,7 @@ import org.kde.neochat 1.0 Kirigami.ScrollablePage { id: root title: i18n("Edit Account") - property var connection + property NeoChatConnection connection readonly property bool compact: width > Kirigami.Units.gridUnit * 30 ? 2 : 1 @@ -114,7 +114,7 @@ Kirigami.ScrollablePage { MobileForm.FormTextFieldDelegate { id: accountLabel label: i18n("Label:") - text: root.connection ? Controller.activeAccountLabel : "" + text: root.connection ? root.connection.label : "" } MobileForm.FormDelegateSeparator {} MobileForm.AbstractFormDelegate { @@ -130,14 +130,14 @@ Kirigami.ScrollablePage { Layout.bottomMargin: Kirigami.Units.smallSpacing Layout.topMargin: Kirigami.Units.smallSpacing onClicked: { - if (!Controller.setAvatar(root.connection, avatar.source)) { + if (!root.connection.setAvatar(avatar.source)) { showPassiveNotification("The Avatar could not be set"); } if (root.connection.localUser.displayName !== name.text) { root.connection.localUser.rename(name.text); } - if (Controller.activeAccountLabel !== accountLabel.text) { - Controller.activeAccountLabel = accountLabel.text; + if (root.connection.label !== accountLabel.text) { + root.connection.label = accountLabel.text; } } } @@ -201,7 +201,7 @@ Kirigami.ScrollablePage { enabled: currentPassword.text.length > 0 && newPassword.text.length > 0 && confirmPassword.text.length > 0 onClicked: { if (newPassword.text === confirmPassword.text) { - Controller.changePassword(root.connection, currentPassword.text, newPassword.text); + root.connection.changePassword(currentPassword.text, newPassword.text); } else { showPassiveNotification(i18n("Passwords do not match")); } diff --git a/src/qml/Settings/AccountsPage.qml b/src/qml/Settings/AccountsPage.qml index a7a2bd9b8..a613267c7 100644 --- a/src/qml/Settings/AccountsPage.qml +++ b/src/qml/Settings/AccountsPage.qml @@ -35,17 +35,19 @@ Kirigami.ScrollablePage { Repeater { model: AccountRegistry delegate: MobileForm.AbstractFormDelegate { + id: accountDelegate + required property NeoChatConnection connection Layout.fillWidth: true onClicked: pageStack.layers.push("qrc:/AccountEditorPage.qml", { - connection: model.connection + connection: accountDelegate.connection }, { title: i18n("Account editor") }) contentItem: RowLayout { KirigamiComponents.Avatar { - name: model.connection.localUser.displayName - source: model.connection.localUser.avatarMediaId ? ("image://mxc/" + model.connection.localUser.avatarMediaId) : "" + name: accountDelegate.connection.localUser.displayName + source: accountDelegate.connection.localUser.avatarMediaId ? ("image://mxc/" + accountDelegate.connection.localUser.avatarMediaId) : "" Layout.rightMargin: Kirigami.Units.largeSpacing implicitWidth: Kirigami.Units.iconSizes.medium @@ -58,7 +60,7 @@ Kirigami.ScrollablePage { QQC2.Label { Layout.fillWidth: true - text: model.connection.localUser.displayName + text: accountDelegate.connection.localUser.displayName textFormat: Text.PlainText elide: Text.ElideRight wrapMode: Text.Wrap @@ -68,7 +70,7 @@ Kirigami.ScrollablePage { QQC2.Label { Layout.fillWidth: true - text: model.connection.localUserId + text: accountDelegate.connection.localUserId color: Kirigami.Theme.disabledTextColor font: Kirigami.Theme.smallFont elide: Text.ElideRight @@ -78,7 +80,7 @@ Kirigami.ScrollablePage { QQC2.ToolButton { text: i18n("Logout") icon.name: "im-kick-user" - onClicked: confirmLogoutDialogComponent.createObject(QQC2.ApplicationWindow.overlay).open() + onClicked: confirmLogoutDialogComponent.createObject(applicationWindow().overlay).open() } Component { diff --git a/src/registration.cpp b/src/registration.cpp index 50a592de7..8d9400f02 100644 --- a/src/registration.cpp +++ b/src/registration.cpp @@ -89,7 +89,7 @@ void Registration::registerAccount() connect(job, &BaseJob::result, this, [=]() { if (job->status() == BaseJob::Success) { setNextStep("loading"_ls); - auto connection = new Connection(this); + auto connection = new NeoChatConnection(this); auto matrixId = "@%1:%2"_ls.arg(m_username, m_homeserver); connection->resolveServer(matrixId);