diff --git a/qml/AccountsPage.qml b/qml/AccountsPage.qml new file mode 100644 index 000000000..b9ef2fa88 --- /dev/null +++ b/qml/AccountsPage.qml @@ -0,0 +1,134 @@ +/** + * SPDX-FileCopyrightText: Tobias Fella + * + * SPDX-LicenseIdentifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.14 +import QtQuick.Controls 2.14 as Controls +import QtQuick.Layouts 1.14 + +import org.kde.kirigami 2.12 as Kirigami + +import Spectral 0.1 + +Kirigami.ScrollablePage { + title: i18n("Accounts") + + ListView { + model: AccountListModel { } + delegate: Kirigami.SwipeListItem { + leftPadding: 0 + rightPadding: 0 + Kirigami.BasicListItem { + anchors.top: parent.top + anchors.bottom: parent.bottom + + text: model.user.defaultName + subtitle: model.user.id + icon: model.connection.user.avatarMediaId ? "image://mxc/" + model.connection.user.avatarMediaId : "im-user" + + onClicked: { + Controller.connection = model.connection + pageStack.layers.pop() + } + } + actions: [ + Kirigami.Action { + text: i18n("Edit this account") + iconName: "document-edit" + onTriggered: { + userEditSheet.connection = model.connection + userEditSheet.open() + } + }, + Kirigami.Action { + text: i18n("Logout") + iconName: "im-kick-user" + onTriggered: { + Controller.logout(model.connection) + if(Controller.accountCount === 1) + pageStack.layers.pop() + } + } + ] + } + } + + Connections { + target: Controller + function onConnectionAdded() { + if (pageStack.layers.depth > 2) + pageStack.layers.pop() + } + function onPasswordStatus(status) { + if(status == Controller.Success) + showPassiveNotification(i18n("Password changed successfully")) + else if(status == Controller.Wrong) + showPassiveNotification(i18n("Wrong password entered")) + else + showPassiveNotification(i18n("Unknown problem while trying to change password")) + } + } + + actions.main: Kirigami.Action { + text: i18n("Add an account") + iconName: "list-add-user" + onTriggered: pageStack.layers.push("qrc:/qml/LoginPage.qml") + } + + Kirigami.OverlaySheet { + id: userEditSheet + + property var connection + + header: Kirigami.Heading { + text: i18n("Edit Account") + } + + Kirigami.FormLayout { + anchors.top: passwordsMessage.bottom + Controls.TextField { + id: name + text: userEditSheet.connection.localUser.defaultName + Kirigami.FormData.label: i18n("Name:") + } + Controls.TextField { + id: currentPassword + Kirigami.FormData.label: i18n("Current Password:") + echoMode: TextInput.Password + } + Controls.TextField { + id: newPassword + Kirigami.FormData.label: i18n("New Password:") + echoMode: TextInput.Password + + } + Controls.TextField { + id: confirmPassword + Kirigami.FormData.label: i18n("Confirm new Password:") + echoMode: TextInput.Password + } + + Controls.Button { + text: i18n("Save") + onClicked: { + if(userEditSheet.connection.localUser.defaultName !== name.text) + userEditSheet.connection.localUser.user.defaultName = name.text + if(currentPassword.text !== "" && newPassword.text !== "" && confirmPassword.text !== "") { + if(newPassword.text === confirmPassword.text) { + Controller.changePassword(userEditSheet.connection, currentPassword.text, newPassword.text) + } else { + showPassiveNotification(i18n("Passwords do not match")) + return + } + } + userEditSheet.close() + currentPassword.text = "" + newPassword.text = "" + confirmPassword.text = "" + } + } + } + } +} diff --git a/qml/main.qml b/qml/main.qml index b11161a74..216f9e84d 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -33,6 +33,12 @@ Kirigami.ApplicationWindow { iconName: "help-about" onTriggered: pageStack.layers.push(aboutPage) enabled: pageStack.layers.currentItem.title !== i18n("About") + }, + Kirigami.Action { + text: i18n("Accounts") + iconName: "im-user" + onTriggered: pageStack.layers.push("qrc:/qml/AccountsPage.qml") + enabled: pageStack.layers.currentItem.title !== i18n("Accounts") } ] } @@ -82,6 +88,12 @@ Kirigami.ApplicationWindow { pageStack.replace(roomListComponent); } } + + onConnectionDropped: { + if(Controller.accountCount === 0) + pageStack.replace("qrc:/qml/LoginPage.qml") + } + onErrorOccured: showPassiveNotification(error + ": " + detail) } diff --git a/res.qrc b/res.qrc index f982bfa12..a985eac07 100644 --- a/res.qrc +++ b/res.qrc @@ -7,6 +7,7 @@ qml/RoomPage.qml qml/ChatTextInput.qml qml/RoomListContextMenu.qml + qml/AccountsPage.qml imports/Spectral/Component/Emoji/EmojiPicker.qml imports/Spectral/Component/Emoji/qmldir imports/Spectral/Component/Timeline/MessageDelegate.qml diff --git a/src/controller.cpp b/src/controller.cpp index f98262389..d02eac570 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -34,6 +34,7 @@ #include "csapi/joining.h" #include "csapi/logout.h" #include "csapi/profile.h" +#include "csapi/registration.h" #include "events/eventcontent.h" #include "events/roommessageevent.h" #include "settings.h" @@ -173,6 +174,8 @@ void Controller::logout(Connection *conn) Q_EMIT conn->loggedOut(); if (!m_connections.isEmpty()) setConnection(m_connections[0]); + else + setConnection(nullptr); }); connect(logoutJob, &LogoutJob::failure, this, [=] { Q_EMIT errorOccured("Server-side Logout Failed", logoutJob->errorString()); @@ -424,3 +427,44 @@ KAboutData Controller::aboutData() const { return m_aboutData; } + +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"] = replyData["session"]; + authData["password"] = currentPassword; + authData["type"] = "m.login.password"; + authData["user"] = connection->user()->id(); + QJsonObject identifier = {{"type", "m.id.user"}, {"user", connection->user()->id()}}; + authData["identifier"] = 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"] == "M_FORBIDDEN") { + Q_EMIT passwordStatus(PasswordStatus::Wrong); + } else { + Q_EMIT passwordStatus(PasswordStatus::Other); + } + }); + } + }); +} + +NeochatChangePasswordJob::NeochatChangePasswordJob(const QString& newPassword, + bool logoutDevices, + const Omittable& auth) + : BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), + QStringLiteral("/_matrix/client/r0") % "/account/password") +{ + QJsonObject _data; + addParam<>(_data, QStringLiteral("new_password"), newPassword); + addParam(_data, QStringLiteral("logout_devices"), logoutDevices); + addParam(_data, QStringLiteral("auth"), auth); + setRequestData(std::move(_data)); +} diff --git a/src/controller.h b/src/controller.h index ab2460e56..b4e4b8ea6 100644 --- a/src/controller.h +++ b/src/controller.h @@ -43,6 +43,8 @@ public: Q_INVOKABLE void loginWithCredentials(QString, QString, QString, QString); Q_INVOKABLE void loginWithAccessToken(QString, QString, QString, QString); + Q_INVOKABLE void changePassword(Connection *connection, const QString ¤tPassword, const QString &newPassword); + QVector connections() const { return m_connections; @@ -108,6 +110,13 @@ public: void setAboutData(KAboutData aboutData); KAboutData aboutData() const; + enum PasswordStatus { + Success, + Wrong, + Other + }; + Q_ENUM(PasswordStatus); + private: explicit Controller(QObject *parent = nullptr); ~Controller(); @@ -143,6 +152,7 @@ Q_SIGNALS: void connectionChanged(); void isOnlineChanged(); void aboutDataChanged(); + void passwordStatus(PasswordStatus status); public Q_SLOTS: void logout(Quotient::Connection *conn); @@ -154,4 +164,12 @@ public Q_SLOTS: void markAllMessagesAsRead(Quotient::Connection *conn); }; +//TODO libQuotient 0.7: Drop +class NeochatChangePasswordJob : public BaseJob { +public: + explicit NeochatChangePasswordJob(const QString& newPassword, + bool logoutDevices, + const Omittable& auth = none); +}; + #endif // CONTROLLER_H diff --git a/src/spectraluser.cpp b/src/spectraluser.cpp index d3d620aa5..efa89631f 100644 --- a/src/spectraluser.cpp +++ b/src/spectraluser.cpp @@ -1,6 +1,33 @@ #include "spectraluser.h" +#include "csapi/profile.h" + QColor SpectralUser::color() { return QColor::fromHslF(hueF(), 0.7, 0.5, 1); } +//TODO libQuotient 0.7: remove default name +void SpectralUser::setDefaultName(QString defaultName) +{ + rename(defaultName); + connect(this, &Quotient::User::defaultNameChanged, this, [this]() { + m_defaultName = ""; + qDebug() << "asdf"; + Q_EMIT nameChanged(); + }); +} + +QString SpectralUser::defaultName() +{ + if(m_defaultName.isEmpty()) { + GetDisplayNameJob *job = connection()->callApi(id()); + connect(job, &BaseJob::success, this, [this, job] { + if(job->displayname().isEmpty()) + m_defaultName = id(); + else + m_defaultName = job->displayname(); + Q_EMIT nameChanged(); + }); + } + return m_defaultName; +} diff --git a/src/spectraluser.h b/src/spectraluser.h index fbee36c93..635b06e2b 100644 --- a/src/spectraluser.h +++ b/src/spectraluser.h @@ -12,6 +12,7 @@ class SpectralUser : public User { Q_OBJECT Q_PROPERTY(QColor color READ color CONSTANT) + Q_PROPERTY(QString defaultName READ defaultName WRITE setDefaultName NOTIFY nameChanged) public: SpectralUser(QString userId, Connection *connection) : User(userId, connection) @@ -19,6 +20,16 @@ public: } QColor color(); + + //TODO libQuotient 0.7: remove + void setDefaultName(QString defaultName); + QString defaultName(); + +Q_SIGNALS: + void nameChanged(); + +private: + QString m_defaultName; }; #endif // SpectralUser_H