diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ba284890..82510cfeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +mobile: allow cloud account deletion (Apple app store requirement) --- * Always add new entries at the very top of this file above other existing entries and this note. diff --git a/core/cloudstorage.cpp b/core/cloudstorage.cpp index 82759e101..d68b530d6 100644 --- a/core/cloudstorage.cpp +++ b/core/cloudstorage.cpp @@ -17,6 +17,7 @@ CloudStorageAuthenticate::CloudStorageAuthenticate(QObject *parent) : #define CLOUDBACKENDSTORAGE CLOUDURL + "/storage" #define CLOUDBACKENDVERIFY CLOUDURL + "/verify" #define CLOUDBACKENDUPDATE CLOUDURL + "/update" +#define CLOUDBACKENDDELETE CLOUDURL + "/delete-account" QNetworkReply* CloudStorageAuthenticate::backend(const QString& email,const QString& password,const QString& pin,const QString& newpasswd) { @@ -50,6 +51,34 @@ QNetworkReply* CloudStorageAuthenticate::backend(const QString& email,const QStr return reply; } +QNetworkReply* CloudStorageAuthenticate::deleteAccount(const QString& email, const QString& password) +{ + QString payload(email + QChar(' ') + password); + QNetworkRequest *request = new QNetworkRequest(QUrl(CLOUDBACKENDDELETE)); + request->setRawHeader("Accept", "text/xml, text/plain"); + request->setRawHeader("User-Agent", userAgent.toUtf8()); + request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); + reply = manager()->post(*request, qPrintable(payload)); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + connect(reply, &QNetworkReply::finished, this, &CloudStorageAuthenticate::deleteFinished); + connect(reply, &QNetworkReply::sslErrors, this, &CloudStorageAuthenticate::sslErrors); + connect(reply, &QNetworkReply::errorOccurred, this, &CloudStorageAuthenticate::uploadError); +#else + connect(reply, SIGNAL(finished()), this, SLOT(deleteFinished())); + connect(reply, SIGNAL(sslErrors(QList)), this, SLOT(sslErrors(QList))); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, + SLOT(uploadError(QNetworkReply::NetworkError))); +#endif + return reply; +} + +void CloudStorageAuthenticate::deleteFinished() +{ + QString cloudAuthReply(reply->readAll()); + qDebug() << "Completed connection with cloud storage backend, response" << cloudAuthReply; + emit finishedDelete(); +} + void CloudStorageAuthenticate::uploadFinished() { static QString myLastError; diff --git a/core/cloudstorage.h b/core/cloudstorage.h index b19b3292c..9b2b22b72 100644 --- a/core/cloudstorage.h +++ b/core/cloudstorage.h @@ -9,15 +9,18 @@ class CloudStorageAuthenticate : public QObject { Q_OBJECT public: QNetworkReply* backend(const QString& email,const QString& password,const QString& pin = QString(),const QString& newpasswd = QString()); + QNetworkReply* deleteAccount(const QString& email, const QString &passwd); explicit CloudStorageAuthenticate(QObject *parent); signals: void finishedAuthenticate(); + void finishedDelete(); void passwordChangeSuccessful(); private slots: void uploadError(QNetworkReply::NetworkError error); void sslErrors(const QList &errorList); void uploadFinished(); + void deleteFinished(); private: QNetworkReply *reply; QString userAgent; diff --git a/mobile-widgets/qml/DeleteAccount.qml b/mobile-widgets/qml/DeleteAccount.qml new file mode 100644 index 000000000..0a8adfd87 --- /dev/null +++ b/mobile-widgets/qml/DeleteAccount.qml @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 +import QtQuick 2.6 +import QtQuick.Layouts 1.2 +import org.kde.kirigami 2.4 as Kirigami +import org.subsurfacedivelog.mobile 1.0 + +Kirigami.ScrollablePage { + id: deleteAccountPage + property int pageWidth: deleteAccountPage.width - deleteAccountPage.leftPadding - deleteAccountPage.rightPadding + title: qsTr("Delete Subsurface Cloud Account") + background: Rectangle { color: subsurfaceTheme.backgroundColor } + + ColumnLayout { + spacing: Kirigami.Units.largeSpacing + width: deleteAccountPage.width + Layout.margins: Kirigami.Units.gridUnit / 2 + + Kirigami.Heading { + text: qsTr("Delete Subsurface Cloud Account") + color: subsurfaceTheme.textColor + Layout.topMargin: Kirigami.Units.gridUnit + Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: pageWidth + wrapMode: TextEdit.NoWrap + fontSizeMode: Text.Fit + } + + Kirigami.Heading { + text: qsTr("Deleting your Subsurface Cloud account is permanent.\n") + + qsTr("There is no way to undo this action.") + level: 4 + color: subsurfaceTheme.textColor + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: Kirigami.Units.largeSpacing * 3 + Layout.maximumWidth: pageWidth + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + anchors.horizontalCenter: parent.Center + horizontalAlignment: Text.AlignHCenter + } + + Kirigami.Heading { + text: PrefCloudStorage.cloud_storage_email + level: 4 + color: subsurfaceTheme.textColor + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: Kirigami.Units.largeSpacing * 3 + Layout.maximumWidth: pageWidth + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + anchors.horizontalCenter: parent.Center + horizontalAlignment: Text.AlignHCenter + } + + + TemplateButton { + id: deleteCloudAccount + Layout.alignment: Qt.AlignHCenter + text: qsTr("delete cloud account") + onClicked: { + manager.appendTextToLog("request to delete account confirmed") + manager.deleteAccount() + rootItem.returnTopPage() + } + } + + TemplateButton { + id: dontDeleteCloudAccount + Layout.alignment: Qt.AlignHCenter + text: qsTr("never mind") + onClicked: { + manager.appendTextToLog("request to delete account cancelled") + rootItem.returnTopPage() + } + } + } +} diff --git a/mobile-widgets/qml/Settings.qml b/mobile-widgets/qml/Settings.qml index 0137fcac1..a75abc1aa 100644 --- a/mobile-widgets/qml/Settings.qml +++ b/mobile-widgets/qml/Settings.qml @@ -61,6 +61,15 @@ TemplatePage { text: describe[Backend.cloud_verification_status] Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5 } + TemplateButton { + id: deleteCloudAccount + enabled: Backend.cloud_verification_status !== Enums.CS_NOCLOUD + text: qsTr("Delete Account") + onClicked: { + manager.appendTextToLog("requesting account deletion"); + showPage(deleteAccount) + } + } } TemplateLine { visible: sectionGeneral.isExpanded diff --git a/mobile-widgets/qml/main.qml b/mobile-widgets/qml/main.qml index 40b397ce1..a0b236484 100644 --- a/mobile-widgets/qml/main.qml +++ b/mobile-widgets/qml/main.qml @@ -783,6 +783,10 @@ if you have network connectivity and want to sync your data to cloud storage."), id: settingsWindow } + DeleteAccount { + id: deleteAccount + } + CopySettings { id: settingsCopyWindow visible: false diff --git a/mobile-widgets/qml/mobile-resources.qrc b/mobile-widgets/qml/mobile-resources.qrc index 94a3cf1d1..19091f7c1 100644 --- a/mobile-widgets/qml/mobile-resources.qrc +++ b/mobile-widgets/qml/mobile-resources.qrc @@ -19,6 +19,7 @@ About.qml CloudCredentials.qml + DeleteAccount.qml DiveDetails.qml DiveDetailsEdit.qml DiveDetailsView.qml diff --git a/mobile-widgets/qmlmanager.cpp b/mobile-widgets/qmlmanager.cpp index 8ff3d2bb5..f5b62e781 100644 --- a/mobile-widgets/qmlmanager.cpp +++ b/mobile-widgets/qmlmanager.cpp @@ -725,6 +725,42 @@ bool QMLManager::verifyCredentials(QString email, QString password, QString pin) return true; } +void QMLManager::deleteAccount() +{ + QString email(prefs.cloud_storage_email); + QString passwd(prefs.cloud_storage_password); + if (email.isEmpty() || passwd.isEmpty()) + return; + + setStartPageText(tr("Deleting cloud account...")); + appendTextToLog(QStringLiteral("user requested that we delete cloud account for email %1").arg(email)); + CloudStorageAuthenticate *csa = new CloudStorageAuthenticate(this); + csa->deleteAccount(email, passwd); + // let's wait here for the signal to avoid too many more nested functions + QTimer myTimer; + myTimer.setSingleShot(true); + QEventLoop loop; + connect(csa, &CloudStorageAuthenticate::finishedDelete, &loop, &QEventLoop::quit); + connect(&myTimer, &QTimer::timeout, &loop, &QEventLoop::quit); + myTimer.start(prefs.cloud_timeout * 3 * 1000); // give it extra time + loop.exec(); + if (!myTimer.isActive()) { + // got no response from the server + setStartPageText(RED_FONT + tr("No response from cloud server to delete account") + END_FONT); + appendTextToLog(QStringLiteral("no response from cloud server to delete account")); + return; + } + myTimer.stop(); + appendTextToLog(QStringLiteral("deleted the account")); + qPrefCloudStorage::set_cloud_storage_email(""); + qPrefCloudStorage::set_cloud_storage_email_encoded(""); + qPrefCloudStorage::set_cloud_storage_password(""); + qPrefCloudStorage::set_cloud_verification_status(qPrefCloudStorage::CS_NOCLOUD); + set_filename(qPrintable(nocloud_localstorage())); + setStartPageText(tr("Cloud storage account deleted.")); + return; +} + void QMLManager::loadDivesWithValidCredentials() { QString url; diff --git a/mobile-widgets/qmlmanager.h b/mobile-widgets/qmlmanager.h index 7e77aa3ae..b424f4417 100644 --- a/mobile-widgets/qmlmanager.h +++ b/mobile-widgets/qmlmanager.h @@ -180,6 +180,7 @@ public slots: void saveChangesCloud(bool forceRemoteSync, bool fromUndo = false); void selectDive(int id); void deleteDive(int id); + void deleteAccount(); void toggleDiveInvalid(int id); void copyDiveData(int id); void pasteDiveData(int id);