diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bb5db407e..8a73a375c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -175,6 +175,10 @@ add_library(neochat STATIC foreigntypes.h models/threepidmodel.cpp models/threepidmodel.h + threepidaddhelper.cpp + threepidaddhelper.h + jobs/neochatadd3pidjob.cpp + jobs/neochatadd3pidjob.h ) set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES diff --git a/src/devtools/FeatureFlagPage.qml b/src/devtools/FeatureFlagPage.qml index a2b07ced8..e17d854fb 100644 --- a/src/devtools/FeatureFlagPage.qml +++ b/src/devtools/FeatureFlagPage.qml @@ -28,5 +28,14 @@ FormCard.FormCardPage { onToggled: Config.secretBackup = checked } + FormCard.FormCheckDelegate { + text: i18nc("@option:check Enable the matrix feature to add a phone number as a third party ID", "Add phone numbers as 3PIDs") + checked: Config.phone3PId + + onToggled: { + Config.phone3PId = checked + Config.save(); + } + } } } diff --git a/src/jobs/neochatadd3pidjob.cpp b/src/jobs/neochatadd3pidjob.cpp new file mode 100644 index 000000000..5c86845ca --- /dev/null +++ b/src/jobs/neochatadd3pidjob.cpp @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#include "neochatadd3pidjob.h" + +using namespace Quotient; + +NeochatAdd3PIdJob::NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const Omittable &auth) + : BaseJob(HttpVerb::Post, QStringLiteral("Add3PIDJob"), makePath("/_matrix/client/v3", "/account/3pid/add")) +{ + QJsonObject _dataJson; + addParam(_dataJson, QStringLiteral("auth"), auth); + addParam<>(_dataJson, QStringLiteral("client_secret"), clientSecret); + addParam<>(_dataJson, QStringLiteral("sid"), sid); + setRequestData({_dataJson}); +} diff --git a/src/jobs/neochatadd3pidjob.h b/src/jobs/neochatadd3pidjob.h new file mode 100644 index 000000000..af17faba2 --- /dev/null +++ b/src/jobs/neochatadd3pidjob.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#pragma once + +#include +#include + +class NeochatAdd3PIdJob : public Quotient::BaseJob +{ +public: + explicit NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const Quotient::Omittable &auth = Quotient::none); +}; diff --git a/src/models/threepidmodel.cpp b/src/models/threepidmodel.cpp index ad7e57c63..71086f7f6 100644 --- a/src/models/threepidmodel.cpp +++ b/src/models/threepidmodel.cpp @@ -10,15 +10,7 @@ ThreePIdModel::ThreePIdModel(NeoChatConnection *connection) { Q_ASSERT(connection); connect(connection, &NeoChatConnection::stateChanged, this, [this]() { - const auto connection = dynamic_cast(this->parent()); - if (connection != nullptr && connection->isLoggedIn()) { - const auto threePIdJob = connection->callApi(); - connect(threePIdJob, &Quotient::BaseJob::success, this, [this, threePIdJob]() { - beginResetModel(); - m_threePIds = threePIdJob->threepids(); - endResetModel(); - }); - } + refreshModel(); }); } @@ -56,4 +48,17 @@ QHash ThreePIdModel::roleNames() const }; } +void ThreePIdModel::refreshModel() +{ + const auto connection = dynamic_cast(this->parent()); + if (connection != nullptr && connection->isLoggedIn()) { + const auto threePIdJob = connection->callApi(); + connect(threePIdJob, &Quotient::BaseJob::success, this, [this, threePIdJob]() { + beginResetModel(); + m_threePIds = threePIdJob->threepids(); + endResetModel(); + }); + } +} + #include "moc_threepidmodel.cpp" diff --git a/src/models/threepidmodel.h b/src/models/threepidmodel.h index 5777fbe01..59662acab 100644 --- a/src/models/threepidmodel.h +++ b/src/models/threepidmodel.h @@ -53,6 +53,8 @@ public: */ [[nodiscard]] QHash roleNames() const override; + Q_INVOKABLE void refreshModel(); + private: QVector m_threePIds; }; diff --git a/src/neochatconfig.kcfg b/src/neochatconfig.kcfg index 1bb076f38..4dff7510b 100644 --- a/src/neochatconfig.kcfg +++ b/src/neochatconfig.kcfg @@ -182,6 +182,10 @@ false + + + false + diff --git a/src/qml/UserInfo.qml b/src/qml/UserInfo.qml index 10019863d..789ee571d 100644 --- a/src/qml/UserInfo.qml +++ b/src/qml/UserInfo.qml @@ -48,11 +48,15 @@ RowLayout { activeFocusOnTab: true - onClicked: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'AccountEditorPage'), { - connection: root.connection + onClicked: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'NeoChatSettings'), { + defaultPage: "accounts", + connection: root.connection, + initialAccount: root.connection }, { - title: i18nc("@title:window", "Account editor") - }); + title: i18nc("@action:button", "Configure"), + width: Kirigami.Units.gridUnit * 50, + height: Kirigami.Units.gridUnit * 42 + }) TapHandler { acceptedButtons: Qt.RightButton diff --git a/src/settings/AccountEditorPage.qml b/src/settings/AccountEditorPage.qml index 3c153e7ec..c2ec02454 100644 --- a/src/settings/AccountEditorPage.qml +++ b/src/settings/AccountEditorPage.qml @@ -202,6 +202,7 @@ FormCard.FormCardPage { medium: "email" } ThreePIdCard { + visible: Config.phone3PId connection: root.connection title: i18n("Phone Numbers") medium: "msisdn" diff --git a/src/settings/AccountsPage.qml b/src/settings/AccountsPage.qml index f49369b2a..82570bfff 100644 --- a/src/settings/AccountsPage.qml +++ b/src/settings/AccountsPage.qml @@ -16,8 +16,25 @@ import org.kde.neochat FormCard.FormCardPage { id: root + property NeoChatConnection initialAccount + title: i18n("Accounts") + Component.onCompleted: if (initialAccount) { + intialAccountTimer.restart() + } + + Timer { + id: intialAccountTimer + interval: 10 + running: false + onTriggered: applicationWindow().pageStack.layers.push(Qt.createComponent('org.kde.neochat.settings', 'AccountEditorPage'), { + connection: initialAccount + }, { + title: i18n("Account editor") + }) + } + FormCard.FormHeader { title: i18n("Accounts") } diff --git a/src/settings/CMakeLists.txt b/src/settings/CMakeLists.txt index 78e2a7de1..6b183c542 100644 --- a/src/settings/CMakeLists.txt +++ b/src/settings/CMakeLists.txt @@ -31,6 +31,7 @@ qt_add_qml_module(settings EmoticonFormCard.qml IgnoredUsersDialog.qml NotificationRuleItem.qml + PasswordSheet.qml ThemeRadioButton.qml ThreePIdCard.qml ) diff --git a/src/settings/NeoChatSettings.qml b/src/settings/NeoChatSettings.qml index 82a86b642..e6dd61799 100644 --- a/src/settings/NeoChatSettings.qml +++ b/src/settings/NeoChatSettings.qml @@ -13,6 +13,8 @@ KirigamiSettings.CategorizedSettings { property NeoChatConnection connection + property NeoChatConnection initialAccount + objectName: "settingsPage" actions: [ KirigamiSettings.SettingAction { @@ -54,6 +56,11 @@ KirigamiSettings.CategorizedSettings { text: i18n("Accounts") icon.name: "preferences-system-users" page: Qt.resolvedUrl("AccountsPage.qml") + initialProperties: { + return { + initialAccount: root.initialAccount + }; + } }, KirigamiSettings.SettingAction { actionName: "emoticons" diff --git a/src/settings/PasswordSheet.qml b/src/settings/PasswordSheet.qml new file mode 100644 index 000000000..b7da5ae38 --- /dev/null +++ b/src/settings/PasswordSheet.qml @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts + +import org.kde.kirigami as Kirigami +import org.kde.kirigamiaddons.formcard as FormCard + +Kirigami.Dialog { + id: root + + signal submitPassword(string password) + + title: i18nc("@title:dialog", "Enter password") + + preferredWidth: Kirigami.Units.gridUnit * 24 + + standardButtons: QQC2.Dialog.Ok | QQC2.Dialog.Cancel + onAccepted: { + root.submitPassword(passwordField.text); + passwordField.text = ""; + root.close(); + } + + ColumnLayout { + FormCard.FormTextFieldDelegate { + id: passwordField + label: i18nc("@label:textbox", "Password:") + echoMode: TextInput.Password + } + } +} diff --git a/src/settings/ThreePIdCard.qml b/src/settings/ThreePIdCard.qml index 89e72aa09..8ad8d7c98 100644 --- a/src/settings/ThreePIdCard.qml +++ b/src/settings/ThreePIdCard.qml @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later import QtQuick +import QtQuick.Controls as QQC2 import QtQuick.Layouts import org.kde.kirigami as Kirigami @@ -18,8 +19,6 @@ ColumnLayout { required property string title required property string medium - visible: deviceRepeater.count > 0 - FormCard.FormHeader { title: root.title } @@ -34,10 +33,124 @@ ColumnLayout { filterString: root.medium } - delegate: FormCard.FormTextDelegate { + delegate: FormCard.AbstractFormDelegate { + id: threePIdDelegate + required property string address + required property string medium + + contentItem: RowLayout { + QQC2.Label { + Layout.fillWidth: true + text: threePIdDelegate.address + textFormat: Text.PlainText + elide: Text.ElideRight + wrapMode: Text.Wrap + maximumLineCount: 2 + color: Kirigami.Theme.textColor + } + QQC2.ToolButton { + text: i18nc("@action:button", "Remove") + icon.name: "edit-delete-remove" + onClicked: threePIdAddHelper.remove3PId(threePIdDelegate.address, threePIdDelegate.medium) + } + } + } + + + + FormCard.FormTextDelegate { required property string address text: address } } + FormCard.FormTextFieldDelegate { + id: newCountryCode + visible: root.medium === "msisdn" + readOnly: threePIdAddHelper.newIdStatus == ThreePIdAddHelper.Verification || + threePIdAddHelper.newIdStatus == ThreePIdAddHelper.Authentication || + threePIdAddHelper.newIdStatus == ThreePIdAddHelper.AuthFailure || + threePIdAddHelper.newIdStatus == ThreePIdAddHelper.VerificationFailure + label: i18nc("@label:textbox", "Country Code for new phone number") + + Connections { + target: root.connection.threePIdModel + + function onModelReset() { + newCountryCode.text = "" + } + } + } + FormCard.FormTextFieldDelegate { + id: newId + readOnly: threePIdAddHelper.newIdStatus == ThreePIdAddHelper.Verification || + threePIdAddHelper.newIdStatus == ThreePIdAddHelper.Authentication || + threePIdAddHelper.newIdStatus == ThreePIdAddHelper.AuthFailure || + threePIdAddHelper.newIdStatus == ThreePIdAddHelper.VerificationFailure + label: root.medium === "email" ? i18nc("@label:textbox", "New email address") : i18nc("@label:textbox", "New phone number") + + statusMessage: switch(threePIdAddHelper.newIdStatus) { + case ThreePIdAddHelper.Verification: + return i18n("%1. Please follow the instructions there and then click the button below", root.medium == "email" ? i18n("We've sent you an email") : i18n("We've sent you a text message")); + case ThreePIdAddHelper.Invalid: + return root.medium == "email" ? i18n("The entered email is not valid") : i18n("The entered phone number is not valid"); + case ThreePIdAddHelper.AuthFailure: + return i18n("Incorrect password entered"); + case ThreePIdAddHelper.VerificationFailure: + return root.medium == "email" ? i18n("The email has not been verified. Please go to the email and follow the instructions there and then click the button below") : i18n("The phone number has not been verified. Please go to the text message and follow the instructions there and then click the button below"); + default: + return ""; + } + status: switch(threePIdAddHelper.newIdStatus) { + case ThreePIdAddHelper.Invalid: + case ThreePIdAddHelper.AuthFailure: + return Kirigami.MessageType.Error; + case ThreePIdAddHelper.VerificationFailure: + return Kirigami.MessageType.Warning; + default: + return Kirigami.MessageType.Information; + } + + onAccepted: _private.openPasswordSheet() + + Connections { + target: root.connection.threePIdModel + + function onModelReset() { + newId.text = "" + } + } + } + FormCard.FormButtonDelegate { + text: threePIdAddHelper.newIdStatus == ThreePIdAddHelper.Ready ? i18nc("@action:button Add new email or phone number", "Add") : i18nc("@action:button", "Continue") + onClicked: _private.openPasswordSheet() + } + FormCard.FormButtonDelegate { + visible: threePIdAddHelper.newIdStatus == ThreePIdAddHelper.Verification || + threePIdAddHelper.newIdStatus == ThreePIdAddHelper.Authentication || + threePIdAddHelper.newIdStatus == ThreePIdAddHelper.AuthFailure || + threePIdAddHelper.newIdStatus == ThreePIdAddHelper.VerificationFailure + text: i18nc("@action:button As in 'go back'", "Back") + onClicked: threePIdAddHelper.back() + } + } + + ThreePIdAddHelper { + id: threePIdAddHelper + connection: root.connection + medium: root.medium + newId: newId.text + } + + QtObject { + id: _private + function openPasswordSheet() { + if (threePIdAddHelper.newIdStatus == ThreePIdAddHelper.Ready) { + threePIdAddHelper.initiateNewIdAdd(); + } else { + let dialog = Qt.createComponent('org.kde.neochat.settings', 'PasswordSheet').createObject(root, {}); + dialog.submitPassword.connect(password => threePIdAddHelper.finalizeNewIdAdd(password)); + dialog.open(); + } + } } } diff --git a/src/threepidaddhelper.cpp b/src/threepidaddhelper.cpp new file mode 100644 index 000000000..fd4d3cc9f --- /dev/null +++ b/src/threepidaddhelper.cpp @@ -0,0 +1,199 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#include "threepidaddhelper.h" + +#include +#include +#include +#include + +#include "jobs/neochatadd3pidjob.h" +#include "neochatconnection.h" + +ThreePIdAddHelper::ThreePIdAddHelper(QObject *parent) + : QObject(parent) +{ +} + +NeoChatConnection *ThreePIdAddHelper::connection() const +{ + return m_connection; +} + +void ThreePIdAddHelper::setConnection(NeoChatConnection *connection) +{ + if (m_connection == connection) { + return; + } + m_connection = connection; + Q_EMIT connectionChanged(); +} + +QString ThreePIdAddHelper::medium() const +{ + return m_medium; +} + +void ThreePIdAddHelper::setMedium(const QString &medium) +{ + if (m_medium == medium) { + return; + } + m_medium = medium; + Q_EMIT mediumChanged(); +} + +QString ThreePIdAddHelper::newId() const +{ + return m_newId; +} + +void ThreePIdAddHelper::setNewId(const QString &newId) +{ + if (newId == m_newId) { + return; + } + m_newId = newId; + Q_EMIT newIdChanged(); + + m_newIdSecret.clear(); + m_newIdSid.clear(); + m_newIdStatus = Ready; + Q_EMIT newIdStatusChanged(); +} + +QString ThreePIdAddHelper::newCountryCode() const +{ + return m_newCountryCode; +} + +void ThreePIdAddHelper::setNewCountryCode(const QString &newCountryCode) +{ + if (newCountryCode == m_newCountryCode) { + return; + } + m_newCountryCode = newCountryCode; + Q_EMIT newCountryCodeChanged(); + + m_newIdSecret.clear(); + m_newIdSid.clear(); + m_newIdStatus = Ready; + Q_EMIT newIdStatusChanged(); +} + +void ThreePIdAddHelper::initiateNewIdAdd() +{ + if (m_newId.isEmpty()) { + return; + } + if (m_medium == QLatin1String("email")) { + emailTokenJob(); + } else { + msisdnTokenJob(); + } +} + +void ThreePIdAddHelper::emailTokenJob() +{ + m_newIdSecret = QString::fromLatin1(QUuid::createUuid().toString().toLatin1().toBase64()); + Quotient::EmailValidationData data; + data.email = m_newId; + data.clientSecret = m_newIdSecret; + data.sendAttempt = 0; + + const auto job = m_connection->callApi(data); + connect(job, &Quotient::BaseJob::finished, this, &ThreePIdAddHelper::tokenJobFinished); +} + +void ThreePIdAddHelper::msisdnTokenJob() +{ + m_newIdSecret = QString::fromLatin1(QUuid::createUuid().toString().toLatin1().toBase64()); + Quotient::MsisdnValidationData data; + data.country = m_newCountryCode; + data.phoneNumber = m_newId; + data.clientSecret = m_newIdSecret; + data.sendAttempt = 0; + + const auto job = m_connection->callApi(data); + connect(job, &Quotient::BaseJob::finished, this, &ThreePIdAddHelper::tokenJobFinished); +} + +void ThreePIdAddHelper::tokenJobFinished(Quotient::BaseJob *job) +{ + if (job->status() == Quotient::BaseJob::Success) { + m_newIdSid = job->jsonData()[QLatin1String("sid")].toString(); + m_newIdStatus = Verification; + Q_EMIT newIdStatusChanged(); + return; + } + m_newIdStatus = Invalid; + Q_EMIT newIdStatusChanged(); +} + +ThreePIdAddHelper::ThreePIdStatus ThreePIdAddHelper::newIdStatus() const +{ + return m_newIdStatus; +} + +void ThreePIdAddHelper::finalizeNewIdAdd(const QString &password) +{ + const auto job = m_connection->callApi(m_newIdSecret, m_newIdSid); + connect(job, &Quotient::BaseJob::result, this, [this, job, password] { + m_newIdStatus = Authentication; + Q_EMIT newIdStatusChanged(); + + if (static_cast(job->error()) == Quotient::BaseJob::Unauthorised) { + QJsonObject replyData = job->jsonData(); + QJsonObject authData; + authData[QLatin1String("session")] = replyData[QLatin1String("session")]; + authData[QLatin1String("password")] = password; + authData[QLatin1String("type")] = QLatin1String("m.login.password"); + QJsonObject identifier = {{QLatin1String("type"), QLatin1String("m.id.user")}, {QLatin1String("user"), m_connection->userId()}}; + authData[QLatin1String("identifier")] = identifier; + const auto innerJob = m_connection->callApi(m_newIdSecret, m_newIdSid, authData); + connect(innerJob, &Quotient::BaseJob::success, this, [this]() { + m_connection->threePIdModel()->refreshModel(); + m_newIdSecret.clear(); + m_newIdSid.clear(); + m_newIdStatus = Success; + Q_EMIT newIdStatusChanged(); + }); + connect(innerJob, &Quotient::BaseJob::failure, this, [innerJob, this]() { + if (innerJob->jsonData()[QLatin1String("errcode")] == QLatin1String("M_FORBIDDEN")) { + m_newIdStatus = AuthFailure; + Q_EMIT newIdStatusChanged(); + } else if (innerJob->jsonData()[QLatin1String("errcode")] == QLatin1String("M_THREEPID_AUTH_FAILED")) { + m_newIdStatus = VerificationFailure; + Q_EMIT newIdStatusChanged(); + } else { + m_newIdStatus = Other; + Q_EMIT newIdStatusChanged(); + } + }); + } + }); +} + +void ThreePIdAddHelper::remove3PId(const QString &threePId, const QString &type) +{ + const auto job = m_connection->callApi(type, threePId); + connect(job, &Quotient::BaseJob::success, this, [this]() { + m_connection->threePIdModel()->refreshModel(); + }); +} + +void ThreePIdAddHelper::back() +{ + switch (m_newIdStatus) { + case Verification: + case Authentication: + case AuthFailure: + case VerificationFailure: + m_newIdStatus = Ready; + Q_EMIT newIdStatusChanged(); + return; + default: + return; + } +} diff --git a/src/threepidaddhelper.h b/src/threepidaddhelper.h new file mode 100644 index 000000000..36b4c5e8f --- /dev/null +++ b/src/threepidaddhelper.h @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#pragma once + +#include +#include + +#include + +class NeoChatConnection; + +/** + * @class ThreePIdAddHelper + * + * This class is designed to help the process of adding a new 3PID to the account. + * It will manage the various stages of verification and authentication. + */ +class ThreePIdAddHelper : public QObject +{ + Q_OBJECT + QML_ELEMENT + + /** + * @brief The connection to add a 3PID to. + */ + Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged) + + /** + * @brief The type of 3PID being added. + * + * email or msisdn. + */ + Q_PROPERTY(QString medium READ medium WRITE setMedium NOTIFY mediumChanged) + + /** + * @brief The 3PID to add. + * + * Email or phone number depending on type. + */ + Q_PROPERTY(QString newId READ newId WRITE setNewId NOTIFY newIdChanged) + + /** + * @brief The country code if a phone number is being added. + */ + Q_PROPERTY(QString newCountryCode READ newCountryCode WRITE setNewCountryCode NOTIFY newCountryCodeChanged) + + /** + * @brief The current status. + * + * @sa ThreePIdStatus + */ + Q_PROPERTY(ThreePIdStatus newIdStatus READ newIdStatus NOTIFY newIdStatusChanged) + +public: + /** + * @brief Defines the current status for adding a 3PID. + */ + enum ThreePIdStatus { + Ready, /**< The process is ready to start. I.e. there is no ongoing attempt to set a new 3PID. */ + Verification, /**< The request to verify the new 3PID has been sent. */ + Authentication, /**< The user needs to authenticate. */ + Success, /**< The 3PID has been successfully added. */ + Invalid, /**< The 3PID can't be used. */ + AuthFailure, /**< The authentication was wrong. */ + VerificationFailure, /**< The verification has not been completed. */ + Other, /**< An unknown problem occurred. */ + }; + Q_ENUM(ThreePIdStatus) + + explicit ThreePIdAddHelper(QObject *parent = nullptr); + + [[nodiscard]] NeoChatConnection *connection() const; + void setConnection(NeoChatConnection *connection); + + [[nodiscard]] QString medium() const; + void setMedium(const QString &medium); + + [[nodiscard]] QString newId() const; + void setNewId(const QString &newEmail); + + [[nodiscard]] QString newCountryCode() const; + void setNewCountryCode(const QString &newCountryCode); + + /** + * @brief Start the process to add the new 3PID. + * + * This will start the process of verifying the 3PID credentials that have been given. + */ + Q_INVOKABLE void initiateNewIdAdd(); + + [[nodiscard]] ThreePIdStatus newIdStatus() const; + + /** + * @brief Finalize the process of adding the new 3PID. + * + * @param password the user's password to authenticate the addition. + */ + Q_INVOKABLE void finalizeNewIdAdd(const QString &password); + + /** + * @brief Remove the given 3PID. + */ + Q_INVOKABLE void remove3PId(const QString &threePId, const QString &type); + + /** + * @brief Go back a step in the process. + */ + Q_INVOKABLE void back(); + +Q_SIGNALS: + void connectionChanged(); + void mediumChanged(); + void newIdChanged(); + void newCountryCodeChanged(); + void newEmailSessionStartedChanged(); + void newIdStatusChanged(); + +private: + QPointer m_connection; + QString m_medium = QString(); + + ThreePIdStatus m_newIdStatus = Ready; + QString m_newId = QString(); + QString m_newCountryCode = QString(); + QString m_newIdSecret = QString(); + QString m_newIdSid = QString(); + + void emailTokenJob(); + void msisdnTokenJob(); + + void tokenJobFinished(Quotient::BaseJob *job); +};