Merge branch 'dev' into feature/killswitch-strict-mode

This commit is contained in:
Mykola Baibuz 2025-03-05 20:29:17 +02:00
commit 8edc60e574
16 changed files with 985 additions and 424 deletions

View file

@ -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.2 project(${PROJECT} VERSION 4.8.4.3
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 2079) set(APP_ANDROID_VERSION_CODE 2080)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")

View file

@ -157,12 +157,12 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt, auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) { this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
encryptedResponseBody = nestedReply->readAll(); encryptedResponseBody = nestedReply->readAll();
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
sslErrors = nestedSslErrors;
reply = nestedReply; reply = nestedReply;
return true; if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
} sslErrors = nestedSslErrors;
return false; return false;
}
return true;
}; };
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction); bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
@ -212,11 +212,6 @@ QStringList GatewayController::getProxyUrls()
wait.exec(); wait.exec();
if (reply->error() == QNetworkReply::NetworkError::NoError) { if (reply->error() == QNetworkReply::NetworkError::NoError) {
break;
}
reply->deleteLater();
}
auto encryptedResponseBody = reply->readAll(); auto encryptedResponseBody = reply->readAll();
reply->deleteLater(); reply->deleteLater();
@ -241,7 +236,7 @@ QStringList GatewayController::getProxyUrls()
} catch (...) { } catch (...) {
Utils::logException(); Utils::logException();
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody; qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
return {}; continue;
} }
auto endpointsArray = QJsonDocument::fromJson(responseBody).array(); auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
@ -251,6 +246,11 @@ QStringList GatewayController::getProxyUrls()
endpoints.push_back(endpoint.toString()); endpoints.push_back(endpoint.toString());
} }
return endpoints; return endpoints;
} else {
reply->deleteLater();
}
}
return {};
} }
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key, bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
@ -262,7 +262,7 @@ bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray
} else if (responseBody.contains("html")) { } else if (responseBody.contains("html")) {
qDebug() << "The response contains an html tag"; qDebug() << "The response contains an html tag";
return true; return true;
} else if (checkEncryption) { } else if (reply->error() == QNetworkReply::NetworkError::NoError && checkEncryption) {
try { try {
QSimpleCrypto::QBlockCipher blockCipher; QSimpleCrypto::QBlockCipher blockCipher;
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt)); static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
@ -296,7 +296,7 @@ void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *repl
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; }); connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(); wait.exec();
if (!replyProcessingFunction(reply, sslErrors)) { if (replyProcessingFunction(reply, sslErrors)) {
break; break;
} }
} }

View file

