diff --git a/CMakeLists.txt b/CMakeLists.txt index 3989f3b1..7c602249 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.4.1 +project(${PROJECT} VERSION 4.8.4.2 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 2078) +set(APP_ANDROID_VERSION_CODE 2079) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") diff --git a/client/core/controllers/coreController.cpp b/client/core/controllers/coreController.cpp index 8d63974c..82232c99 100644 --- a/client/core/controllers/coreController.cpp +++ b/client/core/controllers/coreController.cpp @@ -93,6 +93,9 @@ void CoreController::initModels() m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this)); m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get()); + + m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get()); } void CoreController::initControllers() @@ -132,7 +135,8 @@ void CoreController::initControllers() m_systemController.reset(new SystemController(m_settings)); m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); - m_apiSettingsController.reset(new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_settings)); + m_apiSettingsController.reset( + new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings)); m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get()); m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings)); diff --git a/client/core/controllers/coreController.h b/client/core/controllers/coreController.h index 029044c4..700504af 100644 --- a/client/core/controllers/coreController.h +++ b/client/core/controllers/coreController.h @@ -25,8 +25,9 @@ #include "ui/models/protocols/ikev2ConfigModel.h" #endif #include "ui/models/api/apiAccountInfoModel.h" -#include "ui/models/api/apiServicesModel.h" #include "ui/models/api/apiCountryModel.h" +#include "ui/models/api/apiDevicesModel.h" +#include "ui/models/api/apiServicesModel.h" #include "ui/models/appSplitTunnelingModel.h" #include "ui/models/clientManagementModel.h" #include "ui/models/protocols/awgConfigModel.h" @@ -117,6 +118,7 @@ private: QSharedPointer m_apiServicesModel; QSharedPointer m_apiCountryModel; QSharedPointer m_apiAccountInfoModel; + QSharedPointer m_apiDevicesModel; QScopedPointer m_openVpnConfigModel; QScopedPointer m_shadowSocksConfigModel; diff --git a/client/core/controllers/gatewayController.cpp b/client/core/controllers/gatewayController.cpp index 61c3d594..15776328 100644 --- a/client/core/controllers/gatewayController.cpp +++ b/client/core/controllers/gatewayController.cpp @@ -148,7 +148,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api QByteArray encryptedResponseBody = reply->readAll(); - if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, false)) { + if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) { auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) { request.setUrl(url); return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson()); diff --git a/client/images/controls/monitor.svg b/client/images/controls/monitor.svg new file mode 100644 index 00000000..1cdf57c2 --- /dev/null +++ b/client/images/controls/monitor.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index cd44ca60..16071da0 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -229,6 +229,8 @@ ui/qml/Pages2/PageSettingsApiSupport.qml ui/qml/Pages2/PageSettingsApiInstructions.qml ui/qml/Pages2/PageSettingsApiNativeConfigs.qml + ui/qml/Pages2/PageSettingsApiDevices.qml + images/controls/monitor.svg images/flagKit/ZW.svg diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 27813f9c..d3c8747d 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -1,7 +1,7 @@ #include "apiConfigsController.h" -#include #include +#include #include "amnezia_application.h" #include "configurators/wireguard_configurator.h" @@ -251,6 +251,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const newServerConfig.insert(configKey::apiConfig, newApiConfig); newServerConfig.insert(configKey::authData, authData); + // newServerConfig.insert( m_serversModel->editServer(newServerConfig, serverIndex); if (reloadServiceConfig) { @@ -354,6 +355,43 @@ bool ApiConfigsController::deactivateDevice() return true; } +bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode) +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + + auto serverIndex = m_serversModel->getProcessedServerIndex(); + auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); + auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); + + if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) { + return true; + } + + QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); + ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + + QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); + apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode); + apiPayload[configKey::serverCountryCode] = serverCountryCode; + apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); + apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); + apiPayload[configKey::uuid] = uuid; + + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { + emit errorOccurred(errorCode); + return false; + } + + if (uuid == m_settings->getInstallationUuid(true)) { + serverConfigObject.remove(config_key::containers); + m_serversModel->editServer(serverConfigObject, serverIndex); + } + + return true; +} + bool ApiConfigsController::isConfigValid() { int serverIndex = m_serversModel->getDefaultServerIndex(); diff --git a/client/ui/controllers/api/apiConfigsController.h b/client/ui/controllers/api/apiConfigsController.h index 26b02978..2fe981e4 100644 --- a/client/ui/controllers/api/apiConfigsController.h +++ b/client/ui/controllers/api/apiConfigsController.h @@ -31,6 +31,7 @@ public slots: bool reloadServiceConfig = false); bool updateServiceFromTelegram(const int serverIndex); bool deactivateDevice(); + bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode); bool isConfigValid(); diff --git a/client/ui/controllers/api/apiSettingsController.cpp b/client/ui/controllers/api/apiSettingsController.cpp index 53b6e5a8..737bfd1a 100644 --- a/client/ui/controllers/api/apiSettingsController.cpp +++ b/client/ui/controllers/api/apiSettingsController.cpp @@ -25,11 +25,13 @@ namespace ApiSettingsController::ApiSettingsController(const QSharedPointer &serversModel, const QSharedPointer &apiAccountInfoModel, const QSharedPointer &apiCountryModel, + const QSharedPointer &apiDevicesModel, const std::shared_ptr &settings, QObject *parent) : QObject(parent), m_serversModel(serversModel), m_apiAccountInfoModel(apiAccountInfoModel), m_apiCountryModel(apiCountryModel), + m_apiDevicesModel(apiDevicesModel), m_settings(settings) { } @@ -73,6 +75,7 @@ bool ApiSettingsController::getAccountInfo(bool reload) if (reload) { updateApiCountryModel(); + updateApiDevicesModel(); } return true; @@ -83,3 +86,8 @@ void ApiSettingsController::updateApiCountryModel() m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), ""); m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo()); } + +void ApiSettingsController::updateApiDevicesModel() +{ + m_apiDevicesModel->updateModel(m_apiAccountInfoModel->getIssuedConfigsInfo()); +} diff --git a/client/ui/controllers/api/apiSettingsController.h b/client/ui/controllers/api/apiSettingsController.h index e12e232a..afe9a570 100644 --- a/client/ui/controllers/api/apiSettingsController.h +++ b/client/ui/controllers/api/apiSettingsController.h @@ -5,6 +5,7 @@ #include "ui/models/api/apiAccountInfoModel.h" #include "ui/models/api/apiCountryModel.h" +#include "ui/models/api/apiDevicesModel.h" #include "ui/models/servers_model.h" class ApiSettingsController : public QObject @@ -12,13 +13,14 @@ class ApiSettingsController : public QObject Q_OBJECT public: ApiSettingsController(const QSharedPointer &serversModel, const QSharedPointer &apiAccountInfoModel, - const QSharedPointer &apiCountryModel, const std::shared_ptr &settings, - QObject *parent = nullptr); + const QSharedPointer &apiCountryModel, const QSharedPointer &apiDevicesModel, + const std::shared_ptr &settings, QObject *parent = nullptr); ~ApiSettingsController(); public slots: bool getAccountInfo(bool reload); void updateApiCountryModel(); + void updateApiDevicesModel(); signals: void errorOccurred(ErrorCode errorCode); @@ -27,6 +29,7 @@ private: QSharedPointer m_serversModel; QSharedPointer m_apiAccountInfoModel; QSharedPointer m_apiCountryModel; + QSharedPointer m_apiDevicesModel; std::shared_ptr m_settings; }; diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 9ec93cc0..60621414 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -36,6 +36,7 @@ namespace PageLoader PageSettingsApiSupport, PageSettingsApiInstructions, PageSettingsApiNativeConfigs, + PageSettingsApiDevices, PageServiceSftpSettings, PageServiceTorWebsiteSettings, diff --git a/client/ui/models/api/apiAccountInfoModel.cpp b/client/ui/models/api/apiAccountInfoModel.cpp index 055c2347..cdb9dfe8 100644 --- a/client/ui/models/api/apiAccountInfoModel.cpp +++ b/client/ui/models/api/apiAccountInfoModel.cpp @@ -58,6 +58,19 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const case IsComponentVisibleRole: { return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2; } + case HasExpiredWorkerRole: { + for (int i = 0; i < m_issuedConfigsInfo.size(); i++) { + QJsonObject issuedConfigObject = m_issuedConfigsInfo.at(i).toObject(); + + auto lastDownloaded = QDateTime::fromString(issuedConfigObject.value(apiDefs::key::lastDownloaded).toString()); + auto workerLastUpdated = QDateTime::fromString(issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString()); + + if (lastDownloaded < workerLastUpdated) { + return true; + } + } + return false; + } } return QVariant(); @@ -124,6 +137,7 @@ QHash ApiAccountInfoModel::roleNames() const roles[ConnectedDevicesRole] = "connectedDevices"; roles[ServiceDescriptionRole] = "serviceDescription"; roles[IsComponentVisibleRole] = "isComponentVisible"; + roles[HasExpiredWorkerRole] = "hasExpiredWorker"; return roles; } diff --git a/client/ui/models/api/apiAccountInfoModel.h b/client/ui/models/api/apiAccountInfoModel.h index e50ea5f5..44eb7ee6 100644 --- a/client/ui/models/api/apiAccountInfoModel.h +++ b/client/ui/models/api/apiAccountInfoModel.h @@ -17,7 +17,8 @@ public: ConnectedDevicesRole, ServiceDescriptionRole, EndDateRole, - IsComponentVisibleRole + IsComponentVisibleRole, + HasExpiredWorkerRole }; explicit ApiAccountInfoModel(QObject *parent = nullptr); diff --git a/client/ui/models/api/apiCountryModel.cpp b/client/ui/models/api/apiCountryModel.cpp index 4ded6fed..12f4658e 100644 --- a/client/ui/models/api/apiCountryModel.cpp +++ b/client/ui/models/api/apiCountryModel.cpp @@ -44,6 +44,9 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const case IsIssuedRole: { return isIssued; } + case IsWorkerExpiredRole: { + return issuedConfigInfo.lastDownloaded < issuedConfigInfo.workerLastUpdated; + } } return QVariant(); @@ -114,5 +117,6 @@ QHash ApiCountryModel::roleNames() const roles[CountryCodeRole] = "countryCode"; roles[CountryImageCodeRole] = "countryImageCode"; roles[IsIssuedRole] = "isIssued"; + roles[IsWorkerExpiredRole] = "isWorkerExpired"; return roles; } diff --git a/client/ui/models/api/apiCountryModel.h b/client/ui/models/api/apiCountryModel.h index e57ec0dd..08ac3685 100644 --- a/client/ui/models/api/apiCountryModel.h +++ b/client/ui/models/api/apiCountryModel.h @@ -14,7 +14,8 @@ public: CountryNameRole = Qt::UserRole + 1, CountryCodeRole, CountryImageCodeRole, - IsIssuedRole + IsIssuedRole, + IsWorkerExpiredRole }; explicit ApiCountryModel(QObject *parent = nullptr); diff --git a/client/ui/models/api/apiDevicesModel.cpp b/client/ui/models/api/apiDevicesModel.cpp new file mode 100644 index 00000000..6c0d60d0 --- /dev/null +++ b/client/ui/models/api/apiDevicesModel.cpp @@ -0,0 +1,90 @@ +#include "apiDevicesModel.h" + +#include + +#include "core/api/apiDefs.h" +#include "logger.h" + +namespace +{ + Logger logger("ApiDevicesModel"); + + constexpr QLatin1String gatewayAccount("gateway_account"); +} + +ApiDevicesModel::ApiDevicesModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) +{ +} + +int ApiDevicesModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_issuedConfigs.size(); +} + +QVariant ApiDevicesModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) + return QVariant(); + + IssuedConfigInfo issuedConfigInfo = m_issuedConfigs.at(index.row()); + + switch (role) { + case OsVersionRole: { + return issuedConfigInfo.osVersion; + } + case SupportTagRole: { + return issuedConfigInfo.installationUuid; + } + case CountryCodeRole: { + return issuedConfigInfo.countryCode; + } + case LastUpdateRole: { + return QDateTime::fromString(issuedConfigInfo.lastDownloaded, Qt::ISODate).toLocalTime().toString("d MMM yyyy"); + } + case IsCurrentDeviceRole: { + return issuedConfigInfo.installationUuid == m_settings->getInstallationUuid(false); + } + } + + return QVariant(); +} + +void ApiDevicesModel::updateModel(const QJsonArray &issuedConfigs) +{ + beginResetModel(); + + m_issuedConfigs.clear(); + for (int i = 0; i < issuedConfigs.size(); i++) { + IssuedConfigInfo issuedConfigInfo; + QJsonObject issuedConfigObject = issuedConfigs.at(i).toObject(); + + if (issuedConfigObject.value(apiDefs::key::sourceType).toString() != gatewayAccount) { + continue; + } + + issuedConfigInfo.installationUuid = issuedConfigObject.value(apiDefs::key::installationUuid).toString(); + issuedConfigInfo.workerLastUpdated = issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString(); + issuedConfigInfo.lastDownloaded = issuedConfigObject.value(apiDefs::key::lastDownloaded).toString(); + issuedConfigInfo.sourceType = issuedConfigObject.value(apiDefs::key::sourceType).toString(); + issuedConfigInfo.osVersion = issuedConfigObject.value(apiDefs::key::osVersion).toString(); + + issuedConfigInfo.countryName = issuedConfigObject.value(apiDefs::key::serverCountryName).toString(); + issuedConfigInfo.countryCode = issuedConfigObject.value(apiDefs::key::serverCountryCode).toString(); + + m_issuedConfigs.push_back(issuedConfigInfo); + } + + endResetModel(); +} + +QHash ApiDevicesModel::roleNames() const +{ + QHash roles; + roles[OsVersionRole] = "osVersion"; + roles[SupportTagRole] = "supportTag"; + roles[CountryCodeRole] = "countryCode"; + roles[LastUpdateRole] = "lastUpdate"; + roles[IsCurrentDeviceRole] = "isCurrentDevice"; + return roles; +} diff --git a/client/ui/models/api/apiDevicesModel.h b/client/ui/models/api/apiDevicesModel.h new file mode 100644 index 00000000..e6a59dba --- /dev/null +++ b/client/ui/models/api/apiDevicesModel.h @@ -0,0 +1,52 @@ +#ifndef APIDEVICESMODEL_H +#define APIDEVICESMODEL_H + +#include +#include +#include + +#include "settings.h" + +class ApiDevicesModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + OsVersionRole = Qt::UserRole + 1, + SupportTagRole, + CountryCodeRole, + LastUpdateRole, + IsCurrentDeviceRole + }; + + explicit ApiDevicesModel(std::shared_ptr settings, QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonArray &issuedConfigs); + +protected: + QHash roleNames() const override; + +private: + struct IssuedConfigInfo + { + QString installationUuid; + QString workerLastUpdated; + QString lastDownloaded; + QString sourceType; + QString osVersion; + + QString countryName; + QString countryCode; + }; + + QVector m_issuedConfigs; + + std::shared_ptr m_settings; +}; +#endif // APIDEVICESMODEL_H diff --git a/client/ui/qml/Modules/Style/AmneziaStyle.qml b/client/ui/qml/Modules/Style/AmneziaStyle.qml index f54fefce..4e2e80f0 100644 --- a/client/ui/qml/Modules/Style/AmneziaStyle.qml +++ b/client/ui/qml/Modules/Style/AmneziaStyle.qml @@ -27,5 +27,6 @@ QtObject { readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8) readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65) readonly property color pearlGray: '#EAEAEC' + readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26) } } diff --git a/client/ui/qml/Pages2/PageSettingsApiDevices.qml b/client/ui/qml/Pages2/PageSettingsApiDevices.qml new file mode 100644 index 00000000..5cc21d07 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsApiDevices.qml @@ -0,0 +1,100 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ListViewType { + id: listView + + anchors.fill: parent + anchors.topMargin: 20 + anchors.bottomMargin: 24 + + model: ApiDevicesModel + + header: ColumnLayout { + width: listView.width + + BackButtonType { + id: backButton + } + + HeaderType { + id: header + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Connected devices") + descriptionText: qsTr("To manage connected devices") + } + + WarningType { + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.fillWidth: true + + textString: qsTr("You can find the identifier on the Support tab or, for older versions of the app, " + + "by tapping '+' and then the three dots at the top of the page.") + + iconPath: "qrc:/images/controls/alert-circle.svg" + } + } + + delegate: ColumnLayout { + width: listView.width + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 6 + + text: osVersion + (isCurrentDevice ? qsTr(" (current device)") : "") + descriptionText: qsTr("Support tag: ") + "\n" + supportTag + "\n" + qsTr("Last updated: ") + lastUpdate + rightImageSource: "qrc:/images/controls/trash.svg" + + clickedFunction: function() { + var headerText = qsTr("Deactivate the subscription on selected device") + var descriptionText = qsTr("The next time the “Connect” button is pressed, the device will be activated again") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + Qt.callLater(deactivateExternalDevice, supportTag, countryCode) + } + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + DividerType {} + } + } + + function deactivateExternalDevice(supportTag, countryCode) { + PageController.showBusyIndicator(true) + if (ApiConfigsController.deactivateExternalDevice(supportTag, countryCode)) { + ApiSettingsController.getAccountInfo(true) + } + PageController.showBusyIndicator(false) + } +} diff --git a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml index 70a0df74..3651407b 100644 --- a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml +++ b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml @@ -107,7 +107,6 @@ PageType { width: listView.width LabelWithButtonType { - id: telegramButton Layout.fillWidth: true Layout.topMargin: 6 diff --git a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml index 7fa2b8f4..a1cc1fb8 100644 --- a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml @@ -58,6 +58,9 @@ PageType { Layout.topMargin: 6 text: countryName + descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : "" + descriptionColor: AmneziaStyle.color.vibrantRed + leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg" diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 44ed6f4b..7d089639 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -28,7 +28,7 @@ PageType { readonly property string title: qsTr("Subscription status") readonly property string contentKey: "subscriptionStatus" - readonly property string objectImageSource: "qrc:/images/controls/map-pin.svg" + readonly property string objectImageSource: "qrc:/images/controls/info.svg" } QtObject { @@ -44,7 +44,7 @@ PageType { readonly property string title: qsTr("Connected devices") readonly property string contentKey: "connectedDevices" - readonly property string objectImageSource: "qrc:/images/controls/gauge.svg" + readonly property string objectImageSource: "qrc:/images/controls/monitor.svg" } property var processedServer @@ -158,11 +158,28 @@ PageType { readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible") + WarningType { + id: warning + + Layout.topMargin: 32 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.fillWidth: true + + backGroundColor: AmneziaStyle.color.translucentRichBrown + + textString: qsTr("Configurations have been updated for some countries. Download and install the updated configuration files") + + iconPath: "qrc:/images/controls/alert-circle.svg" + + visible: ApiAccountInfoModel.data("hasExpiredWorker") + } + LabelWithButtonType { id: vpnKey Layout.fillWidth: true - Layout.topMargin: 32 + Layout.topMargin: warning.visible ? 16 : 32 visible: false //footer.isVisibleForAmneziaFree @@ -192,7 +209,7 @@ PageType { LabelWithButtonType { Layout.fillWidth: true - Layout.topMargin: 32 + Layout.topMargin: warning.visible ? 16 : 32 visible: footer.isVisibleForAmneziaFree @@ -211,6 +228,26 @@ PageType { visible: footer.isVisibleForAmneziaFree } + LabelWithButtonType { + Layout.fillWidth: true + + visible: footer.isVisibleForAmneziaFree + + text: qsTr("Connected devices") + + descriptionText: qsTr("To manage connected devices") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + ApiSettingsController.updateApiDevicesModel() + PageController.goToPage(PageEnum.PageSettingsApiDevices) + } + } + + DividerType { + visible: footer.isVisibleForAmneziaFree + } + LabelWithButtonType { Layout.fillWidth: true Layout.topMargin: footer.isVisibleForAmneziaFree ? 0 : 32 @@ -292,12 +329,13 @@ PageType { clickedFunc: function() { var headerText = qsTr("Deactivate the subscription on this device?") + var descriptionText = qsTr("The next time the “Connect” button is pressed, the device will be activated again") var yesButtonText = qsTr("Continue") var noButtonText = qsTr("Cancel") var yesButtonFunction = function() { if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { - PageController.showNotificationMessage(qsTr("The next time the “Connect” button is pressed, the device will be activated again")) + PageController.showNotificationMessage(qsTr("Cannot deactivate subscription during active connection")) } else { PageController.showBusyIndicator(true) if (ApiConfigsController.deactivateDevice()) { @@ -309,7 +347,7 @@ PageType { var noButtonFunction = function() { } - showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } } diff --git a/client/ui/qml/Pages2/PageSettingsApiSupport.qml b/client/ui/qml/Pages2/PageSettingsApiSupport.qml index 64921a61..424e10c5 100644 --- a/client/ui/qml/Pages2/PageSettingsApiSupport.qml +++ b/client/ui/qml/Pages2/PageSettingsApiSupport.qml @@ -16,92 +16,110 @@ import "../Components" PageType { id: root - ColumnLayout { - id: backButtonLayout + QtObject { + id: telegram - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + readonly property string title: qsTr("Telegram") + readonly property string description: "@" + ApiAccountInfoModel.getTelegramBotLink() + readonly property string link: "https://t.me/" + ApiAccountInfoModel.getTelegramBotLink() + } + QtObject { + id: techSupport + + readonly property string title: qsTr("For technical support") + readonly property string description: qsTr("support@amnezia.org") + readonly property string link: "mailto:support@amnezia.org" + } + + QtObject { + id: paymentSupport + + readonly property string title: qsTr("For payment issues") + readonly property string description: qsTr("help@vpnpay.io") + readonly property string link: "mailto:help@vpnpay.io" + } + + QtObject { + id: site + + readonly property string title: qsTr("Site") + readonly property string description: qsTr("amnezia.org") + readonly property string link: LanguageModel.getCurrentSiteUrl() + } + + property list supportModel: [ + telegram, + techSupport, + paymentSupport, + site + ] + + ListViewType { + id: listView + + anchors.fill: parent anchors.topMargin: 20 + anchors.bottomMargin: 24 - BackButtonType { - id: backButton - } + model: supportModel + header: ColumnLayout { + width: listView.width - HeaderType { - id: header + BackButtonType { + id: backButton + } - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 + HeaderType { + id: header - headerText: qsTr("Support") - descriptionText: qsTr("Our technical support specialists are ready to help you at any time") - } + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 - LabelWithButtonType { - Layout.fillWidth: true - - readonly property string telegramBotLink: ApiAccountInfoModel.getTelegramBotLink() - - text: qsTr("Telegram") - descriptionText: "@" + telegramBotLink - rightImageSource: "qrc:/images/controls/external-link.svg" - - clickedFunction: function() { - Qt.openUrlExternally("https://t.me/" + telegramBotLink) + headerText: qsTr("Support") + descriptionText: qsTr("Our technical support specialists are ready to help you at any time") } } - DividerType {} + delegate: ColumnLayout { + width: listView.width - LabelWithButtonType { - Layout.fillWidth: true - - text: qsTr("Mail") - descriptionText: qsTr("support@amnezia.org") - rightImageSource: "qrc:/images/controls/external-link.svg" - - clickedFunction: function() { - Qt.openUrlExternally(qsTr("mailto:support@amnezia.org")) + LabelWithButtonType { + Layout.fillWidth: true + text: title + descriptionText: description + rightImageSource: "qrc:/images/controls/external-link.svg" + clickedFunction: function() { + Qt.openUrlExternally(link) + } } + DividerType {} } - DividerType {} - LabelWithButtonType { - Layout.fillWidth: true + footer: ColumnLayout { + width: listView.width - text: qsTr("Site") - descriptionText: qsTr("amnezia.org") - rightImageSource: "qrc:/images/controls/external-link.svg" + LabelWithButtonType { + id: supportUuid + Layout.fillWidth: true - clickedFunction: function() { - Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) - } - } + text: qsTr("Support tag") + descriptionText: SettingsController.getInstallationUuid() - DividerType {} + descriptionOnTop: true - LabelWithButtonType { - id: supportUuid - Layout.fillWidth: true + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: AmneziaStyle.color.paleGray - text: qsTr("Support tag") - descriptionText: SettingsController.getInstallationUuid() - - descriptionOnTop: true - - rightImageSource: "qrc:/images/controls/copy.svg" - rightImageColor: AmneziaStyle.color.paleGray - - clickedFunction: function() { - GC.copyToClipBoard(descriptionText) - PageController.showNotificationMessage(qsTr("Copied")) - if (!GC.isMobile()) { - this.rightButton.forceActiveFocus() + clickedFunction: function() { + GC.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) + if (!GC.isMobile()) { + this.rightButton.forceActiveFocus() + } } } }