Merge pull request #1440 from amnezia-vpn/feature/subscription-settings-page
feature/subscription settings page
This commit is contained in:
parent
7ccbfa48bc
commit
728b48044c
23 changed files with 466 additions and 81 deletions
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.8.4.1
|
project(${PROJECT} VERSION 4.8.4.2
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
|
@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
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")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|
|
@ -93,6 +93,9 @@ void CoreController::initModels()
|
||||||
|
|
||||||
m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this));
|
m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get());
|
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()
|
void CoreController::initControllers()
|
||||||
|
@ -132,7 +135,8 @@ void CoreController::initControllers()
|
||||||
m_systemController.reset(new SystemController(m_settings));
|
m_systemController.reset(new SystemController(m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
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_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
|
||||||
|
|
||||||
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
||||||
|
|
|
@ -25,8 +25,9 @@
|
||||||
#include "ui/models/protocols/ikev2ConfigModel.h"
|
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||||
#endif
|
#endif
|
||||||
#include "ui/models/api/apiAccountInfoModel.h"
|
#include "ui/models/api/apiAccountInfoModel.h"
|
||||||
#include "ui/models/api/apiServicesModel.h"
|
|
||||||
#include "ui/models/api/apiCountryModel.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/appSplitTunnelingModel.h"
|
||||||
#include "ui/models/clientManagementModel.h"
|
#include "ui/models/clientManagementModel.h"
|
||||||
#include "ui/models/protocols/awgConfigModel.h"
|
#include "ui/models/protocols/awgConfigModel.h"
|
||||||
|
@ -117,6 +118,7 @@ private:
|
||||||
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||||
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||||
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
||||||
|
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
|
||||||
|
|
||||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||||
|
|
|
@ -148,7 +148,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||||
|
|
||||||
QByteArray encryptedResponseBody = reply->readAll();
|
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) {
|
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
||||||
request.setUrl(url);
|
request.setUrl(url);
|
||||||
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||||
|
|
5
client/images/controls/monitor.svg
Normal file
5
client/images/controls/monitor.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20 3H4C2.89543 3 2 3.89543 2 5V15C2 16.1046 2.89543 17 4 17H20C21.1046 17 22 16.1046 22 15V5C22 3.89543 21.1046 3 20 3Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M8 21H16" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 17V21" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 522 B |
|
@ -229,6 +229,8 @@
|
||||||
<file>ui/qml/Pages2/PageSettingsApiSupport.qml</file>
|
<file>ui/qml/Pages2/PageSettingsApiSupport.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file>
|
<file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
|
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
|
||||||
|
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
|
||||||
|
<file>images/controls/monitor.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/countriesFlags">
|
<qresource prefix="/countriesFlags">
|
||||||
<file>images/flagKit/ZW.svg</file>
|
<file>images/flagKit/ZW.svg</file>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#include "apiConfigsController.h"
|
#include "apiConfigsController.h"
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
|
#include <QEventLoop>
|
||||||
|
|
||||||
#include "amnezia_application.h"
|
#include "amnezia_application.h"
|
||||||
#include "configurators/wireguard_configurator.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::apiConfig, newApiConfig);
|
||||||
newServerConfig.insert(configKey::authData, authData);
|
newServerConfig.insert(configKey::authData, authData);
|
||||||
|
// newServerConfig.insert(
|
||||||
|
|
||||||
m_serversModel->editServer(newServerConfig, serverIndex);
|
m_serversModel->editServer(newServerConfig, serverIndex);
|
||||||
if (reloadServiceConfig) {
|
if (reloadServiceConfig) {
|
||||||
|
@ -354,6 +355,43 @@ bool ApiConfigsController::deactivateDevice()
|
||||||
return true;
|
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()
|
bool ApiConfigsController::isConfigValid()
|
||||||
{
|
{
|
||||||
int serverIndex = m_serversModel->getDefaultServerIndex();
|
int serverIndex = m_serversModel->getDefaultServerIndex();
|
||||||
|
|
|
@ -31,6 +31,7 @@ public slots:
|
||||||
bool reloadServiceConfig = false);
|
bool reloadServiceConfig = false);
|
||||||
bool updateServiceFromTelegram(const int serverIndex);
|
bool updateServiceFromTelegram(const int serverIndex);
|
||||||
bool deactivateDevice();
|
bool deactivateDevice();
|
||||||
|
bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode);
|
||||||
|
|
||||||
bool isConfigValid();
|
bool isConfigValid();
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,13 @@ namespace
|
||||||
ApiSettingsController::ApiSettingsController(const QSharedPointer<ServersModel> &serversModel,
|
ApiSettingsController::ApiSettingsController(const QSharedPointer<ServersModel> &serversModel,
|
||||||
const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
|
const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
|
||||||
const QSharedPointer<ApiCountryModel> &apiCountryModel,
|
const QSharedPointer<ApiCountryModel> &apiCountryModel,
|
||||||
|
const QSharedPointer<ApiDevicesModel> &apiDevicesModel,
|
||||||
const std::shared_ptr<Settings> &settings, QObject *parent)
|
const std::shared_ptr<Settings> &settings, QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
m_serversModel(serversModel),
|
m_serversModel(serversModel),
|
||||||
m_apiAccountInfoModel(apiAccountInfoModel),
|
m_apiAccountInfoModel(apiAccountInfoModel),
|
||||||
m_apiCountryModel(apiCountryModel),
|
m_apiCountryModel(apiCountryModel),
|
||||||
|
m_apiDevicesModel(apiDevicesModel),
|
||||||
m_settings(settings)
|
m_settings(settings)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -73,6 +75,7 @@ bool ApiSettingsController::getAccountInfo(bool reload)
|
||||||
|
|
||||||
if (reload) {
|
if (reload) {
|
||||||
updateApiCountryModel();
|
updateApiCountryModel();
|
||||||
|
updateApiDevicesModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -83,3 +86,8 @@ void ApiSettingsController::updateApiCountryModel()
|
||||||
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
|
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
|
||||||
m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo());
|
m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApiSettingsController::updateApiDevicesModel()
|
||||||
|
{
|
||||||
|
m_apiDevicesModel->updateModel(m_apiAccountInfoModel->getIssuedConfigsInfo());
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include "ui/models/api/apiAccountInfoModel.h"
|
#include "ui/models/api/apiAccountInfoModel.h"
|
||||||
#include "ui/models/api/apiCountryModel.h"
|
#include "ui/models/api/apiCountryModel.h"
|
||||||
|
#include "ui/models/api/apiDevicesModel.h"
|
||||||
#include "ui/models/servers_model.h"
|
#include "ui/models/servers_model.h"
|
||||||
|
|
||||||
class ApiSettingsController : public QObject
|
class ApiSettingsController : public QObject
|
||||||
|
@ -12,13 +13,14 @@ class ApiSettingsController : public QObject
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ApiSettingsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
|
ApiSettingsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
|
||||||
const QSharedPointer<ApiCountryModel> &apiCountryModel, const std::shared_ptr<Settings> &settings,
|
const QSharedPointer<ApiCountryModel> &apiCountryModel, const QSharedPointer<ApiDevicesModel> &apiDevicesModel,
|
||||||
QObject *parent = nullptr);
|
const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
|
||||||
~ApiSettingsController();
|
~ApiSettingsController();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
bool getAccountInfo(bool reload);
|
bool getAccountInfo(bool reload);
|
||||||
void updateApiCountryModel();
|
void updateApiCountryModel();
|
||||||
|
void updateApiDevicesModel();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void errorOccurred(ErrorCode errorCode);
|
void errorOccurred(ErrorCode errorCode);
|
||||||
|
@ -27,6 +29,7 @@ private:
|
||||||
QSharedPointer<ServersModel> m_serversModel;
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
||||||
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||||
|
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
|
||||||
|
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,6 +36,7 @@ namespace PageLoader
|
||||||
PageSettingsApiSupport,
|
PageSettingsApiSupport,
|
||||||
PageSettingsApiInstructions,
|
PageSettingsApiInstructions,
|
||||||
PageSettingsApiNativeConfigs,
|
PageSettingsApiNativeConfigs,
|
||||||
|
PageSettingsApiDevices,
|
||||||
|
|
||||||
PageServiceSftpSettings,
|
PageServiceSftpSettings,
|
||||||
PageServiceTorWebsiteSettings,
|
PageServiceTorWebsiteSettings,
|
||||||
|
|
|
@ -58,6 +58,19 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
||||||
case IsComponentVisibleRole: {
|
case IsComponentVisibleRole: {
|
||||||
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2;
|
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();
|
return QVariant();
|
||||||
|
@ -124,6 +137,7 @@ QHash<int, QByteArray> ApiAccountInfoModel::roleNames() const
|
||||||
roles[ConnectedDevicesRole] = "connectedDevices";
|
roles[ConnectedDevicesRole] = "connectedDevices";
|
||||||
roles[ServiceDescriptionRole] = "serviceDescription";
|
roles[ServiceDescriptionRole] = "serviceDescription";
|
||||||
roles[IsComponentVisibleRole] = "isComponentVisible";
|
roles[IsComponentVisibleRole] = "isComponentVisible";
|
||||||
|
roles[HasExpiredWorkerRole] = "hasExpiredWorker";
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@ public:
|
||||||
ConnectedDevicesRole,
|
ConnectedDevicesRole,
|
||||||
ServiceDescriptionRole,
|
ServiceDescriptionRole,
|
||||||
EndDateRole,
|
EndDateRole,
|
||||||
IsComponentVisibleRole
|
IsComponentVisibleRole,
|
||||||
|
HasExpiredWorkerRole
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ApiAccountInfoModel(QObject *parent = nullptr);
|
explicit ApiAccountInfoModel(QObject *parent = nullptr);
|
||||||
|
|
|
@ -44,6 +44,9 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const
|
||||||
case IsIssuedRole: {
|
case IsIssuedRole: {
|
||||||
return isIssued;
|
return isIssued;
|
||||||
}
|
}
|
||||||
|
case IsWorkerExpiredRole: {
|
||||||
|
return issuedConfigInfo.lastDownloaded < issuedConfigInfo.workerLastUpdated;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
@ -114,5 +117,6 @@ QHash<int, QByteArray> ApiCountryModel::roleNames() const
|
||||||
roles[CountryCodeRole] = "countryCode";
|
roles[CountryCodeRole] = "countryCode";
|
||||||
roles[CountryImageCodeRole] = "countryImageCode";
|
roles[CountryImageCodeRole] = "countryImageCode";
|
||||||
roles[IsIssuedRole] = "isIssued";
|
roles[IsIssuedRole] = "isIssued";
|
||||||
|
roles[IsWorkerExpiredRole] = "isWorkerExpired";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,8 @@ public:
|
||||||
CountryNameRole = Qt::UserRole + 1,
|
CountryNameRole = Qt::UserRole + 1,
|
||||||
CountryCodeRole,
|
CountryCodeRole,
|
||||||
CountryImageCodeRole,
|
CountryImageCodeRole,
|
||||||
IsIssuedRole
|
IsIssuedRole,
|
||||||
|
IsWorkerExpiredRole
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ApiCountryModel(QObject *parent = nullptr);
|
explicit ApiCountryModel(QObject *parent = nullptr);
|
||||||
|
|
90
client/ui/models/api/apiDevicesModel.cpp
Normal file
90
client/ui/models/api/apiDevicesModel.cpp
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
#include "apiDevicesModel.h"
|
||||||
|
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
#include "core/api/apiDefs.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
Logger logger("ApiDevicesModel");
|
||||||
|
|
||||||
|
constexpr QLatin1String gatewayAccount("gateway_account");
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiDevicesModel::ApiDevicesModel(std::shared_ptr<Settings> 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<int>(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<int, QByteArray> ApiDevicesModel::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> roles;
|
||||||
|
roles[OsVersionRole] = "osVersion";
|
||||||
|
roles[SupportTagRole] = "supportTag";
|
||||||
|
roles[CountryCodeRole] = "countryCode";
|
||||||
|
roles[LastUpdateRole] = "lastUpdate";
|
||||||
|
roles[IsCurrentDeviceRole] = "isCurrentDevice";
|
||||||
|
return roles;
|
||||||
|
}
|
52
client/ui/models/api/apiDevicesModel.h
Normal file
52
client/ui/models/api/apiDevicesModel.h
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
#ifndef APIDEVICESMODEL_H
|
||||||
|
#define APIDEVICESMODEL_H
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
#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> 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<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct IssuedConfigInfo
|
||||||
|
{
|
||||||
|
QString installationUuid;
|
||||||
|
QString workerLastUpdated;
|
||||||
|
QString lastDownloaded;
|
||||||
|
QString sourceType;
|
||||||
|
QString osVersion;
|
||||||
|
|
||||||
|
QString countryName;
|
||||||
|
QString countryCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
QVector<IssuedConfigInfo> m_issuedConfigs;
|
||||||
|
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
};
|
||||||
|
#endif // APIDEVICESMODEL_H
|
|
@ -27,5 +27,6 @@ QtObject {
|
||||||
readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8)
|
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 cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
|
||||||
readonly property color pearlGray: '#EAEAEC'
|
readonly property color pearlGray: '#EAEAEC'
|
||||||
|
readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
100
client/ui/qml/Pages2/PageSettingsApiDevices.qml
Normal file
100
client/ui/qml/Pages2/PageSettingsApiDevices.qml
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -107,7 +107,6 @@ PageType {
|
||||||
width: listView.width
|
width: listView.width
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
id: telegramButton
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 6
|
Layout.topMargin: 6
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,9 @@ PageType {
|
||||||
Layout.topMargin: 6
|
Layout.topMargin: 6
|
||||||
|
|
||||||
text: countryName
|
text: countryName
|
||||||
|
descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : ""
|
||||||
|
descriptionColor: AmneziaStyle.color.vibrantRed
|
||||||
|
|
||||||
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
|
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
|
||||||
rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg"
|
rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg"
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ PageType {
|
||||||
|
|
||||||
readonly property string title: qsTr("Subscription status")
|
readonly property string title: qsTr("Subscription status")
|
||||||
readonly property string contentKey: "subscriptionStatus"
|
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 {
|
QtObject {
|
||||||
|
@ -44,7 +44,7 @@ PageType {
|
||||||
|
|
||||||
readonly property string title: qsTr("Connected devices")
|
readonly property string title: qsTr("Connected devices")
|
||||||
readonly property string contentKey: "connectedDevices"
|
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
|
property var processedServer
|
||||||
|
@ -158,11 +158,28 @@ PageType {
|
||||||
|
|
||||||
readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible")
|
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 {
|
LabelWithButtonType {
|
||||||
id: vpnKey
|
id: vpnKey
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 32
|
Layout.topMargin: warning.visible ? 16 : 32
|
||||||
|
|
||||||
visible: false //footer.isVisibleForAmneziaFree
|
visible: false //footer.isVisibleForAmneziaFree
|
||||||
|
|
||||||
|
@ -192,7 +209,7 @@ PageType {
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 32
|
Layout.topMargin: warning.visible ? 16 : 32
|
||||||
|
|
||||||
visible: footer.isVisibleForAmneziaFree
|
visible: footer.isVisibleForAmneziaFree
|
||||||
|
|
||||||
|
@ -211,6 +228,26 @@ PageType {
|
||||||
visible: footer.isVisibleForAmneziaFree
|
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 {
|
LabelWithButtonType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: footer.isVisibleForAmneziaFree ? 0 : 32
|
Layout.topMargin: footer.isVisibleForAmneziaFree ? 0 : 32
|
||||||
|
@ -292,12 +329,13 @@ PageType {
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
var headerText = qsTr("Deactivate the subscription on this device?")
|
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 yesButtonText = qsTr("Continue")
|
||||||
var noButtonText = qsTr("Cancel")
|
var noButtonText = qsTr("Cancel")
|
||||||
|
|
||||||
var yesButtonFunction = function() {
|
var yesButtonFunction = function() {
|
||||||
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
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 {
|
} else {
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
if (ApiConfigsController.deactivateDevice()) {
|
if (ApiConfigsController.deactivateDevice()) {
|
||||||
|
@ -309,7 +347,7 @@ PageType {
|
||||||
var noButtonFunction = function() {
|
var noButtonFunction = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,92 +16,110 @@ import "../Components"
|
||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
ColumnLayout {
|
QtObject {
|
||||||
id: backButtonLayout
|
id: telegram
|
||||||
|
|
||||||
anchors.top: parent.top
|
readonly property string title: qsTr("Telegram")
|
||||||
anchors.left: parent.left
|
readonly property string description: "@" + ApiAccountInfoModel.getTelegramBotLink()
|
||||||
anchors.right: parent.right
|
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<QtObject> supportModel: [
|
||||||
|
telegram,
|
||||||
|
techSupport,
|
||||||
|
paymentSupport,
|
||||||
|
site
|
||||||
|
]
|
||||||
|
|
||||||
|
ListViewType {
|
||||||
|
id: listView
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20
|
||||||
|
anchors.bottomMargin: 24
|
||||||
|
|
||||||
BackButtonType {
|
model: supportModel
|
||||||
id: backButton
|
|
||||||
}
|
|
||||||
|
|
||||||
|
header: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
HeaderType {
|
BackButtonType {
|
||||||
id: header
|
id: backButton
|
||||||
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
HeaderType {
|
||||||
Layout.rightMargin: 16
|
id: header
|
||||||
Layout.leftMargin: 16
|
|
||||||
|
|
||||||
headerText: qsTr("Support")
|
Layout.fillWidth: true
|
||||||
descriptionText: qsTr("Our technical support specialists are ready to help you at any time")
|
Layout.rightMargin: 16
|
||||||
}
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
LabelWithButtonType {
|
headerText: qsTr("Support")
|
||||||
Layout.fillWidth: true
|
descriptionText: qsTr("Our technical support specialists are ready to help you at any time")
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DividerType {}
|
delegate: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
text: title
|
||||||
text: qsTr("Mail")
|
descriptionText: description
|
||||||
descriptionText: qsTr("support@amnezia.org")
|
rightImageSource: "qrc:/images/controls/external-link.svg"
|
||||||
rightImageSource: "qrc:/images/controls/external-link.svg"
|
clickedFunction: function() {
|
||||||
|
Qt.openUrlExternally(link)
|
||||||
clickedFunction: function() {
|
}
|
||||||
Qt.openUrlExternally(qsTr("mailto:support@amnezia.org"))
|
|
||||||
}
|
}
|
||||||
|
DividerType {}
|
||||||
}
|
}
|
||||||
|
|
||||||
DividerType {}
|
|
||||||
|
|
||||||
LabelWithButtonType {
|
footer: ColumnLayout {
|
||||||
Layout.fillWidth: true
|
width: listView.width
|
||||||
|
|
||||||
text: qsTr("Site")
|
LabelWithButtonType {
|
||||||
descriptionText: qsTr("amnezia.org")
|
id: supportUuid
|
||||||
rightImageSource: "qrc:/images/controls/external-link.svg"
|
Layout.fillWidth: true
|
||||||
|
|
||||||
clickedFunction: function() {
|
text: qsTr("Support tag")
|
||||||
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())
|
descriptionText: SettingsController.getInstallationUuid()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DividerType {}
|
descriptionOnTop: true
|
||||||
|
|
||||||
LabelWithButtonType {
|
rightImageSource: "qrc:/images/controls/copy.svg"
|
||||||
id: supportUuid
|
rightImageColor: AmneziaStyle.color.paleGray
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: qsTr("Support tag")
|
clickedFunction: function() {
|
||||||
descriptionText: SettingsController.getInstallationUuid()
|
GC.copyToClipBoard(descriptionText)
|
||||||
|
PageController.showNotificationMessage(qsTr("Copied"))
|
||||||
descriptionOnTop: true
|
if (!GC.isMobile()) {
|
||||||
|
this.rightButton.forceActiveFocus()
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue