Compare commits

..

6 Commits

Author SHA1 Message Date
Yaroslav 2ee61a040b fix: iOS appstore publish fix (#1922) 2025-11-04 12:10:30 +08:00
vkamn 741b5cc0f9 fix: qt6 9 support (#1973)
* Fix qt 6.9 support

* add support android sdk 36

* feat: add support SafeMargins from Android

* Fix black screen

---------

Co-authored-by: NickVs2015 <nv@amnezia.org>
2025-11-04 11:43:36 +08:00
MrMirDan aaf0e070dc fix: hide description (#1959) 2025-11-03 10:27:01 +08:00
vkamn e0e126eda8 chore: bump version (#1969) 2025-11-03 10:26:33 +08:00
vkamn 236daf6b3b feat: ad label (#1966)
* refactor: ad label desing refatroing

* feat: add ad label settings processing

* chore: fix ru translations

* chore: minor fixes
2025-11-03 10:26:22 +08:00
vkamn f1481b1b1f feat: add async post in gateway controller (#1963) 2025-10-29 23:24:24 +08:00
77 changed files with 667 additions and 238 deletions
+2 -2
View File
@@ -469,8 +469,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
ANDROID_BUILD_PLATFORM: android-34 ANDROID_BUILD_PLATFORM: android-36
QT_VERSION: 6.7.3 QT_VERSION: 6.9.3
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools' QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
+2 -2
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.11.0) set(AMNEZIAVPN_VERSION 4.8.11.1)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION} project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN" DESCRIPTION "AmneziaVPN"
@@ -12,7 +12,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 2095) set(APP_ANDROID_VERSION_CODE 2096)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
+1
View File
@@ -46,6 +46,7 @@
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc" |fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:windowSoftInputMode="stateUnchanged|adjustResize" android:windowSoftInputMode="stateUnchanged|adjustResize"
android:enableOnBackInvokedCallback="false"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
+3
View File
@@ -6,6 +6,9 @@
<item name="android:colorBackground">@color/black</item> <item name="android:colorBackground">@color/black</item>
<item name="android:windowActionBar">false</item> <item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item> <item name="android:windowNoTitle">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
</style> </style>
<style name="Translucent" parent="NoActionBar"> <style name="Translucent" parent="NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowBackground">@android:color/transparent</item>
@@ -35,6 +35,7 @@ import android.widget.Toast
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsControllerCompat
import java.io.IOException import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE import kotlin.LazyThreadSafetyMode.NONE
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@@ -170,10 +171,9 @@ class AmneziaActivity : QtActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.d(TAG, "Create Amnezia activity") Log.d(TAG, "Create Amnezia activity")
loadLibs() loadLibs()
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) // Configure window for edge-to-edge display
statusBarColor = getColor(R.color.black) configureWindowForEdgeToEdge()
}
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
val proto = mainScope.async(Dispatchers.IO) { val proto = mainScope.async(Dispatchers.IO) {
VpnStateStore.getVpnState().vpnProto VpnStateStore.getVpnState().vpnProto
@@ -265,6 +265,49 @@ class AmneziaActivity : QtActivity() {
super.onStop() super.onStop()
} }
override fun onResume() {
super.onResume()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply {
invalidate()
postDelayed({
sendTouch(1f, 1f)
}, 100)
postDelayed({
sendTouch(2f, 2f)
}, 200)
postDelayed({
requestLayout()
invalidate()
}, 250)
}
}
}
private fun configureWindowForEdgeToEdge() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
addFlags(LayoutParams.FLAG_LAYOUT_NO_LIMITS)
statusBarColor = android.graphics.Color.TRANSPARENT
navigationBarColor = android.graphics.Color.TRANSPARENT
}
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = false
isAppearanceLightNavigationBars = false
}
} else {
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black)
}
}
}
override fun onDestroy() { override fun onDestroy() {
Log.d(TAG, "Destroy Amnezia activity") Log.d(TAG, "Destroy Amnezia activity")
unregisterBroadcastReceiver(notificationStateReceiver) unregisterBroadcastReceiver(notificationStateReceiver)
@@ -666,6 +709,43 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused") @Suppress("unused")
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK) fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
@Suppress("unused")
fun isEdgeToEdgeEnabled(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
@Suppress("unused")
fun getStatusBarHeight(): Int {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
val heightPx = if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else {
0
}
// Convert physical pixels to device-independent pixels for QML
val density = resources.displayMetrics.density
val heightDp = (heightPx / density).toInt()
return heightDp
}
@Suppress("unused")
fun getNavigationBarHeight(): Int {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
val heightPx = if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else {
0
}
// Convert physical pixels to device-independent pixels for QML
val density = resources.displayMetrics.density
val heightDp = (heightPx / density).toInt()
return heightDp
}
@Suppress("unused") @Suppress("unused")
fun startQrCodeReader() { fun startQrCodeReader() {
Log.v(TAG, "Start camera") Log.v(TAG, "Start camera")
@@ -38,15 +38,15 @@ object AppListProvider {
} }
} }
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> { private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo? = pi.applicationInfo) : Comparable<App> {
val name: String? val name: String?
val packageName: String = pi.packageName val packageName: String = pi.packageName
val icon: Boolean = ai.icon != 0 val icon: Boolean = (ai?.icon ?: 0) != 0
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
init { init {
val name = ai.loadLabel(pm).toString() val name = ai?.loadLabel(pm)?.toString()
this.name = if (name != packageName) name else null this.name = name?.takeIf { it != packageName }
} }
override fun compareTo(other: App): Int { override fun compareTo(other: App): Int {
+3 -3
View File
@@ -1,6 +1,6 @@
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build") message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
set(APP_ANDROID_MIN_SDK 26) set(APP_ANDROID_MIN_SDK 28)
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
"The minimum API level supported by the application or library" FORCE) "The minimum API level supported by the application or library" FORCE)
@@ -11,8 +11,8 @@ set_target_properties(${PROJECT} PROPERTIES
QT_ANDROID_VERSION_NAME ${CMAKE_PROJECT_VERSION} QT_ANDROID_VERSION_NAME ${CMAKE_PROJECT_VERSION}
QT_ANDROID_VERSION_CODE ${APP_ANDROID_VERSION_CODE} QT_ANDROID_VERSION_CODE ${APP_ANDROID_VERSION_CODE}
QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK} QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK}
QT_ANDROID_TARGET_SDK_VERSION 34 QT_ANDROID_TARGET_SDK_VERSION 36
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0 QT_ANDROID_SDK_BUILD_TOOLS_REVISION 36.0.0
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
) )
+4 -15
View File
@@ -136,21 +136,10 @@ set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
add_subdirectory(ios/networkextension) add_subdirectory(ios/networkextension)
add_dependencies(${PROJECT} networkextension) add_dependencies(${PROJECT} networkextension)
set(OPENVPN_FRAMEWORK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios") set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
set(OPENVPN_EMBEDDED_FRAMEWORKS "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework"
"${OPENVPN_FRAMEWORK_DIR}/OpenVPNAdapter.framework"
"${OPENVPN_FRAMEWORK_DIR}/OpenVPNClient.framework"
"${OPENVPN_FRAMEWORK_DIR}/mbedTLS.framework"
"${OPENVPN_FRAMEWORK_DIR}/LZ4.framework"
) )
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS "${OPENVPN_EMBEDDED_FRAMEWORKS}") set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/)
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS "$(inherited) ${OPENVPN_FRAMEWORK_DIR}") target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework")
foreach(_framework ${OPENVPN_EMBEDDED_FRAMEWORKS})
target_link_libraries(networkextension PRIVATE "${_framework}")
endforeach()
set_property(TARGET networkextension PROPERTY XCODE_EMBED_FRAMEWORKS "${OPENVPN_EMBEDDED_FRAMEWORKS}")
set_property(TARGET networkextension PROPERTY XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON)
set_property(TARGET networkextension PROPERTY XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS "$(inherited) ${OPENVPN_FRAMEWORK_DIR}")
+8
View File
@@ -47,12 +47,14 @@ namespace apiDefs
constexpr QLatin1String serverCountryName("server_country_name"); constexpr QLatin1String serverCountryName("server_country_name");
constexpr QLatin1String osVersion("os_version"); constexpr QLatin1String osVersion("os_version");
constexpr QLatin1String appLanguage("app_language");
constexpr QLatin1String availableCountries("available_countries"); constexpr QLatin1String availableCountries("available_countries");
constexpr QLatin1String activeDeviceCount("active_device_count"); constexpr QLatin1String activeDeviceCount("active_device_count");
constexpr QLatin1String maxDeviceCount("max_device_count"); constexpr QLatin1String maxDeviceCount("max_device_count");
constexpr QLatin1String subscriptionEndDate("subscription_end_date"); constexpr QLatin1String subscriptionEndDate("subscription_end_date");
constexpr QLatin1String issuedConfigs("issued_configs"); constexpr QLatin1String issuedConfigs("issued_configs");
constexpr QLatin1String subscriptionDescription("subscription_description");
constexpr QLatin1String supportInfo("support_info"); constexpr QLatin1String supportInfo("support_info");
constexpr QLatin1String email("email"); constexpr QLatin1String email("email");
@@ -68,6 +70,12 @@ namespace apiDefs
constexpr QLatin1String transactionId("transaction_id"); constexpr QLatin1String transactionId("transaction_id");
constexpr QLatin1String userCountryCode("user_country_code"); constexpr QLatin1String userCountryCode("user_country_code");
constexpr QLatin1String serviceInfo("service_info");
constexpr QLatin1String isAdVisible("is_ad_visible");
constexpr QLatin1String adHeader("ad_header");
constexpr QLatin1String adDescription("ad_description");
constexpr QLatin1String adEndpoint("ad_endpoint");
} }
const int requestTimeoutMsecs = 12 * 1000; // 12 secs const int requestTimeoutMsecs = 12 * 1000; // 12 secs
+94 -27
View File
@@ -7,6 +7,7 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QNetworkReply> #include <QNetworkReply>
#include <QPromise>
#include <QUrl> #include <QUrl>
#include "QBlockCipher.h" #include "QBlockCipher.h"
@@ -50,24 +51,25 @@ GatewayController::GatewayController(const QString &gatewayEndpoint, const bool
{ {
} }
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody) GatewayController::EncryptedRequestData GatewayController::prepareRequest(const QString &endpoint, const QJsonObject &apiPayload)
{ {
EncryptedRequestData encRequestData;
encRequestData.errorCode = ErrorCode::NoError;
#ifdef Q_OS_IOS #ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess(); IosController::Instance()->requestInetAccess();
QThread::msleep(10); QThread::msleep(10);
#endif #endif
QNetworkRequest request; encRequestData.request.setTransferTimeout(m_requestTimeoutMsecs);
request.setTransferTimeout(m_requestTimeoutMsecs); encRequestData.request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); encRequestData.request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8()); encRequestData.request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
// bypass killSwitch exceptions for API-gateway // bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
if (m_isStrictKillSwitchEnabled) { if (m_isStrictKillSwitchEnabled) {
QString host = QUrl(request.url()).host(); QString host = QUrl(encRequestData.request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host); QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) { if (!ip.isEmpty()) {
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip }); IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
@@ -76,14 +78,14 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
#endif #endif
QSimpleCrypto::QBlockCipher blockCipher; QSimpleCrypto::QBlockCipher blockCipher;
QByteArray key = blockCipher.generatePrivateSalt(32); encRequestData.key = blockCipher.generatePrivateSalt(32);
QByteArray iv = blockCipher.generatePrivateSalt(32); encRequestData.iv = blockCipher.generatePrivateSalt(32);
QByteArray salt = blockCipher.generatePrivateSalt(8); encRequestData.salt = blockCipher.generatePrivateSalt(8);
QJsonObject keyPayload; QJsonObject keyPayload;
keyPayload[configKey::aesKey] = QString(key.toBase64()); keyPayload[configKey::aesKey] = QString(encRequestData.key.toBase64());
keyPayload[configKey::aesIv] = QString(iv.toBase64()); keyPayload[configKey::aesIv] = QString(encRequestData.iv.toBase64());
keyPayload[configKey::aesSalt] = QString(salt.toBase64()); keyPayload[configKey::aesSalt] = QString(encRequestData.salt.toBase64());
QByteArray encryptedKeyPayload; QByteArray encryptedKeyPayload;
QByteArray encryptedApiPayload; QByteArray encryptedApiPayload;
@@ -98,24 +100,37 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
} catch (...) { } catch (...) {
Utils::logException(); Utils::logException();
qCritical() << "error loading public key from environment variables"; qCritical() << "error loading public key from environment variables";
return ErrorCode::ApiMissingAgwPublicKey; encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
return encRequestData;
} }
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING); encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
EVP_PKEY_free(publicKey); EVP_PKEY_free(publicKey);
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt); encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv, "", encRequestData.salt);
} catch (...) { // todo change error handling in QSimpleCrypto? } catch (...) {
Utils::logException(); Utils::logException();
qCritical() << "error when encrypting the request body"; qCritical() << "error when encrypting the request body";
return ErrorCode::ApiConfigDecryptionError; encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
return encRequestData;
} }
QJsonObject requestBody; QJsonObject requestBody;
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64()); requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64()); requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson()); encRequestData.requestBody = QJsonDocument(requestBody).toJson();
return encRequestData;
}
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
{
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
if (encRequestData.errorCode != ErrorCode::NoError) {
return encRequestData.errorCode;
}
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
QEventLoop wait; QEventLoop wait;
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
@@ -131,19 +146,19 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
reply->deleteLater(); reply->deleteLater();
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, encryptedResponseBody, true, key, iv, salt)) { if (sslErrors.isEmpty() && shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) { auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
request.setUrl(url); encRequestData.request.setUrl(url);
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson()); return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
}; };
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &key, &iv, auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &encRequestData,
&salt, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) { this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
encryptedResponseBody = reply->readAll(); encryptedResponseBody = reply->readAll();
replyErrorString = reply->errorString(); replyErrorString = reply->errorString();
replyError = reply->error(); replyError = reply->error();
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (!sslErrors.isEmpty() || shouldBypassProxy(replyError, encryptedResponseBody, true, key, iv, salt)) { if (!sslErrors.isEmpty() || shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
sslErrors = nestedSslErrors; sslErrors = nestedSslErrors;
return false; return false;
} }
@@ -161,7 +176,8 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
} }
try { try {
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt); QSimpleCrypto::QBlockCipher blockCipher;
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
return ErrorCode::NoError; return ErrorCode::NoError;
} catch (...) { // todo change error handling in QSimpleCrypto? } catch (...) { // todo change error handling in QSimpleCrypto?
Utils::logException(); Utils::logException();
@@ -170,6 +186,57 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
} }
} }
QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
{
auto promise = QSharedPointer<QPromise<QPair<ErrorCode, QByteArray>>>::create();
promise->start();
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
if (encRequestData.errorCode != ErrorCode::NoError) {
promise->addResult(qMakePair(encRequestData.errorCode, QByteArray()));
promise->finish();
return promise->future();
}
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) {
*sslErrors = errors;
});
connect(reply, &QNetworkReply::finished, reply, [=]() {
QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString();
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();
auto errorCode = apiUtils::checkNetworkReplyErrors(*sslErrors, replyErrorString, replyError, httpStatusCode, encryptedResponseBody);
if (errorCode) {
promise->addResult(qMakePair(errorCode, QByteArray()));
promise->finish();
return;
}
QSimpleCrypto::QBlockCipher blockCipher;
try {
QByteArray responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
promise->addResult(qMakePair(ErrorCode::NoError, responseBody));
promise->finish();
} catch (...) {
Utils::logException();
qCritical() << "error when decrypting the request body";
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
promise->finish();
}
});
return promise->future();
}
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode) QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
{ {
QNetworkRequest request; QNetworkRequest request;
@@ -1,8 +1,10 @@
#ifndef GATEWAYCONTROLLER_H #ifndef GATEWAYCONTROLLER_H
#define GATEWAYCONTROLLER_H #define GATEWAYCONTROLLER_H
#include <QFuture>
#include <QNetworkReply> #include <QNetworkReply>
#include <QObject> #include <QObject>
#include <QPair>
#include "core/defs.h" #include "core/defs.h"
@@ -19,8 +21,20 @@ public:
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr); const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody); amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
private: private:
struct EncryptedRequestData {
QNetworkRequest request;
QByteArray requestBody;
QByteArray key;
QByteArray iv;
QByteArray salt;
amnezia::ErrorCode errorCode;
};
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode); QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption, bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption,
const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = ""); const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = "");
@@ -202,6 +202,21 @@ bool AndroidController::isOnTv()
return callActivityMethod<jboolean>("isOnTv", "()Z"); return callActivityMethod<jboolean>("isOnTv", "()Z");
} }
bool AndroidController::isEdgeToEdgeEnabled()
{
return callActivityMethod<jboolean>("isEdgeToEdgeEnabled", "()Z");
}
int AndroidController::getStatusBarHeight()
{
return callActivityMethod<jint>("getStatusBarHeight", "()I");
}
int AndroidController::getNavigationBarHeight()
{
return callActivityMethod<jint>("getNavigationBarHeight", "()I");
}
void AndroidController::startQrReaderActivity() void AndroidController::startQrReaderActivity()
{ {
callActivityMethod("startQrCodeReader", "()V"); callActivityMethod("startQrCodeReader", "()V");
@@ -39,6 +39,9 @@ public:
QString getFileName(const QString &uri); QString getFileName(const QString &uri);
bool isCameraPresent(); bool isCameraPresent();
bool isOnTv(); bool isOnTv();
bool isEdgeToEdgeEnabled();
int getStatusBarHeight();
int getNavigationBarHeight();
void startQrReaderActivity(); void startQrReaderActivity();
void setSaveLogs(bool enabled); void setSaveLogs(bool enabled);
void exportLogsFile(const QString &fileName); void exportLogsFile(const QString &fileName);
-1
View File
@@ -131,7 +131,6 @@
<file>ui/qml/Components/SelectLanguageDrawer.qml</file> <file>ui/qml/Components/SelectLanguageDrawer.qml</file>
<file>ui/qml/Components/ServersListView.qml</file> <file>ui/qml/Components/ServersListView.qml</file>
<file>ui/qml/Components/SettingsContainersListView.qml</file> <file>ui/qml/Components/SettingsContainersListView.qml</file>
<file>ui/qml/Components/TransportProtoSelector.qml</file> <file>ui/qml/Components/TransportProtoSelector.qml</file>
<file>ui/qml/Components/AddSitePanel.qml</file> <file>ui/qml/Components/AddSitePanel.qml</file>
<file>ui/qml/Config/GlobalConfig.qml</file> <file>ui/qml/Config/GlobalConfig.qml</file>
+56 -41
View File
@@ -4,9 +4,8 @@
<context> <context>
<name>AdLabel</name> <name>AdLabel</name>
<message> <message>
<location filename="../ui/qml/Components/AdLabel.qml" line="57"/>
<source>Amnezia Premium - for access to all websites and online resources</source> <source>Amnezia Premium - for access to all websites and online resources</source>
<translation>Amnezia Premium - доступ ко всем сайтам и онлайн ресурсам</translation> <translation type="vanished">Amnezia Premium - доступ ко всем сайтам и онлайн ресурсам</translation>
</message> </message>
</context> </context>
<context> <context>
@@ -61,7 +60,7 @@
<name>ApiAccountInfoModel</name> <name>ApiAccountInfoModel</name>
<message> <message>
<location filename="../ui/models/api/apiAccountInfoModel.cpp" line="31"/> <location filename="../ui/models/api/apiAccountInfoModel.cpp" line="31"/>
<location filename="../ui/models/api/apiAccountInfoModel.cpp" line="34"/> <location filename="../ui/models/api/apiAccountInfoModel.cpp" line="35"/>
<source>Active</source> <source>Active</source>
<translation>Активна</translation> <translation>Активна</translation>
</message> </message>
@@ -71,35 +70,33 @@
<translation>Не активна</translation> <translation>Не активна</translation>
</message> </message>
<message> <message>
<location filename="../ui/models/api/apiAccountInfoModel.cpp" line="47"/> <location filename="../ui/models/api/apiAccountInfoModel.cpp" line="48"/>
<source>%1 out of %2</source> <source>%1 out of %2</source>
<translation>%1 из %2</translation> <translation>%1 из %2</translation>
</message> </message>
<message> <message>
<location filename="../ui/models/api/apiAccountInfoModel.cpp" line="51"/>
<source>Classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online resources. Speeds up to 200 Mbps</source> <source>Classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online resources. Speeds up to 200 Mbps</source>
<translation>Классический VPN для комфортной работы, загрузки больших файлов и просмотра видео. Доступ ко всем сайтам и онлайн-ресурсам. Скорость до 200 Мбит/с</translation> <translation type="vanished">Классический VPN для комфортной работы, загрузки больших файлов и просмотра видео. Доступ ко всем сайтам и онлайн-ресурсам. Скорость до 200 Мбит/с</translation>
</message> </message>
<message> <message>
<location filename="../ui/models/api/apiAccountInfoModel.cpp" line="55"/>
<source>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.</source> <source>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.</source>
<translation>Бесплатный неограниченный доступ к базовому набору сайтов и приложений, таким как Facebook, Instagram, Twitter (X), Discord, Telegram и другим. YouTube не включён в бесплатный тариф.</translation> <translation type="vanished">Бесплатный неограниченный доступ к базовому набору сайтов и приложений, таким как Facebook, Instagram, Twitter (X), Discord, Telegram и другим. YouTube не включён в бесплатный тариф.</translation>
</message> </message>
</context> </context>
<context> <context>
<name>ApiConfigsController</name> <name>ApiConfigsController</name>
<message> <message>
<location filename="../ui/controllers/api/apiConfigsController.cpp" line="413"/> <location filename="../ui/controllers/api/apiConfigsController.cpp" line="448"/>
<source>%1 installed successfully.</source> <source>%1 installed successfully.</source>
<translation>%1 успешно установлен.</translation> <translation>%1 успешно установлен.</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/api/apiConfigsController.cpp" line="473"/> <location filename="../ui/controllers/api/apiConfigsController.cpp" line="513"/>
<source>API config reloaded</source> <source>API config reloaded</source>
<translation>Конфигурация API перезагружена</translation> <translation>Конфигурация API перезагружена</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/api/apiConfigsController.cpp" line="477"/> <location filename="../ui/controllers/api/apiConfigsController.cpp" line="517"/>
<source>Successfully changed the country of connection to %1</source> <source>Successfully changed the country of connection to %1</source>
<translation>Страна подключения изменена на %1</translation> <translation>Страна подключения изменена на %1</translation>
</message> </message>
@@ -627,27 +624,32 @@ Thank you for staying with us!</source>
<translation>Продолжить</translation> <translation>Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="102"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="92"/>
<source>Logging enabled</source> <source>Logging enabled</source>
<translation>Логирование включено</translation> <translation>Логирование включено</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="144"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="120"/>
<source>Dev gateway enabled</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="162"/>
<source>Split tunneling enabled</source> <source>Split tunneling enabled</source>
<translation>Раздельное туннелирование включено</translation> <translation>Раздельное туннелирование включено</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="144"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="162"/>
<source>Split tunneling disabled</source> <source>Split tunneling disabled</source>
<translation>Раздельное туннелирование выключено</translation> <translation>Раздельное туннелирование выключено</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="381"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="409"/>
<source>VPN protocol</source> <source>VPN protocol</source>
<translation>VPN-протокол</translation> <translation>VPN-протокол</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="434"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="462"/>
<source>Servers</source> <source>Servers</source>
<translation>Серверы</translation> <translation>Серверы</translation>
</message> </message>
@@ -1579,32 +1581,37 @@ Thank you for staying with us!</source>
<translation>Настройки</translation> <translation>Настройки</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettings.qml" line="101"/> <location filename="../ui/qml/Pages2/PageSettings.qml" line="102"/>
<source>Servers</source> <source>Servers</source>
<translation>Серверы</translation> <translation>Серверы</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettings.qml" line="112"/> <location filename="../ui/qml/Pages2/PageSettings.qml" line="113"/>
<source>Connection</source> <source>Connection</source>
<translation>Соединение</translation> <translation>Соединение</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettings.qml" line="123"/> <location filename="../ui/qml/Pages2/PageSettings.qml" line="124"/>
<source>Application</source> <source>Application</source>
<translation>Приложение</translation> <translation>Приложение</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettings.qml" line="134"/> <location filename="../ui/qml/Pages2/PageSettings.qml" line="135"/>
<source>News &amp; Notifications</source>
<translation>Новости и Уведомления</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettings.qml" line="152"/>
<source>Backup</source> <source>Backup</source>
<translation>Резервное копирование</translation> <translation>Резервное копирование</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettings.qml" line="145"/> <location filename="../ui/qml/Pages2/PageSettings.qml" line="163"/>
<source>About AmneziaVPN</source> <source>About AmneziaVPN</source>
<translation>Об AmneziaVPN</translation> <translation>Об AmneziaVPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettings.qml" line="156"/> <location filename="../ui/qml/Pages2/PageSettings.qml" line="174"/>
<source>Dev console</source> <source>Dev console</source>
<translation>Dev console</translation> <translation>Dev console</translation>
</message> </message>
@@ -2763,6 +2770,14 @@ Thank you for staying with us!</source>
<translation>Очистить логи</translation> <translation>Очистить логи</translation>
</message> </message>
</context> </context>
<context>
<name>PageSettingsNewsNotifications</name>
<message>
<location filename="../ui/qml/Pages2/PageSettingsNewsNotifications.qml" line="33"/>
<source>News &amp; Notifications</source>
<translation>Новости и Уведомления</translation>
</message>
</context>
<context> <context>
<name>PageSettingsServerData</name> <name>PageSettingsServerData</name>
<message> <message>
@@ -3012,13 +3027,13 @@ Thank you for staying with us!</source>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="210"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="210"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="358"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="357"/>
<source>Continue</source> <source>Continue</source>
<translation>Продолжить</translation> <translation>Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="211"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="211"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="359"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="358"/>
<source>Cancel</source> <source>Cancel</source>
<translation>Отменить</translation> <translation>Отменить</translation>
</message> </message>
@@ -3059,8 +3074,8 @@ Thank you for staying with us!</source>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="332"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="332"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="459"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="458"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="472"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="471"/>
<source>Sites files (*.json)</source> <source>Sites files (*.json)</source>
<translation>Файлы сайтов (*.json)</translation> <translation>Файлы сайтов (*.json)</translation>
</message> </message>
@@ -3070,33 +3085,33 @@ Thank you for staying with us!</source>
<translation>Очистить список сайтов</translation> <translation>Очистить список сайтов</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="356"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="355"/>
<source>Clear site list?</source> <source>Clear site list?</source>
<translation>Очистить список сайтов?</translation> <translation>Очистить список сайтов?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="357"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="356"/>
<source>All sites will be removed from list.</source> <source>All sites will be removed from list.</source>
<translation>Все сайты будут удалены из списка.</translation> <translation>Все сайты будут удалены из списка.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="421"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="420"/>
<source>Import a list of sites</source> <source>Import a list of sites</source>
<translation>Импортировать список с сайтами</translation> <translation>Импортировать список с сайтами</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="456"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="455"/>
<source>Replace site list</source> <source>Replace site list</source>
<translation>Заменить список с сайтами</translation> <translation>Заменить список с сайтами</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="458"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="457"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="471"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="470"/>
<source>Open sites file</source> <source>Open sites file</source>
<translation>Открыть список с сайтами</translation> <translation>Открыть список с сайтами</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="469"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="468"/>
<source>Add imported sites to existing ones</source> <source>Add imported sites to existing ones</source>
<translation>Добавить импортированные сайты к существующим</translation> <translation>Добавить импортированные сайты к существующим</translation>
</message> </message>
@@ -3521,32 +3536,32 @@ Thank you for staying with us!</source>
<context> <context>
<name>PageSetupWizardViewConfig</name> <name>PageSetupWizardViewConfig</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="70"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="72"/>
<source>New connection</source> <source>New connection</source>
<translation>Новое соединение</translation> <translation>Новое соединение</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="112"/>
<source>Collapse content</source> <source>Collapse content</source>
<translation>Свернуть</translation> <translation>Свернуть</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="112"/>
<source>Show content</source> <source>Show content</source>
<translation>Показать</translation> <translation>Показать</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="127"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="129"/>
<source>Enable WireGuard obfuscation. It may be useful if WireGuard is blocked on your provider.</source> <source>Enable WireGuard obfuscation. It may be useful if WireGuard is blocked on your provider.</source>
<translation>Включить обфускацию WireGuard. Это может быть полезно, если WireGuard блокируется вашим провайдером.</translation> <translation>Включить обфускацию WireGuard. Это может быть полезно, если WireGuard блокируется вашим провайдером.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="158"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="160"/>
<source>Use connection codes only from sources you trust. Codes from public sources may have been created to intercept your data.</source> <source>Use connection codes only from sources you trust. Codes from public sources may have been created to intercept your data.</source>
<translation>Используйте файлы конфигурации только из тех источников, которым вы доверяете. Файлы из общедоступных источников могли быть созданы с целью перехвата ваших личных данных.</translation> <translation>Используйте файлы конфигурации только из тех источников, которым вы доверяете. Файлы из общедоступных источников могли быть созданы с целью перехвата ваших личных данных.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="202"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="204"/>
<source>Connect</source> <source>Connect</source>
<translation>Подключиться</translation> <translation>Подключиться</translation>
</message> </message>
@@ -4950,12 +4965,12 @@ FileZilla или другие SFTP-клиенты, а также смонтир
<context> <context>
<name>SettingsController</name> <name>SettingsController</name>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="250"/> <location filename="../ui/controllers/settingsController.cpp" line="258"/>
<source>All settings have been reset to default values</source> <source>All settings have been reset to default values</source>
<translation>Все настройки сброшены до значений по умолчанию</translation> <translation>Все настройки сброшены до значений по умолчанию</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="227"/> <location filename="../ui/controllers/settingsController.cpp" line="235"/>
<source>Backup file is corrupted</source> <source>Backup file is corrupted</source>
<translation>Файл резервной копии поврежден</translation> <translation>Файл резервной копии поврежден</translation>
</message> </message>
@@ -29,7 +29,6 @@ namespace
constexpr char uuid[] = "installation_uuid"; constexpr char uuid[] = "installation_uuid";
constexpr char osVersion[] = "os_version"; constexpr char osVersion[] = "os_version";
constexpr char appVersion[] = "app_version"; constexpr char appVersion[] = "app_version";
constexpr char appLanguage[] = "app_language";
constexpr char userCountryCode[] = "user_country_code"; constexpr char userCountryCode[] = "user_country_code";
constexpr char serverCountryCode[] = "server_country_code"; constexpr char serverCountryCode[] = "server_country_code";
@@ -65,6 +64,7 @@ namespace
{ {
QString osVersion; QString osVersion;
QString appVersion; QString appVersion;
QString appLanguage;
QString installationUuid; QString installationUuid;
@@ -84,6 +84,9 @@ namespace
if (!appVersion.isEmpty()) { if (!appVersion.isEmpty()) {
obj[configKey::appVersion] = appVersion; obj[configKey::appVersion] = appVersion;
} }
if (!appLanguage.isEmpty()) {
obj[apiDefs::key::appLanguage] = appLanguage;
}
if (!installationUuid.isEmpty()) { if (!installationUuid.isEmpty()) {
obj[configKey::uuid] = installationUuid; obj[configKey::uuid] = installationUuid;
} }
@@ -223,6 +226,9 @@ namespace
if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) {
apiConfig.insert(apiDefs::key::supportedProtocols, apiConfig.insert(apiDefs::key::supportedProtocols,
QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::supportedProtocols).toArray()); QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::supportedProtocols).toArray());
apiConfig.insert(apiDefs::key::serviceInfo,
QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::serviceInfo).toObject());
} }
serverConfig[configKey::apiConfig] = apiConfig; serverConfig[configKey::apiConfig] = apiConfig;
@@ -285,6 +291,7 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
GatewayRequestData gatewayRequestData { QSysInfo::productType(), GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION), QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),
m_settings->getInstallationUuid(true), m_settings->getInstallationUuid(true),
apiConfigObject.value(configKey::userCountryCode).toString(), apiConfigObject.value(configKey::userCountryCode).toString(),
serverCountryCode, serverCountryCode,
@@ -325,6 +332,7 @@ bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
GatewayRequestData gatewayRequestData { QSysInfo::productType(), GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION), QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),
m_settings->getInstallationUuid(true), m_settings->getInstallationUuid(true),
apiConfigObject.value(configKey::userCountryCode).toString(), apiConfigObject.value(configKey::userCountryCode).toString(),
serverCountryCode, serverCountryCode,
@@ -375,7 +383,7 @@ bool ApiConfigsController::fillAvailableServices()
{ {
QJsonObject apiPayload; QJsonObject apiPayload;
apiPayload[configKey::osVersion] = QSysInfo::productType(); apiPayload[configKey::osVersion] = QSysInfo::productType();
apiPayload[configKey::appLanguage] = m_settings->getAppLanguage().name().split("_").first(); apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
QByteArray responseBody; QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody); ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
@@ -399,6 +407,7 @@ bool ApiConfigsController::importServiceFromGateway()
{ {
GatewayRequestData gatewayRequestData { QSysInfo::productType(), GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION), QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),
m_settings->getInstallationUuid(true), m_settings->getInstallationUuid(true),
m_apiServicesModel->getCountryCode(), m_apiServicesModel->getCountryCode(),
"", "",
@@ -457,6 +466,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
GatewayRequestData gatewayRequestData { QSysInfo::productType(), GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION), QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),
m_settings->getInstallationUuid(true), m_settings->getInstallationUuid(true),
apiConfig.value(configKey::userCountryCode).toString(), apiConfig.value(configKey::userCountryCode).toString(),
newCountryCode, newCountryCode,
@@ -577,6 +587,7 @@ bool ApiConfigsController::deactivateDevice(const bool isRemoveEvent)
GatewayRequestData gatewayRequestData { QSysInfo::productType(), GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION), QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),
m_settings->getInstallationUuid(true), m_settings->getInstallationUuid(true),
apiConfigObject.value(configKey::userCountryCode).toString(), apiConfigObject.value(configKey::userCountryCode).toString(),
apiConfigObject.value(configKey::serverCountryCode).toString(), apiConfigObject.value(configKey::serverCountryCode).toString(),
@@ -616,6 +627,7 @@ bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const Q
GatewayRequestData gatewayRequestData { QSysInfo::productType(), GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION), QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),
uuid, uuid,
apiConfigObject.value(configKey::userCountryCode).toString(), apiConfigObject.value(configKey::userCountryCode).toString(),
serverCountryCode, serverCountryCode,
+20 -17
View File
@@ -32,7 +32,6 @@ void ApiNewsController::fetchNews()
} }
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled()); m_settings->isStrictKillSwitchEnabled());
QByteArray responseBody;
QJsonObject payload; QJsonObject payload;
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first()); payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());
@@ -44,22 +43,26 @@ void ApiNewsController::fetchNews()
payload.insert(configKey::serviceType, stacksJson.value(configKey::serviceType)); payload.insert(configKey::serviceType, stacksJson.value(configKey::serviceType));
} }
ErrorCode errorCode = gatewayController.post(QString("%1v1/news"), payload, responseBody); auto future = gatewayController.postAsync(QString("%1v1/news"), payload);
if (errorCode != ErrorCode::NoError) { future.then(this, [this](QPair<ErrorCode, QByteArray> result) {
emit errorOccurred(errorCode); auto [errorCode, responseBody] = result;
return; if (errorCode != ErrorCode::NoError) {
} emit errorOccurred(errorCode);
return;
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
QJsonArray newsArray;
if (doc.isArray()) {
newsArray = doc.array();
} else if (doc.isObject()) {
QJsonObject obj = doc.object();
if (obj.value("news").isArray()) {
newsArray = obj.value("news").toArray();
} }
}
m_newsModel->updateModel(newsArray); QJsonDocument doc = QJsonDocument::fromJson(responseBody);
QJsonArray newsArray;
if (doc.isArray()) {
newsArray = doc.array();
} else if (doc.isObject()) {
QJsonObject obj = doc.object();
if (obj.value("news").isArray()) {
newsArray = obj.value("news").toArray();
}
}
m_newsModel->updateModel(newsArray);
emit fetchNewsFinished();
});
} }
@@ -23,6 +23,7 @@ public:
signals: signals:
void errorOccurred(ErrorCode errorCode); void errorOccurred(ErrorCode errorCode);
void fetchNewsFinished();
private: private:
QSharedPointer<NewsModel> m_newsModel; QSharedPointer<NewsModel> m_newsModel;
@@ -62,6 +62,7 @@ bool ApiSettingsController::getAccountInfo(bool reload)
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString(); apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
apiPayload[configKey::authData] = authData; apiPayload[configKey::authData] = authData;
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
QByteArray responseBody; QByteArray responseBody;
@@ -431,6 +431,63 @@ bool SettingsController::isOnTv()
#endif #endif
} }
bool SettingsController::isEdgeToEdgeEnabled()
{
#ifdef Q_OS_ANDROID
return AndroidController::instance()->isEdgeToEdgeEnabled();
#else
return false;
#endif
}
int SettingsController::getStatusBarHeight()
{
#ifdef Q_OS_ANDROID
if (m_cachedStatusBarHeight < 0) {
m_cachedStatusBarHeight = AndroidController::instance()->getStatusBarHeight();
}
return m_cachedStatusBarHeight;
#else
return 0;
#endif
}
int SettingsController::getNavigationBarHeight()
{
#ifdef Q_OS_ANDROID
if (m_cachedNavigationBarHeight < 0) {
m_cachedNavigationBarHeight = AndroidController::instance()->getNavigationBarHeight();
}
return m_cachedNavigationBarHeight;
#else
return 0;
#endif
}
int SettingsController::getSafeAreaTopMargin()
{
#ifdef Q_OS_ANDROID
if (isEdgeToEdgeEnabled()) {
int height = getStatusBarHeight();
int result = height > 0 ? height : 40; // fallback to 40 if system returns 0
return result;
}
#endif
return 0;
}
int SettingsController::getSafeAreaBottomMargin()
{
#ifdef Q_OS_ANDROID
if (isEdgeToEdgeEnabled()) {
int height = getNavigationBarHeight();
int result = height > 0 ? height : 56; // fallback to 56 if system returns 0
return result;
}
#endif
return 0;
}
bool SettingsController::isHomeAdLabelVisible() bool SettingsController::isHomeAdLabelVisible()
{ {
return m_settings->isHomeAdLabelVisible(); return m_settings->isHomeAdLabelVisible();
@@ -33,6 +33,8 @@ public:
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged) Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged) Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
Q_PROPERTY(int safeAreaTopMargin READ getSafeAreaTopMargin CONSTANT)
Q_PROPERTY(int safeAreaBottomMargin READ getSafeAreaBottomMargin CONSTANT)
public slots: public slots:
void toggleAmneziaDns(bool enable); void toggleAmneziaDns(bool enable);
@@ -96,6 +98,11 @@ public slots:
void toggleDevGatewayEnv(bool enabled); void toggleDevGatewayEnv(bool enabled);
bool isOnTv(); bool isOnTv();
bool isEdgeToEdgeEnabled();
int getStatusBarHeight();
int getNavigationBarHeight();
int getSafeAreaTopMargin();
int getSafeAreaBottomMargin();
bool isHomeAdLabelVisible(); bool isHomeAdLabelVisible();
void disableHomeAdLabel(); void disableHomeAdLabel();
@@ -134,6 +141,9 @@ private:
QSharedPointer<LanguageModel> m_languageModel; QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<SitesModel> m_sitesModel; QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel; QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
mutable int m_cachedStatusBarHeight = -1;
mutable int m_cachedNavigationBarHeight = -1;
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;
QString m_appVersion; QString m_appVersion;
+5 -11
View File
@@ -31,7 +31,8 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
return tr("Active"); return tr("Active");
} }
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("<p><a style=\"color: #EB5757;\">Inactive</a>") : tr("Active"); return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("<p><a style=\"color: #EB5757;\">Inactive</a>")
: tr("Active");
} }
case EndDateRole: { case EndDateRole: {
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) { if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
@@ -47,16 +48,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
return tr("%1 out of %2").arg(m_accountInfoData.activeDeviceCount).arg(m_accountInfoData.maxDeviceCount); return tr("%1 out of %2").arg(m_accountInfoData.activeDeviceCount).arg(m_accountInfoData.maxDeviceCount);
} }
case ServiceDescriptionRole: { case ServiceDescriptionRole: {
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) { return m_accountInfoData.subscriptionDescription;
return tr("Classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online "
"resources. "
"Speeds up to 200 Mbps");
} 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 "
"more. YouTube is not included in the free plan.");
} else {
return "";
}
} }
case IsComponentVisibleRole: { case IsComponentVisibleRole: {
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2 return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2
@@ -101,6 +93,8 @@ void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, cons
accountInfoData.configType = apiUtils::getConfigType(serverConfig); accountInfoData.configType = apiUtils::getConfigType(serverConfig);
accountInfoData.subscriptionDescription = accountInfoObject.value(apiDefs::key::subscriptionDescription).toString();
for (const auto &protocol : accountInfoObject.value(apiDefs::key::supportedProtocols).toArray()) { for (const auto &protocol : accountInfoObject.value(apiDefs::key::supportedProtocols).toArray()) {
accountInfoData.supportedProtocols.push_back(protocol.toString()); accountInfoData.supportedProtocols.push_back(protocol.toString());
} }
@@ -54,6 +54,8 @@ private:
apiDefs::ConfigType configType; apiDefs::ConfigType configType;
QStringList supportedProtocols; QStringList supportedProtocols;
QString subscriptionDescription;
}; };
AccountInfoData m_accountInfoData; AccountInfoData m_accountInfoData;
+39 -6
View File
@@ -158,6 +158,18 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
QString primaryDns = server.value(config_key::dns1).toString(); QString primaryDns = server.value(config_key::dns1).toString();
return primaryDns == protocols::dns::amneziaDnsIp; return primaryDns == protocols::dns::amneziaDnsIp;
} }
case IsAdVisibleRole:{
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::isAdVisible).toBool(false);
}
case AdHeaderRole: {
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adHeader).toString();
}
case AdDescriptionRole: {
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adDescription).toString();
}
case AdEndpointRole: {
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adEndpoint).toString();
}
} }
return QVariant(); return QVariant();
@@ -403,6 +415,12 @@ QHash<int, QByteArray> ServersModel::roleNames() const
roles[IsCountrySelectionAvailableRole] = "isCountrySelectionAvailable"; roles[IsCountrySelectionAvailableRole] = "isCountrySelectionAvailable";
roles[ApiAvailableCountriesRole] = "apiAvailableCountries"; roles[ApiAvailableCountriesRole] = "apiAvailableCountries";
roles[ApiServerCountryCodeRole] = "apiServerCountryCode"; roles[ApiServerCountryCodeRole] = "apiServerCountryCode";
roles[IsAdVisibleRole] = "isAdVisible";
roles[AdHeaderRole] = "adHeader";
roles[AdDescriptionRole] = "adDescription";
roles[AdEndpointRole] = "adEndpoint";
return roles; return roles;
} }
@@ -784,22 +802,22 @@ void ServersModel::recomputeGatewayStacks()
const bool wasEmpty = m_gatewayStacks.isEmpty(); const bool wasEmpty = m_gatewayStacks.isEmpty();
GatewayStacks computed; GatewayStacks computed;
bool hasNewTags = false; bool hasNewTags = false;
for (int i = 0; i < m_servers.count(); ++i) { for (int i = 0; i < m_servers.count(); ++i) {
if (data(i, IsServerFromGatewayApiRole).toBool()) { if (data(i, IsServerFromGatewayApiRole).toBool()) {
const QJsonObject server = m_servers.at(i).toObject(); const QJsonObject server = m_servers.at(i).toObject();
const QJsonObject apiConfig = server.value(configKey::apiConfig).toObject(); const QJsonObject apiConfig = server.value(configKey::apiConfig).toObject();
const QString userCountryCode = apiConfig.value(configKey::userCountryCode).toString(); const QString userCountryCode = apiConfig.value(configKey::userCountryCode).toString();
const QString serviceType = apiConfig.value(configKey::serviceType).toString(); const QString serviceType = apiConfig.value(configKey::serviceType).toString();
if (!userCountryCode.isEmpty()) { if (!userCountryCode.isEmpty()) {
if (!m_gatewayStacks.userCountryCodes.contains(userCountryCode)) { if (!m_gatewayStacks.userCountryCodes.contains(userCountryCode)) {
hasNewTags = true; hasNewTags = true;
} }
computed.userCountryCodes.insert(userCountryCode); computed.userCountryCodes.insert(userCountryCode);
} }
if (!serviceType.isEmpty()) { if (!serviceType.isEmpty()) {
if (!m_gatewayStacks.serviceTypes.contains(serviceType)) { if (!m_gatewayStacks.serviceTypes.contains(serviceType)) {
hasNewTags = true; hasNewTags = true;
@@ -808,12 +826,12 @@ void ServersModel::recomputeGatewayStacks()
} }
} }
} }
m_gatewayStacks = std::move(computed); m_gatewayStacks = std::move(computed);
if (hasNewTags) { if (hasNewTags) {
emit gatewayStacksExpanded(); emit gatewayStacksExpanded();
} }
if (wasEmpty != m_gatewayStacks.isEmpty()) { if (wasEmpty != m_gatewayStacks.isEmpty()) {
emit hasServersFromGatewayApiChanged(); emit hasServersFromGatewayApiChanged();
} }
@@ -885,3 +903,18 @@ bool ServersModel::processedServerIsPremium() const
{ {
return apiUtils::isPremiumServer(getServerConfig(m_processedServerIndex)); return apiUtils::isPremiumServer(getServerConfig(m_processedServerIndex));
} }
bool ServersModel::isAdVisible()
{
return data(m_defaultServerIndex, IsAdVisibleRole).toBool();
}
QString ServersModel::adHeader()
{
return data(m_defaultServerIndex, AdHeaderRole).toString();
}
QString ServersModel::adDescription()
{
return data(m_defaultServerIndex, AdDescriptionRole).toString();
}
+12
View File
@@ -47,6 +47,10 @@ public:
IsCountrySelectionAvailableRole, IsCountrySelectionAvailableRole,
ApiAvailableCountriesRole, ApiAvailableCountriesRole,
ApiServerCountryCodeRole, ApiServerCountryCodeRole,
IsAdVisibleRole,
AdHeaderRole,
AdDescriptionRole,
AdEndpointRole,
HasAmneziaDns HasAmneziaDns
}; };
@@ -79,6 +83,10 @@ public:
Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged) Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged)
Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerChanged) Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerChanged)
Q_PROPERTY(bool isAdVisible READ isAdVisible NOTIFY defaultServerIndexChanged)
Q_PROPERTY(QString adHeader READ adHeader NOTIFY defaultServerIndexChanged)
Q_PROPERTY(QString adDescription READ adDescription NOTIFY defaultServerIndexChanged)
bool processedServerIsPremium() const; bool processedServerIsPremium() const;
public slots: public slots:
@@ -144,6 +152,10 @@ public slots:
bool isApiKeyExpired(const int serverIndex); bool isApiKeyExpired(const int serverIndex);
void removeApiConfig(const int serverIndex); void removeApiConfig(const int serverIndex);
bool isAdVisible();
QString adHeader();
QString adDescription();
protected: protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
+113 -36
View File
@@ -2,7 +2,6 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Shapes import QtQuick.Shapes
import Qt5Compat.GraphicalEffects
import Style 1.0 import Style 1.0
@@ -13,61 +12,139 @@ import "../Controls2/TextTypes"
Rectangle { Rectangle {
id: root id: root
property real contentHeight: ad.implicitHeight + ad.anchors.topMargin + ad.anchors.bottomMargin property real contentHeight: content.implicitHeight + content.anchors.topMargin + content.anchors.bottomMargin
property bool isFocusable: true
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: AmneziaStyle.color.translucentSlateGray }
GradientStop { position: 1.0; color: AmneziaStyle.color.translucentOnyxBlack }
}
border.width: 1 border.width: 1
border.color: AmneziaStyle.color.goldenApricot border.color: AmneziaStyle.color.onyxBlack
color: AmneziaStyle.color.transparent
radius: 13 radius: 13
visible: false visible: ServersModel.isAdVisible
// visible: GC.isDesktop() && ServersModel.isDefaultServerFromApi
// && ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && SettingsController.isHomeAdLabelVisible
MouseArea { Keys.onTabPressed: {
anchors.fill: parent FocusController.nextKeyTabItem()
cursorShape: Qt.PointingHandCursor }
onClicked: function() { Keys.onBacktabPressed: {
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl("premium")) FocusController.previousKeyTabItem()
} }
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
Keys.onEnterPressed: {
Qt.openUrlExternally(ServersModel.getDefaultServerData("adEndpoint"))
}
Keys.onReturnPressed: {
Qt.openUrlExternally(ServersModel.getDefaultServerData("adEndpoint"))
} }
RowLayout { RowLayout {
id: ad id: content
anchors.fill: parent anchors.fill: parent
anchors.margins: 16 anchors.leftMargin: 16
anchors.rightMargin: 12
anchors.topMargin: 12
anchors.bottomMargin: 12
spacing: 20
Image { ColumnLayout {
source: "qrc:/images/controls/amnezia.svg" Layout.fillWidth: true
sourceSize: Qt.size(36, 36) spacing: 4
layer { CaptionTextType {
effect: ColorOverlay { Layout.fillWidth: true
color: AmneziaStyle.color.paleGray text: ServersModel.adHeader
} color: AmneziaStyle.color.paleGray
font.pixelSize: 14
font.weight: 700
textFormat: Text.RichText
}
CaptionTextType {
Layout.fillWidth: true
text: ServersModel.adDescription
color: AmneziaStyle.color.mutedGray
wrapMode: Text.WordWrap
lineHeight: 18
lineHeightMode: Text.FixedHeight
font.pixelSize: 14
visible: text !== ""
} }
} }
CaptionTextType { Item {
Layout.fillWidth: true implicitWidth: 40
Layout.rightMargin: 10 implicitHeight: 40
Layout.leftMargin: 10 Layout.alignment: Qt.AlignVCenter
text: qsTr("Amnezia Premium - for access to all websites and online resources") Rectangle {
color: AmneziaStyle.color.pearlGray id: chevronBackground
anchors.fill: parent
radius: 12
color: AmneziaStyle.color.transparent
border.width: root.activeFocus ? 1 : 0
border.color: AmneziaStyle.color.paleGray
lineHeight: 18 Behavior on color {
font.pixelSize: 15 PropertyAnimation { duration: 200 }
} }
ImageButtonType { Behavior on border.width {
image: "qrc:/images/controls/close.svg" PropertyAnimation { duration: 200 }
imageColor: AmneziaStyle.color.paleGray }
}
onClicked: function() { Image {
SettingsController.disableHomeAdLabel() anchors.centerIn: parent
source: "qrc:/images/controls/chevron-right.svg"
sourceSize: Qt.size(24, 24)
} }
} }
} }
MouseArea {
id: mouseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: {
chevronBackground.color = AmneziaStyle.color.slateGray
}
onExited: {
chevronBackground.color = AmneziaStyle.color.transparent
}
onPressedChanged: {
chevronBackground.color = pressed ? AmneziaStyle.color.charcoalGray : containsMouse ? AmneziaStyle.color.slateGray : AmneziaStyle.color.transparent
}
onClicked: function() {
root.forceActiveFocus()
Qt.openUrlExternally(ServersModel.getDefaultServerData("adEndpoint"))
}
}
} }
+1 -1
View File
@@ -32,7 +32,7 @@ DrawerType2 {
spacing: 8 spacing: 8
onImplicitHeightChanged: { onImplicitHeightChanged: {
root.expandedHeight = content.implicitHeight + 32 root.expandedHeight = content.implicitHeight + 32 + SettingsController.safeAreaBottomMargin
} }
Header2TextType { Header2TextType {
+1 -1
View File
@@ -15,7 +15,7 @@ Popup {
leftMargin: 25 leftMargin: 25
rightMargin: 25 rightMargin: 25
bottomMargin: 70 bottomMargin: 70 + SettingsController.safeAreaBottomMargin
width: parent.width - leftMargin - rightMargin width: parent.width - leftMargin - rightMargin
@@ -28,5 +28,7 @@ QtObject {
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) readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26)
readonly property color translucentSlateGray: Qt.rgba(85/255, 86/255, 92/255, 0.13)
readonly property color translucentOnyxBlack: Qt.rgba(28/255, 29/255, 33/255, 0.13)
} }
} }
+11 -11
View File
@@ -68,19 +68,9 @@ PageType {
objectName: "homeColumnLayout" objectName: "homeColumnLayout"
anchors.fill: parent anchors.fill: parent
anchors.topMargin: 12 anchors.topMargin: 12 + SettingsController.safeAreaTopMargin
anchors.bottomMargin: 16 anchors.bottomMargin: 16
AdLabel {
id: adLabel
Layout.fillWidth: true
Layout.preferredHeight: adLabel.contentHeight
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 22
}
BasicButtonType { BasicButtonType {
id: loggingButton id: loggingButton
objectName: "loggingButton" objectName: "loggingButton"
@@ -189,6 +179,16 @@ PageType {
parent: root parent: root
} }
} }
AdLabel {
id: adLabel
Layout.fillWidth: true
Layout.preferredHeight: adLabel.contentHeight
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 22
}
} }
} }
@@ -25,7 +25,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: { onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) { if(backButton.enabled && backButton.activeFocus) {
@@ -22,7 +22,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: { onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) { if(backButton.enabled && backButton.activeFocus) {
@@ -23,7 +23,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: { onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) { if(backButton.enabled && backButton.activeFocus) {
+1 -1
View File
@@ -25,7 +25,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
@@ -22,7 +22,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
@@ -22,7 +22,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
@@ -22,7 +22,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
@@ -23,7 +23,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
@@ -22,7 +22,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
@@ -30,7 +30,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
@@ -31,7 +31,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
@@ -31,7 +31,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
+15 -3
View File
@@ -14,6 +14,19 @@ import "../Config"
PageType { PageType {
id: root id: root
Connections {
target: ApiNewsController
function onFetchNewsFinished() {
PageController.showBusyIndicator(false)
}
function onErrorOccurred(errorCode) {
PageController.showErrorMessage(errorCode)
PageController.closePage()
PageController.showBusyIndicator(false)
}
}
ListViewType { ListViewType {
id: listView id: listView
@@ -25,7 +38,7 @@ PageType {
BaseHeaderType { BaseHeaderType {
id: header id: header
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24 + SettingsController.safeAreaTopMargin
Layout.bottomMargin: 16 Layout.bottomMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
@@ -140,9 +153,8 @@ PageType {
return; return;
} }
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
ApiNewsController.fetchNews(); ApiNewsController.fetchNews()
PageController.goToPage(PageEnum.PageSettingsNewsNotifications) PageController.goToPage(PageEnum.PageSettingsNewsNotifications)
PageController.showBusyIndicator(false)
} }
} }
+1 -1
View File
@@ -20,7 +20,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: { onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) { if(backButton.enabled && backButton.activeFocus) {
@@ -23,7 +23,7 @@ PageType {
id: listView id: listView
anchors.fill: parent anchors.fill: parent
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
anchors.bottomMargin: 24 anchors.bottomMargin: 24
model: ApiDevicesModel model: ApiDevicesModel
@@ -79,7 +79,7 @@ PageType {
id: listView id: listView
anchors.fill: parent anchors.fill: parent
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
anchors.bottomMargin: 24 anchors.bottomMargin: 24
model: instructionsModel model: instructionsModel
@@ -28,7 +28,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: { onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) { if(backButton.enabled && backButton.activeFocus) {
@@ -71,7 +71,7 @@ PageType {
text: countryName text: countryName
descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : "" descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : ""
hideDescription: isWorkerExpired ? true : false hideDescription: isWorkerExpired ? false : true
descriptionColor: AmneziaStyle.color.vibrantRed descriptionColor: AmneziaStyle.color.vibrantRed
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
@@ -93,7 +93,7 @@ PageType {
id: backButton id: backButton
objectName: "backButton" objectName: "backButton"
Layout.topMargin: 20 Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
} }
HeaderTypeWithButton { HeaderTypeWithButton {
@@ -59,7 +59,7 @@ PageType {
id: listView id: listView
anchors.fill: parent anchors.fill: parent
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
anchors.bottomMargin: 24 anchors.bottomMargin: 24
model: supportModel model: supportModel
@@ -74,7 +74,7 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
BackButtonType { BackButtonType {
id: backButton id: backButton
@@ -216,7 +216,7 @@ PageType {
Rectangle { Rectangle {
anchors.fill: addAppButton anchors.fill: addAppButton
anchors.bottomMargin: -24 anchors.bottomMargin: -24 - SettingsController.safeAreaBottomMargin
color: AmneziaStyle.color.midnightBlack color: AmneziaStyle.color.midnightBlack
opacity: 0.8 opacity: 0.8
} }
@@ -232,7 +232,7 @@ PageType {
anchors.topMargin: 24 anchors.topMargin: 24
anchors.rightMargin: 16 anchors.rightMargin: 16
anchors.leftMargin: 16 anchors.leftMargin: 16
anchors.bottomMargin: 24 anchors.bottomMargin: 24 + SettingsController.safeAreaBottomMargin
TextFieldWithHeaderType { TextFieldWithHeaderType {
id: searchField id: searchField
@@ -20,7 +20,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: { onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) { if(backButton.enabled && backButton.activeFocus) {
+1 -1
View File
@@ -40,7 +40,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: { onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) { if(backButton.enabled && backButton.activeFocus) {
@@ -20,7 +20,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: { onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) { if(backButton.enabled && backButton.activeFocus) {
+1 -1
View File
@@ -20,7 +20,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
@@ -17,7 +17,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
} }
FlickableType { FlickableType {
@@ -30,7 +30,7 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
BackButtonType { BackButtonType {
id: backButton id: backButton
+1 -1
View File
@@ -22,7 +22,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
@@ -34,7 +34,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
} }
FlickableType { FlickableType {
@@ -19,7 +19,7 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
BackButtonType { BackButtonType {
id: backButton id: backButton
@@ -62,7 +62,7 @@ PageType {
objectName: "mainLayout" objectName: "mainLayout"
anchors.fill: parent anchors.fill: parent
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
spacing: 4 spacing: 4
@@ -27,7 +27,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
@@ -25,7 +25,7 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
BackButtonType { BackButtonType {
id: backButton id: backButton
@@ -88,7 +88,7 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
BackButtonType { BackButtonType {
id: backButton id: backButton
@@ -21,7 +21,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
@@ -22,7 +22,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
Layout.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: { onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) { if(backButton.enabled && backButton.activeFocus) {
@@ -41,7 +41,7 @@ PageType {
property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible() property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible()
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24 + SettingsController.safeAreaTopMargin
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
@@ -19,7 +19,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
+1 -1
View File
@@ -40,7 +40,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: { onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) { if(backButton.enabled && backButton.activeFocus) {
@@ -35,7 +35,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
@@ -41,7 +41,7 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: { onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) { if(backButton.enabled && backButton.activeFocus) {
@@ -19,7 +19,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
@@ -22,7 +22,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: { onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) { if(backButton.enabled && backButton.activeFocus) {
+1 -1
View File
@@ -173,7 +173,7 @@ PageType {
HeaderTypeWithButton { HeaderTypeWithButton {
id: header id: header
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24 + SettingsController.safeAreaTopMargin
headerText: qsTr("Share VPN Access") headerText: qsTr("Share VPN Access")
+2 -2
View File
@@ -47,7 +47,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
} }
Text { Text {
@@ -55,7 +55,7 @@ PageType {
anchors.top: backButton.bottom anchors.top: backButton.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
anchors.leftMargin: 16 anchors.leftMargin: 16
anchors.rightMargin: 16 anchors.rightMargin: 16
+1 -1
View File
@@ -25,7 +25,7 @@ PageType {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
+1 -1
View File
@@ -306,7 +306,7 @@ PageType {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
topPadding: 8 topPadding: 8
bottomPadding: 8 bottomPadding: 8 + SettingsController.safeAreaBottomMargin
leftPadding: 96 leftPadding: 96
rightPadding: 96 rightPadding: 96
+21 -2
View File
@@ -16,6 +16,26 @@ Window {
id: root id: root
objectName: "mainWindow" objectName: "mainWindow"
Connections {
target: Qt.application
function onStateChanged() {
if (Qt.platform.os === "android" && Qt.application.state === Qt.ApplicationActive) {
refreshTimer.restart()
}
}
}
Timer {
id: refreshTimer
interval: 150
repeat: false
onTriggered: {
if (Qt.platform.os === "android" && SettingsController.isEdgeToEdgeEnabled()) {
console.log("QML: Application resumed with edge-to-edge")
}
}
}
visible: true visible: true
width: GC.screenWidth width: GC.screenWidth
height: GC.screenHeight height: GC.screenHeight
@@ -111,7 +131,6 @@ Window {
PageStart { PageStart {
objectName: "pageStart" objectName: "pageStart"
width: root.width width: root.width
height: root.height height: root.height
} }
@@ -164,7 +183,7 @@ Window {
id: privateKeyPassphraseDrawer id: privateKeyPassphraseDrawer
anchors.fill: parent anchors.fill: parent
expandedHeight: root.height * 0.35 expandedHeight: root.height * 0.35 + SettingsController.safeAreaBottomMargin
expandedStateContent: ColumnLayout { expandedStateContent: ColumnLayout {
anchors.top: parent.top anchors.top: parent.top