Compare commits

...

15 Commits

Author SHA1 Message Date
vkamn 06ea7b9316 Merge branch 'dev' of github-amnezia:amnezia-vpn/amnezia-client into feat/add-system-dns-support 2025-11-13 19:44:27 +08:00
NickVs2015 b53cdcff08 fix: fix self-hosted TextFields and Keyboard reset issue (#1983)
Co-authored-by: vkamn <vk@amnezia.org>
2025-11-12 15:57:53 +08:00
vkamn 3cc18c5807 chore: bump version (#1982) 2025-11-11 23:03:24 +08:00
NickVs2015 5fdce1e49e fix: fix ui android issues (#1980)
* Fix UI issues

* Fix Screen Swipe
2025-11-11 22:03:27 +08:00
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
aiamnezia 498607cd16 Add collecting dns from interface settings on Linux 2025-11-03 01:52:46 +04:00
aiamnezia cd03e11dde fix: Fix bag with endian conversion in DnsResolver 2025-11-03 01:17:42 +04:00
aiamnezia 1f9f71f244 chore: fix Android build 2025-11-02 21:38:41 +04:00
aiamnezia d230043ead Add system dns toggle 2025-11-02 18:38:49 +04:00
vkamn f1481b1b1f feat: add async post in gateway controller (#1963) 2025-10-29 23:24:24 +08:00
vkamn f6e7d3ccf1 fix: minor ui fixes (#1917)
* feat: improve storage processing

* fix: minor ui fixes
2025-10-09 23:22:58 +08:00
95 changed files with 1569 additions and 342 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.8.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.2)
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 2097)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
+2 -1
View File
@@ -45,7 +45,8 @@
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|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="adjustResize|stateUnchanged"
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,11 @@ 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.graphics.Insets
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
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 +175,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 +269,82 @@ 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
}
// Workaround for Android 14 (API 34+) IME adjustResize bug
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
setupImeInsetsListener()
}
} else {
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black)
}
}
}
private fun setupImeInsetsListener() {
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
val imeHeight = if (imeVisible) imeInsets.bottom else 0
val density = resources.displayMetrics.density
val imeHeightDp = (imeHeight / density).toInt()
// Also track system bars (navigation bar, status bar) changes
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val navBarHeight = systemBarsInsets.bottom
val navBarHeightDp = (navBarHeight / density).toInt()
val statusBarHeight = systemBarsInsets.top
val statusBarHeightDp = (statusBarHeight / density).toInt()
mainScope.launch {
qtInitialized.await()
QtAndroidController.onImeInsetsChanged(imeHeightDp)
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
}
// Return windowInsets instead of CONSUMED to allow proper handling
windowInsets
}
}
override fun onDestroy() { override fun onDestroy() {
Log.d(TAG, "Destroy Amnezia activity") Log.d(TAG, "Destroy Amnezia activity")
unregisterBroadcastReceiver(notificationStateReceiver) unregisterBroadcastReceiver(notificationStateReceiver)
@@ -666,6 +746,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 {
@@ -28,4 +28,7 @@ object QtAndroidController {
external fun onAuthResult(result: Boolean) external fun onAuthResult(result: Boolean)
external fun decodeQrCode(data: String): Boolean external fun decodeQrCode(data: String): Boolean
external fun onImeInsetsChanged(heightDp: Int)
external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: 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
+97 -29
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;
@@ -192,11 +259,12 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
QStringList proxyStorageUrls; QStringList proxyStorageUrls;
if (!serviceType.isEmpty()) { if (!serviceType.isEmpty()) {
for (const auto &baseUrl : baseUrls) { for (const auto &baseUrl : baseUrls) {
proxyStorageUrls.push_back(baseUrl + "-" + serviceType + "-" + userCountryCode + ".json"); QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
proxyStorageUrls.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + ".json");
} }
} }
for (const auto &baseUrl : baseUrls) { for (const auto &baseUrl : baseUrls) {
proxyStorageUrls.push_back(baseUrl + ".json"); proxyStorageUrls.push_back(baseUrl + "endpoints.json");
} }
for (const auto &proxyStorageUrl : proxyStorageUrls) { for (const auto &proxyStorageUrl : proxyStorageUrls) {
@@ -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 = "");
+338 -1
View File
@@ -13,6 +13,7 @@
#include <QNetworkInterface> #include <QNetworkInterface>
#include "qendian.h" #include "qendian.h"
#include <QSettings> #include <QSettings>
#pragma comment(lib, "iphlpapi.lib")
#endif #endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
#include <arpa/inet.h> #include <arpa/inet.h>
@@ -23,6 +24,28 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <unistd.h> #include <unistd.h>
#endif #endif
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#include <QDBusReply>
#include <QDBusArgument>
#include <QDBusObjectPath>
#include <QVariant>
#include <QVariantMap>
#include <QFile>
#include <QTextStream>
#include <QNetworkInterface>
#include <algorithm>
#include <climits>
#include <limits>
#include "platforms/linux/daemon/dbustypeslinux.h"
constexpr const char* DBUS_RESOLVE_SERVICE = "org.freedesktop.resolve1";
constexpr const char* DBUS_RESOLVE_PATH = "/org/freedesktop/resolve1";
constexpr const char* DBUS_RESOLVE_MANAGER = "org.freedesktop.resolve1.Manager";
constexpr const char* DBUS_PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE) #if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
#include <sys/param.h> #include <sys/param.h>
#include <sys/sysctl.h> #include <sys/sysctl.h>
@@ -30,10 +53,17 @@
#include <netinet/in.h> #include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <net/route.h> #include <net/route.h>
#include <CoreFoundation/CoreFoundation.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <QFile>
#include <QTextStream>
#include <QRegularExpression>
#endif #endif
#include <QHostAddress> #include <QHostAddress>
#include <QHostInfo> #include <QHostInfo>
#include <QPair>
#include "logger.h"
QRegularExpression NetworkUtilities::ipAddressRegExp() QRegularExpression NetworkUtilities::ipAddressRegExp()
{ {
@@ -277,7 +307,7 @@ QString NetworkUtilities::getGatewayAndIface()
free(pAdapterAddresses); free(pAdapterAddresses);
return result; return result;
#endif #endif
#ifdef Q_OS_LINUX #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
constexpr int BUFFER_SIZE = 100; constexpr int BUFFER_SIZE = 100;
int received_bytes = 0, msg_len = 0, route_attribute_len = 0; int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
int sock = -1, msgseq = 0; int sock = -1, msgseq = 0;
@@ -475,3 +505,310 @@ QString NetworkUtilities::getGatewayAndIface()
return gateway; return gateway;
#endif #endif
} }
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
namespace {
struct LinkInfo {
int ifindex;
bool defaultRoute;
quint32 routeMetric;
QStringList dnsServers;
bool operator<(const LinkInfo& other) const {
// Sort by defaultRoute first (true comes first), then by routeMetric (lower is better)
if (defaultRoute != other.defaultRoute) {
return defaultRoute > other.defaultRoute;
}
return routeMetric < other.routeMetric;
}
};
QStringList extractDnsFromDbusArgument(const QDBusArgument& arg) {
QStringList dnsServers;
QDBusArgument dnsArg = arg;
QList<DnsResolver> resolverList = qdbus_cast<QList<DnsResolver>>(dnsArg);
for (const auto& resolver : resolverList) {
if (resolver.protocol() == QAbstractSocket::IPv4Protocol) {
const QString dnsStr = resolver.toString();
if (NetworkUtilities::checkIPv4Format(dnsStr) && !dnsServers.contains(dnsStr)) {
dnsServers.append(dnsStr);
}
}
}
return dnsServers;
}
QList<LinkInfo> getDnsFromInterfaces(const QDBusConnection& bus) {
QList<LinkInfo> links;
// Get all network interfaces
QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
for (const QNetworkInterface& iface : interfaces) {
int ifindex = iface.index();
if (ifindex <= 0) {
continue;
}
// Call GetLink to get the link object path
QDBusMessage getLinkMsg = QDBusMessage::createMethodCall(
DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_RESOLVE_MANAGER, "GetLink");
getLinkMsg << ifindex;
QDBusReply<QDBusObjectPath> linkReply = bus.call(getLinkMsg);
if (!linkReply.isValid()) {
continue;
}
QString linkPath = linkReply.value().path();
// Get properties from the link object
QDBusMessage getPropMsg = QDBusMessage::createMethodCall(
DBUS_RESOLVE_SERVICE, linkPath, DBUS_PROPERTY_INTERFACE, "GetAll");
getPropMsg << QString("org.freedesktop.resolve1.Link");
QDBusReply<QVariantMap> propReply = bus.call(getPropMsg);
if (!propReply.isValid()) {
continue;
}
QVariantMap properties = propReply.value();
// Check if DefaultRoute is true
bool defaultRoute = properties.value("DefaultRoute").toBool();
// Get RouteMetric (default to maximum if not available)
quint32 routeMetric = std::numeric_limits<quint32>::max();
if (properties.contains("RouteMetric")) {
routeMetric = properties.value("RouteMetric").toUInt();
}
// Get DNS servers - use Get method directly for DNS property
QDBusMessage getDnsMsg = QDBusMessage::createMethodCall(
DBUS_RESOLVE_SERVICE, linkPath, DBUS_PROPERTY_INTERFACE, "Get");
getDnsMsg << QString("org.freedesktop.resolve1.Link");
getDnsMsg << QString("DNS");
QDBusReply<QVariant> dnsReply = bus.call(getDnsMsg);
if (!dnsReply.isValid()) {
continue;
}
QVariant dnsVariant = dnsReply.value();
QStringList dnsServers;
if (dnsVariant.canConvert<QDBusArgument>()) {
QDBusArgument dnsArg = qvariant_cast<QDBusArgument>(dnsVariant);
dnsServers = extractDnsFromDbusArgument(dnsArg);
}
if (!dnsServers.isEmpty()) {
LinkInfo info;
info.ifindex = ifindex;
info.defaultRoute = defaultRoute;
info.routeMetric = routeMetric;
info.dnsServers = dnsServers;
links.append(info);
}
}
return links;
}
}
#endif
QPair<QString, QString> NetworkUtilities::getSystemDnsAddress()
{
QPair<QString, QString> result;
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
// Try systemd-resolved via D-Bus first
QDBusConnection bus = QDBusConnection::systemBus();
if (bus.isConnected()) {
// Try to get DNS from Resolve DNS property using org.freedesktop.DBus.Properties
QDBusMessage message = QDBusMessage::createMethodCall(
DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_PROPERTY_INTERFACE, "Get");
message << QString(DBUS_RESOLVE_MANAGER);
message << QString("DNS");
QDBusReply<QVariant> dnsReply = bus.call(message);
if (dnsReply.isValid()) {
QDBusArgument dnsArg = qvariant_cast<QDBusArgument>(dnsReply.value());
QList<DnsResolver> resolverList = qdbus_cast<QList<DnsResolver>>(dnsArg);
QStringList dnsServers;
for (const auto& resolver : resolverList) {
if (resolver.protocol() == QAbstractSocket::IPv4Protocol) {
QString dnsStr = resolver.toString();
if (checkIPv4Format(dnsStr) && !dnsServers.contains(dnsStr)) {
dnsServers.append(dnsStr);
}
}
}
if (!dnsServers.isEmpty()) {
result.first = dnsServers.first();
if (dnsServers.size() > 1) {
result.second = dnsServers.at(1);
}
return result;
}
}
// If no global DNS, try to get DNS from interfaces
QList<LinkInfo> links = getDnsFromInterfaces(bus);
if (!links.isEmpty()) {
// Sort by priority: DefaultRoute first, then by RouteMetric
std::sort(links.begin(), links.end());
// Get DNS from the highest priority interface
const LinkInfo& bestLink = links.first();
result.first = bestLink.dnsServers.first();
if (bestLink.dnsServers.size() > 1) {
result.second = bestLink.dnsServers.at(1);
}
qDebug() << "Got DNS from interface" << bestLink.ifindex
<< "DefaultRoute:" << bestLink.defaultRoute
<< "RouteMetric:" << bestLink.routeMetric;
return result;
}
}
// Fallback to /etc/resolv.conf
QFile resolvConf("/etc/resolv.conf");
if (resolvConf.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&resolvConf);
QStringList dnsServers;
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
if (line.startsWith("nameserver")) {
QStringList parts = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
if (parts.size() >= 2) {
QString dns = parts.at(1);
if (checkIPv4Format(dns)) {
dnsServers.append(dns);
}
}
}
}
if (!dnsServers.isEmpty()) {
result.first = dnsServers.first();
if (dnsServers.size() > 1) {
result.second = dnsServers.at(1);
}
return result;
}
}
qWarning() << "Failed to get system DNS on Linux";
return result; // Return empty pair
#elif defined(Q_OS_WIN)
// Use GetAdaptersAddresses to get DNS servers
ULONG bufferSize = 0;
DWORD ret = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, nullptr, &bufferSize);
if (ret == ERROR_BUFFER_OVERFLOW) {
PIP_ADAPTER_ADDRESSES adapterAddresses = (PIP_ADAPTER_ADDRESSES)malloc(bufferSize);
if (adapterAddresses) {
ret = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, adapterAddresses, &bufferSize);
if (ret == NO_ERROR) {
PIP_ADAPTER_ADDRESSES currentAdapter = adapterAddresses;
QStringList dnsServers;
while (currentAdapter) {
// Check if adapter is active and has IP addresses
if (currentAdapter->OperStatus == IfOperStatusUp &&
currentAdapter->FirstUnicastAddress != nullptr) {
PIP_ADAPTER_DNS_SERVER_ADDRESS dnsServer = currentAdapter->FirstDnsServerAddress;
while (dnsServer) {
if (dnsServer->Address.lpSockaddr->sa_family == AF_INET) {
struct sockaddr_in* sa_in = (struct sockaddr_in*)dnsServer->Address.lpSockaddr;
char ipstr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &sa_in->sin_addr, ipstr, INET_ADDRSTRLEN);
QString dns = QString::fromLatin1(ipstr);
if (checkIPv4Format(dns) && !dnsServers.contains(dns)) {
dnsServers.append(dns);
}
}
dnsServer = dnsServer->Next;
}
}
currentAdapter = currentAdapter->Next;
}
if (!dnsServers.isEmpty()) {
result.first = dnsServers.first();
if (dnsServers.size() > 1) {
result.second = dnsServers.at(1);
}
qDebug() << "Got system DNS from Windows:" << result.first << result.second;
free(adapterAddresses);
return result;
}
}
free(adapterAddresses);
}
}
qWarning() << "Failed to get system DNS on Windows";
return result; // Return empty pair
#elif defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
// Use SCDynamicStore to get DNS from system configuration
SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("amneziavpn"), nullptr, nullptr);
if (store) {
CFDictionaryRef dnsDict = (CFDictionaryRef)SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/DNS"));
if (dnsDict) {
CFArrayRef dnsServersArray = (CFArrayRef)CFDictionaryGetValue(dnsDict, CFSTR("ServerAddresses"));
if (dnsServersArray && CFArrayGetCount(dnsServersArray) > 0) {
QStringList dnsServers;
for (CFIndex i = 0; i < CFArrayGetCount(dnsServersArray); i++) {
CFStringRef dnsString = (CFStringRef)CFArrayGetValueAtIndex(dnsServersArray, i);
if (dnsString) {
char buffer[256];
if (CFStringGetCString(dnsString, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
QString dns = QString::fromLatin1(buffer);
if (checkIPv4Format(dns)) {
dnsServers.append(dns);
}
}
}
}
if (!dnsServers.isEmpty()) {
result.first = dnsServers.first();
if (dnsServers.size() > 1) {
result.second = dnsServers.at(1);
}
qDebug() << "Got system DNS from macOS:" << result.first << result.second;
CFRelease(dnsDict);
CFRelease(store);
return result;
}
}
CFRelease(dnsDict);
}
CFRelease(store);
}
qWarning() << "Failed to get system DNS on macOS";
return result; // Return empty pair
#else
qWarning() << "System DNS reading not implemented for this platform";
return result; // Return empty pair
#endif
}
+4
View File
@@ -6,6 +6,7 @@
#include <QString> #include <QString>
#include <QHostAddress> #include <QHostAddress>
#include <QNetworkReply> #include <QNetworkReply>
#include <QPair>
class NetworkUtilities : public QObject class NetworkUtilities : public QObject
@@ -31,6 +32,9 @@ public:
static QString netMaskFromIpWithSubnet(const QString ip); static QString netMaskFromIpWithSubnet(const QString ip);
static QString ipAddressFromIpWithSubnet(const QString ip); static QString ipAddressFromIpWithSubnet(const QString ip);
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr); static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
// Returns pair of (primary DNS, secondary DNS) or empty strings on error
static QPair<QString, QString> getSystemDnsAddress();
}; };
#endif // NETWORKUTILITIES_H #endif // NETWORKUTILITIES_H
@@ -99,7 +99,9 @@ bool AndroidController::initialize()
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)}, {"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)}, {"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
{"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)}, {"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)},
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)} {"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)},
{"onImeInsetsChanged", "(I)V", reinterpret_cast<void *>(onImeInsetsChanged)},
{"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast<void *>(onSystemBarsInsetsChanged)}
}; };
QJniEnvironment env; QJniEnvironment env;
@@ -202,6 +204,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");
@@ -521,3 +538,23 @@ bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data)
return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data)); return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data));
} }
// static
void AndroidController::onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
qDebug() << "Android IME insets changed: height =" << heightDp << "dp";
emit AndroidController::instance()->imeInsetsChanged(heightDp);
}
// static
void AndroidController::onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
qDebug() << "Android system bars insets changed: nav bar =" << navBarHeightDp << "dp, status bar =" << statusBarHeightDp << "dp";
emit AndroidController::instance()->systemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp);
}
@@ -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);
@@ -70,6 +73,8 @@ signals:
void importConfigFromOutside(QString config); void importConfigFromOutside(QString config);
void initConnectionState(Vpn::ConnectionState state); void initConnectionState(Vpn::ConnectionState state);
void authenticationResult(bool result); void authenticationResult(bool result);
void imeInsetsChanged(int heightDp);
void systemBarsInsetsChanged(int navBarHeightDp, int statusBarHeightDp);
private: private:
bool isWaitingStatus = true; bool isWaitingStatus = true;
@@ -98,6 +103,8 @@ private:
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri); static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
static void onAuthResult(JNIEnv *env, jobject thiz, jboolean result); static void onAuthResult(JNIEnv *env, jobject thiz, jboolean result);
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data); static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
static void onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp);
static void onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp);
template <typename Ret, typename ...Args> template <typename Ret, typename ...Args>
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args); static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
+5 -12
View File
@@ -11,6 +11,7 @@
#include <QDBusArgument> #include <QDBusArgument>
#include <QHostAddress> #include <QHostAddress>
#include <QtDBus/QtDBus> #include <QtDBus/QtDBus>
#include <QtEndian>
/* D-Bus metatype for marshalling arguments to the SetLinkDNS method */ /* D-Bus metatype for marshalling arguments to the SetLinkDNS method */
class DnsResolver : public QHostAddress { class DnsResolver : public QHostAddress {
@@ -25,12 +26,8 @@ class DnsResolver : public QHostAddress {
args << AF_INET6; args << AF_INET6;
args << QByteArray::fromRawData((const char*)&addrv6, sizeof(addrv6)); args << QByteArray::fromRawData((const char*)&addrv6, sizeof(addrv6));
} else { } else {
quint32 addrv4 = ip.toIPv4Address(); quint32 addrv4 = qToBigEndian(ip.toIPv4Address());
QByteArray data(4, 0); QByteArray data = QByteArray::fromRawData((const char*)&addrv4, sizeof(addrv4));
data[0] = (addrv4 >> 24) & 0xff;
data[1] = (addrv4 >> 16) & 0xff;
data[2] = (addrv4 >> 8) & 0xff;
data[3] = (addrv4 >> 0) & 0xff;
args << AF_INET; args << AF_INET;
args << data; args << data;
} }
@@ -46,12 +43,8 @@ class DnsResolver : public QHostAddress {
args.endStructure(); args.endStructure();
if (family == AF_INET6) { if (family == AF_INET6) {
ip.setAddress(data.constData()); ip.setAddress(data.constData());
} else if (data.count() >= 4) { } else if (data.size() >= 4) {
quint32 addrv4 = 0; const quint32 addrv4 = qFromBigEndian<quint32>(data.constData());
addrv4 |= (data[0] << 24);
addrv4 |= (data[1] << 16);
addrv4 |= (data[2] << 8);
addrv4 |= (data[3] << 0);
ip.setAddress(addrv4); ip.setAddress(addrv4);
} }
return args; return args;
+1 -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>
@@ -248,6 +247,7 @@
<file>ui/qml/Components/OtpCodeDrawer.qml</file> <file>ui/qml/Components/OtpCodeDrawer.qml</file>
<file>ui/qml/Components/AwgTextField.qml</file> <file>ui/qml/Components/AwgTextField.qml</file>
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file> <file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
<file>ui/qml/Components/SmartScroll.qml</file>
</qresource> </qresource>
<qresource prefix="/countriesFlags"> <qresource prefix="/countriesFlags">
<file>images/flagKit/ZW.svg</file> <file>images/flagKit/ZW.svg</file>
+9
View File
@@ -145,6 +145,15 @@ public:
setValue("Conf/useAmneziaDns", enabled); setValue("Conf/useAmneziaDns", enabled);
} }
bool useSystemDnsAddress() const
{
return value("Conf/useSystemDnsAddress", false).toBool();
}
void setUseSystemDnsAddress(bool enabled)
{
setValue("Conf/useSystemDnsAddress", enabled);
}
QString primaryDns() const; QString primaryDns() const;
QString secondaryDns() const; QString secondaryDns() const;
+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;
@@ -58,6 +58,12 @@ void ConnectionController::openConnection()
auto dns = m_serversModel->getDnsPair(serverIndex); auto dns = m_serversModel->getDnsPair(serverIndex);
// Check if DNS retrieval failed (empty pair means system DNS retrieval failed)
if (dns.first.isEmpty() && dns.second.isEmpty()) {
emit connectionErrorOccurred(ErrorCode::InternalError);
return;
}
auto vpnConfiguration = vpnConfigurationController.createVpnConfiguration(dns, serverConfig, containerConfig, container); auto vpnConfiguration = vpnConfigurationController.createVpnConfiguration(dns, serverConfig, containerConfig, container);
emit connectToVpn(serverIndex, credentials, container, vpnConfiguration); emit connectToVpn(serverIndex, credentials, container, vpnConfiguration);
} }
@@ -1,6 +1,7 @@
#include "settingsController.h" #include "settingsController.h"
#include <QStandardPaths> #include <QStandardPaths>
#include <QOperatingSystemVersion>
#include "logger.h" #include "logger.h"
#include "systemController.h" #include "systemController.h"
@@ -33,6 +34,17 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
checkIfNeedDisableLogs(); checkIfNeedDisableLogs();
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsController::onNotificationStateChanged); connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsController::onNotificationStateChanged);
connect(AndroidController::instance(), &AndroidController::imeInsetsChanged, this, [this](int heightDp) {
m_imeHeight = heightDp;
emit imeHeightChanged(heightDp);
emit safeAreaBottomMarginChanged();
});
connect(AndroidController::instance(), &AndroidController::systemBarsInsetsChanged, this, [this](int navBarHeightDp, int statusBarHeightDp) {
m_cachedNavigationBarHeight = navBarHeightDp;
m_cachedStatusBarHeight = statusBarHeightDp;
emit safeAreaBottomMarginChanged();
emit safeAreaTopMarginChanged();
});
#endif #endif
m_isDevModeEnabled = m_settings->isDevGatewayEnv(); m_isDevModeEnabled = m_settings->isDevGatewayEnv();
@@ -67,6 +79,17 @@ bool SettingsController::isAmneziaDnsEnabled()
return m_settings->useAmneziaDns(); return m_settings->useAmneziaDns();
} }
bool SettingsController::isUseSystemDnsAddressEnabled()
{
return m_settings->useSystemDnsAddress();
}
void SettingsController::setUseSystemDnsAddress(bool enable)
{
m_settings->setUseSystemDnsAddress(enable);
emit useSystemDnsAddressChanged(enable);
}
QString SettingsController::getPrimaryDns() QString SettingsController::getPrimaryDns()
{ {
return m_settings->primaryDns(); return m_settings->primaryDns();
@@ -431,6 +454,76 @@ bool SettingsController::isOnTv()
#endif #endif
} }
bool SettingsController::isEdgeToEdgeEnabled()
{
#ifdef Q_OS_ANDROID
if (!m_edgeToEdgeCached) {
m_cachedEdgeToEdgeEnabled = AndroidController::instance()->isEdgeToEdgeEnabled();
m_edgeToEdgeCached = true;
}
return m_cachedEdgeToEdgeEnabled;
#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()) {
if (m_imeHeight > 0) {
return 0;
}
int height = getNavigationBarHeight();
int result = height > 0 ? height : 56; // fallback to 56 if system returns 0
return result;
}
#endif
return 0;
}
int SettingsController::getImeHeight()
{
return m_imeHeight;
}
bool SettingsController::isHomeAdLabelVisible() bool SettingsController::isHomeAdLabelVisible()
{ {
return m_settings->isHomeAdLabelVisible(); return m_settings->isHomeAdLabelVisible();
@@ -26,6 +26,7 @@ public:
Q_PROPERTY(bool isNotificationPermissionGranted READ isNotificationPermissionGranted NOTIFY onNotificationStateChanged) Q_PROPERTY(bool isNotificationPermissionGranted READ isNotificationPermissionGranted NOTIFY onNotificationStateChanged)
Q_PROPERTY(bool isKillSwitchEnabled READ isKillSwitchEnabled WRITE toggleKillSwitch NOTIFY killSwitchEnabledChanged) Q_PROPERTY(bool isKillSwitchEnabled READ isKillSwitchEnabled WRITE toggleKillSwitch NOTIFY killSwitchEnabledChanged)
Q_PROPERTY(bool strictKillSwitchEnabled READ isStrictKillSwitchEnabled WRITE toggleStrictKillSwitch NOTIFY strictKillSwitchEnabledChanged) Q_PROPERTY(bool strictKillSwitchEnabled READ isStrictKillSwitchEnabled WRITE toggleStrictKillSwitch NOTIFY strictKillSwitchEnabledChanged)
Q_PROPERTY(bool useSystemDnsAddressEnabled READ isUseSystemDnsAddressEnabled WRITE setUseSystemDnsAddress NOTIFY useSystemDnsAddressChanged)
Q_PROPERTY(bool isDevModeEnabled READ isDevModeEnabled NOTIFY devModeEnabled) Q_PROPERTY(bool isDevModeEnabled READ isDevModeEnabled NOTIFY devModeEnabled)
Q_PROPERTY(QString gatewayEndpoint READ getGatewayEndpoint WRITE setGatewayEndpoint NOTIFY gatewayEndpointChanged) Q_PROPERTY(QString gatewayEndpoint READ getGatewayEndpoint WRITE setGatewayEndpoint NOTIFY gatewayEndpointChanged)
@@ -33,11 +34,17 @@ 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 NOTIFY safeAreaTopMarginChanged)
Q_PROPERTY(int safeAreaBottomMargin READ getSafeAreaBottomMargin NOTIFY safeAreaBottomMarginChanged)
Q_PROPERTY(int imeHeight READ getImeHeight NOTIFY imeHeightChanged)
public slots: public slots:
void toggleAmneziaDns(bool enable); void toggleAmneziaDns(bool enable);
bool isAmneziaDnsEnabled(); bool isAmneziaDnsEnabled();
bool isUseSystemDnsAddressEnabled();
void setUseSystemDnsAddress(bool enable);
QString getPrimaryDns(); QString getPrimaryDns();
void setPrimaryDns(const QString &dns); void setPrimaryDns(const QString &dns);
@@ -96,6 +103,12 @@ public slots:
void toggleDevGatewayEnv(bool enabled); void toggleDevGatewayEnv(bool enabled);
bool isOnTv(); bool isOnTv();
bool isEdgeToEdgeEnabled();
int getStatusBarHeight();
int getNavigationBarHeight();
int getSafeAreaTopMargin();
int getSafeAreaBottomMargin();
int getImeHeight();
bool isHomeAdLabelVisible(); bool isHomeAdLabelVisible();
void disableHomeAdLabel(); void disableHomeAdLabel();
@@ -117,6 +130,8 @@ signals:
void amneziaDnsToggled(bool enable); void amneziaDnsToggled(bool enable);
void useSystemDnsAddressChanged(bool enabled);
void loggingDisableByWatcher(); void loggingDisableByWatcher();
void onNotificationStateChanged(); void onNotificationStateChanged();
@@ -125,6 +140,10 @@ signals:
void gatewayEndpointChanged(const QString &endpoint); void gatewayEndpointChanged(const QString &endpoint);
void devGatewayEnvChanged(bool enabled); void devGatewayEnvChanged(bool enabled);
void imeHeightChanged(int height);
void safeAreaTopMarginChanged();
void safeAreaBottomMarginChanged();
void isHomeAdLabelVisibleChanged(bool visible); void isHomeAdLabelVisibleChanged(bool visible);
void startMinimizedChanged(); void startMinimizedChanged();
@@ -134,6 +153,12 @@ 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;
mutable bool m_cachedEdgeToEdgeEnabled = false;
mutable bool m_edgeToEdgeCached = false;
int m_imeHeight = 0;
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;
+53
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;
} }
@@ -603,6 +621,26 @@ QPair<QString, QString> ServersModel::getDnsPair(int serverIndex)
} }
} }
// Check if system DNS should be used
if (m_settings->useSystemDnsAddress() && apiUtils::isPremiumServer(server)) {
auto systemDns = NetworkUtilities::getSystemDnsAddress();
bool hasPrimary = !systemDns.first.isEmpty() && NetworkUtilities::checkIPv4Format(systemDns.first);
bool hasSecondary = !systemDns.second.isEmpty() && NetworkUtilities::checkIPv4Format(systemDns.second);
if (hasPrimary || hasSecondary) {
if (hasPrimary) {
dns.first = systemDns.first;
}
if (hasSecondary) {
dns.second = systemDns.second;
}
qDebug() << "VpnConfigurator::getDnsForConfig using system DNS:" << dns.first << dns.second;
} else {
qWarning() << "Failed to get system DNS for premium config, connection will fail";
}
return dns;
}
dns.first = server.value(config_key::dns1).toString(); dns.first = server.value(config_key::dns1).toString();
dns.second = server.value(config_key::dns2).toString(); dns.second = server.value(config_key::dns2).toString();
@@ -885,3 +923,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 {
+55
View File
@@ -0,0 +1,55 @@
import QtQuick
import QtQuick.Controls
QtObject {
id: root
property var listView: null
property var scrollToItemTarget: null
property Connections imeConnection: Connections {
target: SettingsController
function onImeHeightChanged() {
if (root.scrollToItemTarget && SettingsController.imeHeight > 0) {
scrollTimer.restart()
}
}
}
property Timer scrollTimer: Timer {
interval: 100
repeat: false
onTriggered: {
if (root.scrollToItemTarget && root.listView) {
if (SettingsController.imeHeight > 0) {
var item = root.scrollToItemTarget
var itemY = item.mapToItem(root.listView.contentItem, 0, 0).y
var itemHeight = item.height
var keyboardHeight = SettingsController.imeHeight + SettingsController.safeAreaBottomMargin
var visibleHeight = root.listView.height - keyboardHeight
var desiredTopOffset = visibleHeight * 0.25
var targetContentY = itemY - desiredTopOffset
if (targetContentY < 0) {
targetContentY = 0
}
var maxContentY = root.listView.contentHeight - root.listView.height
if (targetContentY > maxContentY) {
targetContentY = maxContentY
}
root.listView.contentY = targetContentY
root.scrollToItemTarget = null
}
}
}
}
function scrollToItem(item) {
scrollToItemTarget = item
scrollTimer.restart()
}
}
+16
View File
@@ -49,6 +49,22 @@ Item {
return drawerContent.state === stateName return drawerContent.state === stateName
} }
Connections {
target: Qt.application
function onStateChanged() {
if (Qt.application.state !== Qt.ApplicationActive) {
if (dragArea.drag.active) {
dragArea.drag.target = null
dragArea.drag.target = drawerContent
}
if (isOpened && !isCollapsedStateActive()) {
root.closeTriggered()
}
}
}
}
Connections { Connections {
target: PageController target: PageController
+12
View File
@@ -74,6 +74,18 @@ Item {
FocusController.nextKeyRightItem() FocusController.nextKeyRightItem()
} }
Connections {
target: Qt.application
function onStateChanged() {
if (Qt.application.state !== Qt.ApplicationActive) {
if (!menu.isClosed) {
menu.closeTriggered()
}
}
}
}
implicitWidth: rootButtonContent.implicitWidth implicitWidth: rootButtonContent.implicitWidth
implicitHeight: rootButtonContent.implicitHeight implicitHeight: rootButtonContent.implicitHeight
+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
@@ -19,6 +19,9 @@ Item {
property string buttonText property string buttonText
property string buttonImageSource property string buttonImageSource
property string buttonImageColor: AmneziaStyle.color.midnightBlack
property string buttonBackgroundColor: AmneziaStyle.color.paleGray
property string buttonHoveredColor: AmneziaStyle.color.lightGray
property var clickedFunc property var clickedFunc
property alias textField: textField property alias textField: textField
@@ -67,7 +70,7 @@ Item {
border.width: 1 border.width: 1
Behavior on border.color { Behavior on border.color {
PropertyAnimation { duration: 200 } PropertyAnimation { duration: 100 }
} }
RowLayout { RowLayout {
@@ -194,6 +197,14 @@ Item {
focusPolicy: Qt.NoFocus focusPolicy: Qt.NoFocus
text: root.buttonText text: root.buttonText
leftImageSource: root.buttonImageSource leftImageSource: root.buttonImageSource
leftImageColor: root.buttonImageColor
defaultColor: root.buttonBackgroundColor
hoveredColor: root.buttonHoveredColor
pressedColor: root.buttonHoveredColor
disabledColor: AmneziaStyle.color.transparent
borderWidth: 0
anchors.top: content.top anchors.top: content.top
anchors.bottom: content.bottom anchors.bottom: content.bottom
@@ -201,7 +212,7 @@ Item {
height: content.implicitHeight height: content.implicitHeight
width: content.implicitHeight width: content.implicitHeight
squareLeftSide: true squareLeftSide: false
clickedFunc: function() { clickedFunc: function() {
if (root.clickedFunc && typeof root.clickedFunc === "function") { if (root.clickedFunc && typeof root.clickedFunc === "function") {
@@ -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)
} }
} }
+1 -1
View File
@@ -45,7 +45,7 @@ PageType {
BaseHeaderType { BaseHeaderType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 20 Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
+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
} }
ListViewType { ListViewType {
+26 -11
View File
@@ -20,6 +20,21 @@ import "../Components"
PageType { PageType {
id: root id: root
Connections {
target: Qt.application
function onStateChanged() {
if (Qt.application.state !== Qt.ApplicationActive) {
if (drawer.isOpened) {
drawer.closeTriggered()
}
if (homeSplitTunnelingDrawer.isOpened) {
homeSplitTunnelingDrawer.closeTriggered()
}
}
}
}
Connections { Connections {
objectName: "pageControllerConnections" objectName: "pageControllerConnections"
@@ -68,19 +83,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 +194,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
}
} }
} }
@@ -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) {
@@ -31,6 +31,11 @@ PageType {
} }
} }
SmartScroll {
id: smartScroll
listView: listView
}
ListViewType { ListViewType {
id: listView id: listView
@@ -80,6 +85,13 @@ PageType {
clientMtu = textField.text clientMtu = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(mtuTextField)
}
}
checkEmptyText: true checkEmptyText: true
} }
@@ -97,6 +109,12 @@ PageType {
clientJunkPacketCount = textField.text clientJunkPacketCount = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketCountTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -113,6 +131,12 @@ PageType {
clientJunkPacketMinSize = textField.text clientJunkPacketMinSize = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketMinSizeTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -129,6 +153,12 @@ PageType {
clientJunkPacketMaxSize = textField.text clientJunkPacketMaxSize = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketMaxSizeTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -147,6 +177,12 @@ PageType {
clientSpecialJunk1 = textField.text clientSpecialJunk1 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk1TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -165,6 +201,12 @@ PageType {
clientSpecialJunk2 = textField.text clientSpecialJunk2 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk2TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -183,6 +225,12 @@ PageType {
clientSpecialJunk3 = textField.text clientSpecialJunk3 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk3TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -201,6 +249,12 @@ PageType {
clientSpecialJunk4 = textField.text clientSpecialJunk4 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk4TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -219,6 +273,12 @@ PageType {
clientSpecialJunk5 = textField.text clientSpecialJunk5 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(specialJunk5TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -237,6 +297,12 @@ PageType {
clientControlledJunk1 = textField.text clientControlledJunk1 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(controlledJunk1TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -255,6 +321,12 @@ PageType {
clientControlledJunk2 = textField.text clientControlledJunk2 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(controlledJunk2TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -273,6 +345,12 @@ PageType {
clientControlledJunk3 = textField.text clientControlledJunk3 = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(controlledJunk3TextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -290,6 +368,12 @@ PageType {
clientSpecialHandshakeTimeout = textField.text clientSpecialHandshakeTimeout = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(iTimeTextField)
}
}
} }
Header2TextType { Header2TextType {
@@ -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) {
@@ -34,6 +34,11 @@ PageType {
} }
} }
SmartScroll {
id: smartScroll
listView: listView
}
ListViewType { ListViewType {
id: listView id: listView
@@ -81,6 +86,12 @@ PageType {
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(vpnAddressSubnetTextField)
}
}
checkEmptyText: true checkEmptyText: true
} }
@@ -104,6 +115,12 @@ PageType {
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(portTextField)
}
}
checkEmptyText: true checkEmptyText: true
} }
@@ -121,6 +138,12 @@ PageType {
serverJunkPacketCount = textField.text serverJunkPacketCount = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketCountTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -137,6 +160,12 @@ PageType {
serverJunkPacketMinSize = textField.text serverJunkPacketMinSize = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketMinSizeTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -153,6 +182,12 @@ PageType {
serverJunkPacketMaxSize = textField.text serverJunkPacketMaxSize = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(junkPacketMaxSizeTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -169,6 +204,12 @@ PageType {
serverInitPacketJunkSize = textField.text serverInitPacketJunkSize = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(initPacketJunkSizeTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -185,6 +226,12 @@ PageType {
serverResponsePacketJunkSize = textField.text serverResponsePacketJunkSize = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(responsePacketJunkSizeTextField)
}
}
} }
// AwgTextField { // AwgTextField {
@@ -233,6 +280,12 @@ PageType {
serverInitPacketMagicHeader = textField.text serverInitPacketMagicHeader = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(initPacketMagicHeaderTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -249,6 +302,12 @@ PageType {
serverResponsePacketMagicHeader = textField.text serverResponsePacketMagicHeader = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(responsePacketMagicHeaderTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -265,6 +324,12 @@ PageType {
serverUnderloadPacketMagicHeader = textField.text serverUnderloadPacketMagicHeader = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(underloadPacketMagicHeaderTextField)
}
}
} }
AwgTextField { AwgTextField {
@@ -281,6 +346,12 @@ PageType {
serverTransportPacketMagicHeader = textField.text serverTransportPacketMagicHeader = textField.text
} }
} }
textField.onActiveFocusChanged: {
if (textField.activeFocus) {
smartScroll.scrollToItem(transportPacketMagicHeaderTextField)
}
}
} }
BasicButtonType { BasicButtonType {
@@ -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) {
+19 -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
@@ -136,8 +149,11 @@ PageType {
readonly property string leftImagePath: NewsModel.hasUnread ? "qrc:/images/controls/news-unread.svg" : "qrc:/images/controls/news.svg" readonly property string leftImagePath: NewsModel.hasUnread ? "qrc:/images/controls/news-unread.svg" : "qrc:/images/controls/news.svg"
property bool isVisible: ServersModel.hasServersFromGatewayApi property bool isVisible: ServersModel.hasServersFromGatewayApi
readonly property var clickedHandler: function() { readonly property var clickedHandler: function() {
if (!ServersModel.hasServersFromGatewayApi) return; if (!ServersModel.hasServersFromGatewayApi) {
ApiNewsController.fetchNews(); return;
}
PageController.showBusyIndicator(true)
ApiNewsController.fetchNews()
PageController.goToPage(PageEnum.PageSettingsNewsNotifications) PageController.goToPage(PageEnum.PageSettingsNewsNotifications)
} }
} }
+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) {
@@ -66,7 +66,7 @@ PageType {
id: backButton id: backButton
objectName: "backButton" objectName: "backButton"
Layout.topMargin: 20 Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
} }
HeaderTypeWithButton { HeaderTypeWithButton {
@@ -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 {
@@ -61,7 +61,7 @@ PageType {
width: root.width width: root.width
BackButtonType { BackButtonType {
Layout.topMargin: 20 Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
} }
Label { Label {
@@ -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
@@ -165,9 +165,11 @@ PageType {
ScrollBar.vertical: ScrollBarType { policy: ScrollBar.AlwaysOn } ScrollBar.vertical: ScrollBarType { policy: ScrollBar.AlwaysOn }
anchors.top: header.bottom anchors.top: header.bottom
anchors.bottom: addAppButton.top anchors.bottom: parent.bottom
anchors.bottomMargin: addAppButton.implicitHeight + 48 + SettingsController.safeAreaBottomMargin + (searchField.textField.activeFocus ? 0 : SettingsController.imeHeight)
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
clip: true
model: SortFilterProxyModel { model: SortFilterProxyModel {
id: proxyAppSplitTunnelingModel id: proxyAppSplitTunnelingModel
@@ -215,50 +217,54 @@ PageType {
} }
Rectangle { Rectangle {
anchors.fill: addAppButton
anchors.bottomMargin: -24
color: AmneziaStyle.color.midnightBlack
opacity: 0.8
}
RowLayout {
id: addAppButton
enabled: root.pageEnabled
anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 24 anchors.bottom: parent.bottom
anchors.rightMargin: 16
anchors.leftMargin: 16
anchors.bottomMargin: 24
TextFieldWithHeaderType { height: addAppButton.implicitHeight + 48 + SettingsController.safeAreaBottomMargin
id: searchField
Layout.fillWidth: true color: AmneziaStyle.color.midnightBlack
opacity: 0.8
textField.placeholderText: qsTr("application name") RowLayout {
buttonImageSource: "qrc:/images/controls/plus.svg" id: addAppButton
rightButtonClickedOnEnter: true enabled: root.pageEnabled
clickedFunc: function() { anchors.bottom: parent.bottom
searchField.focus = false anchors.left: parent.left
PageController.showBusyIndicator(true) anchors.right: parent.right
anchors.topMargin: 24
anchors.rightMargin: 16
anchors.leftMargin: 16
anchors.bottomMargin: 24 + SettingsController.safeAreaBottomMargin
if (Qt.platform.os === "windows") { TextFieldWithHeaderType {
var fileName = SystemController.getFileName(qsTr("Open executable file"), id: searchField
qsTr("Executable files (*.*)"))
if (fileName !== "") { Layout.fillWidth: true
AppSplitTunnelingController.addApp(fileName)
textField.placeholderText: qsTr("application name")
buttonImageSource: "qrc:/images/controls/plus.svg"
rightButtonClickedOnEnter: true
clickedFunc: function() {
searchField.focus = false
PageController.showBusyIndicator(true)
if (Qt.platform.os === "windows") {
var fileName = SystemController.getFileName(qsTr("Open executable file"),
qsTr("Executable files (*.*)"))
if (fileName !== "") {
AppSplitTunnelingController.addApp(fileName)
}
} else if (Qt.platform.os === "android"){
installedAppDrawer.openTriggered()
} }
} else if (Qt.platform.os === "android"){
installedAppDrawer.openTriggered()
}
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
}
} }
} }
} }
@@ -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) {
@@ -75,6 +75,29 @@ PageType {
DividerType {} DividerType {}
SwitcherType {
id: useSystemDnsSwitch
visible: ServersModel.processedServerIsPremium
Layout.fillWidth: true
Layout.margins: 16
text: qsTr("Use system DNS")
descriptionText: qsTr("Use system DNS servers")
checked: SettingsController.useSystemDnsAddressEnabled
onToggled: function() {
if (checked !== SettingsController.useSystemDnsAddressEnabled) {
SettingsController.setUseSystemDnsAddress(checked)
}
}
}
DividerType {
visible: ServersModel.processedServerIsPremium
}
LabelWithButtonType { LabelWithButtonType {
id: dnsServersButton id: dnsServersButton
+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
@@ -168,11 +168,13 @@ PageType {
anchors.top: header.bottom anchors.top: header.bottom
anchors.topMargin: 16 anchors.topMargin: 16
anchors.bottom: addSiteButton.top anchors.bottom: parent.bottom
anchors.bottomMargin: addSiteButton.implicitHeight + 48 + (searchField.textField.activeFocus ? 0 : SettingsController.imeHeight)
width: parent.width width: parent.width
enabled: root.pageEnabled enabled: root.pageEnabled
clip: true
model: SortFilterProxyModel { model: SortFilterProxyModel {
id: proxySitesModel id: proxySitesModel
@@ -231,56 +233,60 @@ PageType {
} }
Rectangle { Rectangle {
anchors.fill: addSiteButton
anchors.bottomMargin: -24
color: AmneziaStyle.color.midnightBlack
opacity: 0.8
}
RowLayout {
id: addSiteButton
enabled: root.pageEnabled
anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 24 anchors.bottom: parent.bottom
anchors.rightMargin: 16
anchors.leftMargin: 16
anchors.bottomMargin: 24
TextFieldWithHeaderType { height: addSiteButton.implicitHeight + 48
id: searchField
Layout.fillWidth: true color: AmneziaStyle.color.midnightBlack
rightButtonClickedOnEnter: true opacity: 0.8
textField.placeholderText: qsTr("website or IP") RowLayout {
buttonImageSource: "qrc:/images/controls/plus.svg" id: addSiteButton
clickedFunc: function() { enabled: root.pageEnabled
PageController.showBusyIndicator(true)
SitesController.addSite(textField.text)
textField.text = ""
PageController.showBusyIndicator(false)
}
}
ImageButtonType { anchors.bottom: parent.bottom
id: addSiteButtonImage anchors.left: parent.left
implicitWidth: 56 anchors.right: parent.right
implicitHeight: 56 anchors.topMargin: 24
anchors.rightMargin: 16
anchors.leftMargin: 16
anchors.bottomMargin: 24
image: "qrc:/images/controls/more-vertical.svg" TextFieldWithHeaderType {
imageColor: AmneziaStyle.color.paleGray id: searchField
onClicked: function () { Layout.fillWidth: true
moreActionsDrawer.openTriggered() rightButtonClickedOnEnter: true
textField.placeholderText: qsTr("website or IP")
buttonImageSource: "qrc:/images/controls/plus.svg"
clickedFunc: function() {
PageController.showBusyIndicator(true)
SitesController.addSite(textField.text)
textField.text = ""
PageController.showBusyIndicator(false)
}
} }
Keys.onReturnPressed: addSiteButtonImage.clicked() ImageButtonType {
Keys.onEnterPressed: addSiteButtonImage.clicked() id: addSiteButtonImage
implicitWidth: 56
implicitHeight: 56
image: "qrc:/images/controls/more-vertical.svg"
imageColor: AmneziaStyle.color.paleGray
onClicked: function () {
moreActionsDrawer.openTriggered()
}
Keys.onReturnPressed: addSiteButtonImage.clicked()
Keys.onEnterPressed: addSiteButtonImage.clicked()
}
} }
} }
@@ -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) {
@@ -103,7 +103,7 @@ PageType {
BaseHeaderType { BaseHeaderType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 20 Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
@@ -147,6 +147,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24
Layout.bottomMargin: 24 + SettingsController.safeAreaBottomMargin
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
@@ -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) {
@@ -20,7 +20,7 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 20 anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
} }
ParagraphTextType { ParagraphTextType {
@@ -25,7 +25,7 @@ PageType {
source: "qrc:/images/amneziaBigLogo.png" source: "qrc:/images/amneziaBigLogo.png"
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.topMargin: 32 Layout.topMargin: 32 + SettingsController.safeAreaTopMargin
Layout.preferredWidth: 360 Layout.preferredWidth: 360
Layout.preferredHeight: 287 Layout.preferredHeight: 287
} }
@@ -33,7 +33,7 @@ PageType {
BasicButtonType { BasicButtonType {
id: startButton id: startButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.bottomMargin: 48 Layout.bottomMargin: 48 + SettingsController.safeAreaBottomMargin
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
@@ -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) {
@@ -35,6 +35,7 @@ PageType {
target: ImportController target: ImportController
function onImportErrorOccurred(error, goToPageHome) { function onImportErrorOccurred(error, goToPageHome) {
PageController.showBusyIndicator(false)
if (goToPageHome) { if (goToPageHome) {
PageController.goToStartPage() PageController.goToStartPage()
} else { } else {
@@ -43,6 +44,7 @@ PageType {
} }
function onImportFinished() { function onImportFinished() {
PageController.showBusyIndicator(false)
if (!ConnectionController.isConnected) { if (!ConnectionController.isConnected) {
ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1);
ServersModel.processedIndex = ServersModel.defaultIndex ServersModel.processedIndex = ServersModel.defaultIndex
@@ -216,6 +218,7 @@ PageType {
if (cloakingCheckBoxItem.checked) { if (cloakingCheckBoxItem.checked) {
ImportController.processNativeWireGuardConfig() ImportController.processNativeWireGuardConfig()
} }
PageController.showBusyIndicator(true)
ImportController.importConfig() ImportController.importConfig()
} }
} }
+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) {
+4 -1
View File
@@ -305,8 +305,11 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
// Also adjust TabBar position when keyboard appears (Android 14+ workaround)
anchors.bottomMargin: SettingsController.imeHeight
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 + SettingsController.imeHeight
expandedStateContent: ColumnLayout { expandedStateContent: ColumnLayout {
anchors.top: parent.top anchors.top: parent.top