@ -14,10 +14,15 @@ extension UIApplication {
var keyWindows: [UIWindow] { var keyWindows: [UIWindow] {
connectedScenes connectedScenes
.compactMap { .compactMap {
guard let windowScene = $0 as? UIWindowScene else { return nil }
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {
($0 as? UIWindowScene)?.keyWindow guard let keywindow = windowScene.keyWindow else {
windowScene.windows.first?.makeKey()
return windowScene.windows.first
}
return keywindow
} else { } else {
($0 as? UIWindowScene)?.windows.first { $0.isKeyWindow } return windowScene.windows.first { $0.isKeyWindow }
} }
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -407,7 +407,7 @@ bool ApiConfigsController::isConfigValid()
return updateServiceFromGateway(serverIndex, "", ""); return updateServiceFromGateway(serverIndex, "", "");
} else if (configSource && m_serversModel->isApiKeyExpired(serverIndex)) { } else if (configSource && m_serversModel->isApiKeyExpired(serverIndex)) {
qDebug() << "attempt to update api config by expires_at event"; qDebug() << "attempt to update api config by expires_at event";
if (configSource == apiDefs::ConfigSource::Telegram) { if (configSource == apiDefs::ConfigSource::AmneziaGateway) {
return updateServiceFromGateway(serverIndex, "", ""); return updateServiceFromGateway(serverIndex, "", "");
} else { } else {
m_serversModel->removeApiConfig(serverIndex); m_serversModel->removeApiConfig(serverIndex);

View file

@ -27,8 +27,6 @@ namespace
ConfigTypes checkConfigFormat(const QString &config) ConfigTypes checkConfigFormat(const QString &config)
{ {
const QString openVpnConfigPatternCli = "client"; const QString openVpnConfigPatternCli = "client";
const QString openVpnConfigPatternProto1 = "proto tcp";
const QString openVpnConfigPatternProto2 = "proto udp";
const QString openVpnConfigPatternDriver1 = "dev tun"; const QString openVpnConfigPatternDriver1 = "dev tun";
const QString openVpnConfigPatternDriver2 = "dev tap"; const QString openVpnConfigPatternDriver2 = "dev tap";
@ -53,14 +51,13 @@ namespace
|| (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName) || (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
&& config.contains(amneziaConfigPatternPassword))) { && config.contains(amneziaConfigPatternPassword))) {
return ConfigTypes::Amnezia; return ConfigTypes::Amnezia;
} else if (config.contains(openVpnConfigPatternCli)
&& (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2))
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
return ConfigTypes::OpenVpn;
} else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) { } else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) {
return ConfigTypes::WireGuard; return ConfigTypes::WireGuard;
} else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) { } else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) {
return ConfigTypes::Xray; return ConfigTypes::Xray;
} else if (config.contains(openVpnConfigPatternCli)
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
return ConfigTypes::OpenVpn;
} }
return ConfigTypes::Invalid; return ConfigTypes::Invalid;
} }
@ -345,7 +342,7 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data)
arr.push_back(containers); arr.push_back(containers);
QString hostName; QString hostName;
const static QRegularExpression hostNameRegExp("remote (.*) [0-9]*"); const static QRegularExpression hostNameRegExp("remote\\s+([^\\s]+)");
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data); QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
if (hostNameMatch.hasMatch()) { if (hostNameMatch.hasMatch()) {
hostName = hostNameMatch.captured(1); hostName = hostNameMatch.captured(1);

View file

@ -44,7 +44,6 @@ void SitesController::addSite(QString hostname)
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList() << hostname)); Q_ARG(QStringList, QStringList() << hostname));
} }
QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection);
}; };
const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) { const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) {
@ -75,7 +74,6 @@ void SitesController::removeSite(int index)
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection, QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList() << hostname)); Q_ARG(QStringList, QStringList() << hostname));
QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection);
emit finished(tr("Site removed: %1").arg(hostname)); emit finished(tr("Site removed: %1").arg(hostname));
} }
@ -124,7 +122,6 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting)
m_sitesModel->addSites(sites, replaceExisting); m_sitesModel->addSites(sites, replaceExisting);
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips)); QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips));
QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection);
emit finished(tr("Import completed")); emit finished(tr("Import completed"));
} }

View file

@ -48,8 +48,8 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
} }
case ServiceDescriptionRole: { case ServiceDescriptionRole: {
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) { if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) {
return tr("Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to 200 " return tr("Classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online resources. "
"Mb/s"); "Speeds up to 200 Mbps");
} else if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) { } else if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
return tr("Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and " return tr("Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and "
"more. YouTube is not included in the free plan."); "more. YouTube is not included in the free plan.");

View file

@ -65,8 +65,8 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
case CardDescriptionRole: { case CardDescriptionRole: {
auto speed = apiServiceData.serviceInfo.speed; auto speed = apiServiceData.serviceInfo.speed;
if (serviceType == serviceType::amneziaPremium) { if (serviceType == serviceType::amneziaPremium) {
return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. " return tr("Amnezia Premium is classic VPN for seamless work, downloading large files, and watching videos. "
"Works for any sites with no restrictions. Speed up to %1 MBit/s. Unlimited traffic.") "Access all websites and online resources. Speeds up to %1 Mbps.")
.arg(speed); .arg(speed);
} else if (serviceType == serviceType::amneziaFree) { } else if (serviceType == serviceType::amneziaFree) {
QString description = tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan."); QString description = tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
@ -79,8 +79,8 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
} }
case ServiceDescriptionRole: { case ServiceDescriptionRole: {
if (serviceType == serviceType::amneziaPremium) { if (serviceType == serviceType::amneziaPremium) {
return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. " return tr("Amnezia Premium is classic VPN for for seamless work, downloading large files, and watching videos. "
"Works for any sites with no restrictions."); "Access all websites and online resources.");
} else { } else {
return tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan."); return tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
} }

View file

@ -54,7 +54,7 @@ Rectangle {
Layout.rightMargin: 10 Layout.rightMargin: 10
Layout.leftMargin: 10 Layout.leftMargin: 10
text: qsTr("Amnezia Premium - for access to any website") text: qsTr("Amnezia Premium - for access to all websites and online resources")
color: AmneziaStyle.color.pearlGray color: AmneziaStyle.color.pearlGray
lineHeight: 18 lineHeight: 18

View file

@ -81,7 +81,7 @@ PageType {
actionButtonImage: "qrc:/images/controls/settings.svg" actionButtonImage: "qrc:/images/controls/settings.svg"
headerText: root.processedServer.name headerText: root.processedServer.name
descriptionText: qsTr("Locations for connection") descriptionText: qsTr("Location for connection")
actionButtonFunction: function() { actionButtonFunction: function() {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)

View file

@ -42,8 +42,8 @@ PageType {
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
headerText: qsTr("Connected devices") headerText: qsTr("Active Devices")
descriptionText: qsTr("To manage connected devices") descriptionText: qsTr("Manage currently connected devices")
} }
WarningType { WarningType {
@ -71,8 +71,13 @@ PageType {
rightImageSource: "qrc:/images/controls/trash.svg" rightImageSource: "qrc:/images/controls/trash.svg"
clickedFunction: function() { clickedFunction: function() {
var headerText = qsTr("Deactivate the subscription on selected device") if (isCurrentDevice && ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
var descriptionText = qsTr("The next time the “Connect” button is pressed, the device will be activated again") PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection"))
return
}
var headerText = qsTr("Are you sure you want to unlink this device?")
var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect.")
var yesButtonText = qsTr("Continue") var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel") var noButtonText = qsTr("Cancel")

View file

@ -99,7 +99,7 @@ PageType {
Layout.leftMargin: 16 Layout.leftMargin: 16
headerText: qsTr("How to connect on another device") headerText: qsTr("How to connect on another device")
descriptionText: qsTr("Instructions on the Amnezia website") descriptionText: qsTr("Setup guides on the Amnezia website")
} }
} }

View file

@ -45,8 +45,8 @@ PageType {
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
headerText: qsTr("Configuration files") headerText: qsTr("Configuration Files")
descriptionText: qsTr("To connect a router or AmneziaWG application") descriptionText: qsTr("For router setup or the AmneziaWG app")
} }
} }
@ -123,13 +123,13 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 16 Layout.margins: 16
headerText: qsTr("Configuration file ") + moreOptionsDrawer.countryName headerText: moreOptionsDrawer.countryName + qsTr(" configuration file")
} }
LabelWithButtonType { LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Create a new") text: qsTr("Generate a new configuration file")
descriptionText: qsTr("The previously created one will stop working") descriptionText: qsTr("The previously created one will stop working")
clickedFunction: function() { clickedFunction: function() {
@ -193,9 +193,15 @@ PageType {
} }
function showQuestion(isConfigIssue, countryCode, countryName) { function showQuestion(isConfigIssue, countryCode, countryName) {
var headerText = qsTr("Revoke the actual %1 configuration file?").arg(countryName) var headerText
var descriptionText = qsTr("The previously created file will no longer be valid. It will not be possible to connect using it.") if (isConfigIssue) {
var yesButtonText = qsTr("Continue") headerText = qsTr("Generate a new %1 configuration file?").arg(countryName)
} else {
headerText = qsTr("Revoke the current %1 configuration file?").arg(countryName)
}
var descriptionText = qsTr("Your previous configuration file will no longer work, and it will not be possible to connect using it")
var yesButtonText = isConfigIssue ? qsTr("Download") : qsTr("Continue")
var noButtonText = qsTr("Cancel") var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() { var yesButtonFunction = function() {

View file

@ -26,7 +26,7 @@ PageType {
QtObject { QtObject {
id: statusObject id: statusObject
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/info.svg" readonly property string objectImageSource: "qrc:/images/controls/info.svg"
} }
@ -34,7 +34,7 @@ PageType {
QtObject { QtObject {
id: endDateObject id: endDateObject
readonly property string title: qsTr("Valid until") readonly property string title: qsTr("Valid Until")
readonly property string contentKey: "endDate" readonly property string contentKey: "endDate"
readonly property string objectImageSource: "qrc:/images/controls/history.svg" readonly property string objectImageSource: "qrc:/images/controls/history.svg"
} }
@ -42,7 +42,7 @@ PageType {
QtObject { QtObject {
id: deviceCountObject id: deviceCountObject
readonly property string title: qsTr("Connected devices") readonly property string title: qsTr("Active Connections")
readonly property string contentKey: "connectedDevices" readonly property string contentKey: "connectedDevices"
readonly property string objectImageSource: "qrc:/images/controls/monitor.svg" readonly property string objectImageSource: "qrc:/images/controls/monitor.svg"
} }
@ -183,7 +183,7 @@ PageType {
visible: false //footer.isVisibleForAmneziaFree visible: false //footer.isVisibleForAmneziaFree
text: qsTr("Subscription key") text: qsTr("Subscription Key")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
@ -191,7 +191,7 @@ PageType {
shareConnectionDrawer.openTriggered() shareConnectionDrawer.openTriggered()
shareConnectionDrawer.isSelfHostedConfig = false; shareConnectionDrawer.isSelfHostedConfig = false;
shareConnectionDrawer.shareButtonText = qsTr("Save VPN key to file") shareConnectionDrawer.shareButtonText = qsTr("Save VPN key as a file")
shareConnectionDrawer.copyButtonText = qsTr("Copy VPN key") shareConnectionDrawer.copyButtonText = qsTr("Copy VPN key")
@ -213,9 +213,9 @@ PageType {
visible: footer.isVisibleForAmneziaFree visible: footer.isVisibleForAmneziaFree
text: qsTr("Configuration files") text: qsTr("Configuration Files")
descriptionText: qsTr("To connect a router or AmneziaWG application") descriptionText: qsTr("Manage configuration files")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
@ -233,9 +233,9 @@ PageType {
visible: footer.isVisibleForAmneziaFree visible: footer.isVisibleForAmneziaFree
text: qsTr("Connected devices") text: qsTr("Active Devices")
descriptionText: qsTr("To manage connected devices") descriptionText: qsTr("Manage currently connected devices")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
@ -265,6 +265,8 @@ PageType {
LabelWithButtonType { LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
visible: footer.isVisibleForAmneziaFree
text: qsTr("How to connect on another device") text: qsTr("How to connect on another device")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
@ -273,7 +275,9 @@ PageType {
} }
} }
DividerType {} DividerType {
visible: footer.isVisibleForAmneziaFree
}
BasicButtonType { BasicButtonType {
id: resetButton id: resetButton
@ -325,17 +329,17 @@ PageType {
pressedColor: AmneziaStyle.color.sheerWhite pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.vibrantRed textColor: AmneziaStyle.color.vibrantRed
text: qsTr("Deactivate the subscription on this device") text: qsTr("Unlink this device")
clickedFunc: function() { clickedFunc: function() {
var headerText = qsTr("Deactivate the subscription on this device?") var headerText = qsTr("Are you sure you want to unlink this device?")
var descriptionText = qsTr("The next time the “Connect” button is pressed, the device will be activated again") var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect.")
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("Cannot deactivate subscription during active connection")) PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection"))
} else { } else {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
if (ApiConfigsController.deactivateDevice()) { if (ApiConfigsController.deactivateDevice()) {

View file

@ -27,7 +27,7 @@ PageType {
QtObject { QtObject {
id: techSupport id: techSupport
readonly property string title: qsTr("For technical support") readonly property string title: qsTr("Email")
readonly property string description: qsTr("support@amnezia.org") readonly property string description: qsTr("support@amnezia.org")
readonly property string link: "mailto:support@amnezia.org" readonly property string link: "mailto:support@amnezia.org"
} }
@ -35,7 +35,7 @@ PageType {
QtObject { QtObject {
id: paymentSupport id: paymentSupport
readonly property string title: qsTr("For payment issues") readonly property string title: qsTr("Email Billing & Orders")
readonly property string description: qsTr("help@vpnpay.io") readonly property string description: qsTr("help@vpnpay.io")
readonly property string link: "mailto:help@vpnpay.io" readonly property string link: "mailto:help@vpnpay.io"
} }
@ -43,7 +43,7 @@ PageType {
QtObject { QtObject {
id: site id: site
readonly property string title: qsTr("Site") readonly property string title: qsTr("Website")
readonly property string description: qsTr("amnezia.org") readonly property string description: qsTr("amnezia.org")
readonly property string link: LanguageModel.getCurrentSiteUrl() readonly property string link: LanguageModel.getCurrentSiteUrl()
} }
@ -79,7 +79,7 @@ PageType {
Layout.leftMargin: 16 Layout.leftMargin: 16
headerText: qsTr("Support") headerText: qsTr("Support")
descriptionText: qsTr("Our technical support specialists are ready to help you at any time") descriptionText: qsTr("Our technical support specialists are available to assist you at any time")
} }
} }