mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ee61a040b | |||
| 741b5cc0f9 | |||
| aaf0e070dc | |||
| e0e126eda8 | |||
| 236daf6b3b | |||
| f1481b1b1f |
@@ -469,8 +469,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
ANDROID_BUILD_PLATFORM: android-34
|
ANDROID_BUILD_PLATFORM: android-36
|
||||||
QT_VERSION: 6.7.3
|
QT_VERSION: 6.9.3
|
||||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
set(AMNEZIAVPN_VERSION 4.8.11.0)
|
set(AMNEZIAVPN_VERSION 4.8.11.1)
|
||||||
|
|
||||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 2095)
|
set(APP_ANDROID_VERSION_CODE 2096)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:windowSoftInputMode="stateUnchanged|adjustResize"
|
android:windowSoftInputMode="stateUnchanged|adjustResize"
|
||||||
|
android:enableOnBackInvokedCallback="false"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
<item name="android:colorBackground">@color/black</item>
|
<item name="android:colorBackground">@color/black</item>
|
||||||
<item name="android:windowActionBar">false</item>
|
<item name="android:windowActionBar">false</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
|
<item name="android:enforceNavigationBarContrast">false</item>
|
||||||
|
<item name="android:enforceStatusBarContrast">false</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="Translucent" parent="NoActionBar">
|
<style name="Translucent" parent="NoActionBar">
|
||||||
<item name="android:windowBackground">@android:color/transparent</item>
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import android.widget.Toast
|
|||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlin.LazyThreadSafetyMode.NONE
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
@@ -170,10 +171,9 @@ class AmneziaActivity : QtActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.d(TAG, "Create Amnezia activity")
|
Log.d(TAG, "Create Amnezia activity")
|
||||||
loadLibs()
|
loadLibs()
|
||||||
window.apply {
|
|
||||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
// Configure window for edge-to-edge display
|
||||||
statusBarColor = getColor(R.color.black)
|
configureWindowForEdgeToEdge()
|
||||||
}
|
|
||||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||||
val proto = mainScope.async(Dispatchers.IO) {
|
val proto = mainScope.async(Dispatchers.IO) {
|
||||||
VpnStateStore.getVpnState().vpnProto
|
VpnStateStore.getVpnState().vpnProto
|
||||||
@@ -265,6 +265,49 @@ class AmneziaActivity : QtActivity() {
|
|||||||
super.onStop()
|
super.onStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
|
window.decorView.apply {
|
||||||
|
invalidate()
|
||||||
|
|
||||||
|
postDelayed({
|
||||||
|
sendTouch(1f, 1f)
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
postDelayed({
|
||||||
|
sendTouch(2f, 2f)
|
||||||
|
}, 200)
|
||||||
|
|
||||||
|
postDelayed({
|
||||||
|
requestLayout()
|
||||||
|
invalidate()
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun configureWindowForEdgeToEdge() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
|
window.apply {
|
||||||
|
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||||
|
addFlags(LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
||||||
|
statusBarColor = android.graphics.Color.TRANSPARENT
|
||||||
|
navigationBarColor = android.graphics.Color.TRANSPARENT
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowInsetsControllerCompat(window, window.decorView).apply {
|
||||||
|
isAppearanceLightStatusBars = false
|
||||||
|
isAppearanceLightNavigationBars = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window.apply {
|
||||||
|
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||||
|
statusBarColor = getColor(R.color.black)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
Log.d(TAG, "Destroy Amnezia activity")
|
Log.d(TAG, "Destroy Amnezia activity")
|
||||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||||
@@ -666,6 +709,43 @@ class AmneziaActivity : QtActivity() {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun isEdgeToEdgeEnabled(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun getStatusBarHeight(): Int {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
|
||||||
|
|
||||||
|
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
|
||||||
|
val heightPx = if (resourceId > 0) {
|
||||||
|
resources.getDimensionPixelSize(resourceId)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert physical pixels to device-independent pixels for QML
|
||||||
|
val density = resources.displayMetrics.density
|
||||||
|
val heightDp = (heightPx / density).toInt()
|
||||||
|
return heightDp
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun getNavigationBarHeight(): Int {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
|
||||||
|
|
||||||
|
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
|
||||||
|
val heightPx = if (resourceId > 0) {
|
||||||
|
resources.getDimensionPixelSize(resourceId)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert physical pixels to device-independent pixels for QML
|
||||||
|
val density = resources.displayMetrics.density
|
||||||
|
val heightDp = (heightPx / density).toInt()
|
||||||
|
return heightDp
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun startQrCodeReader() {
|
fun startQrCodeReader() {
|
||||||
Log.v(TAG, "Start camera")
|
Log.v(TAG, "Start camera")
|
||||||
|
|||||||
@@ -38,15 +38,15 @@ object AppListProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
|
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo? = pi.applicationInfo) : Comparable<App> {
|
||||||
val name: String?
|
val name: String?
|
||||||
val packageName: String = pi.packageName
|
val packageName: String = pi.packageName
|
||||||
val icon: Boolean = ai.icon != 0
|
val icon: Boolean = (ai?.icon ?: 0) != 0
|
||||||
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
|
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val name = ai.loadLabel(pm).toString()
|
val name = ai?.loadLabel(pm)?.toString()
|
||||||
this.name = if (name != packageName) name else null
|
this.name = name?.takeIf { it != packageName }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun compareTo(other: App): Int {
|
override fun compareTo(other: App): Int {
|
||||||
|
|||||||
@@ -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
@@ -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}")
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
#include <QPromise>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include "QBlockCipher.h"
|
#include "QBlockCipher.h"
|
||||||
@@ -50,24 +51,25 @@ GatewayController::GatewayController(const QString &gatewayEndpoint, const bool
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
GatewayController::EncryptedRequestData GatewayController::prepareRequest(const QString &endpoint, const QJsonObject &apiPayload)
|
||||||
{
|
{
|
||||||
|
EncryptedRequestData encRequestData;
|
||||||
|
encRequestData.errorCode = ErrorCode::NoError;
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
IosController::Instance()->requestInetAccess();
|
IosController::Instance()->requestInetAccess();
|
||||||
QThread::msleep(10);
|
QThread::msleep(10);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QNetworkRequest request;
|
encRequestData.request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
encRequestData.request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
encRequestData.request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||||
request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
encRequestData.request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
||||||
|
|
||||||
request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
|
||||||
|
|
||||||
// bypass killSwitch exceptions for API-gateway
|
// bypass killSwitch exceptions for API-gateway
|
||||||
#ifdef AMNEZIA_DESKTOP
|
#ifdef AMNEZIA_DESKTOP
|
||||||
if (m_isStrictKillSwitchEnabled) {
|
if (m_isStrictKillSwitchEnabled) {
|
||||||
QString host = QUrl(request.url()).host();
|
QString host = QUrl(encRequestData.request.url()).host();
|
||||||
QString ip = NetworkUtilities::getIPAddress(host);
|
QString ip = NetworkUtilities::getIPAddress(host);
|
||||||
if (!ip.isEmpty()) {
|
if (!ip.isEmpty()) {
|
||||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||||
@@ -76,14 +78,14 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
QByteArray key = blockCipher.generatePrivateSalt(32);
|
encRequestData.key = blockCipher.generatePrivateSalt(32);
|
||||||
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
encRequestData.iv = blockCipher.generatePrivateSalt(32);
|
||||||
QByteArray salt = blockCipher.generatePrivateSalt(8);
|
encRequestData.salt = blockCipher.generatePrivateSalt(8);
|
||||||
|
|
||||||
QJsonObject keyPayload;
|
QJsonObject keyPayload;
|
||||||
keyPayload[configKey::aesKey] = QString(key.toBase64());
|
keyPayload[configKey::aesKey] = QString(encRequestData.key.toBase64());
|
||||||
keyPayload[configKey::aesIv] = QString(iv.toBase64());
|
keyPayload[configKey::aesIv] = QString(encRequestData.iv.toBase64());
|
||||||
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
|
keyPayload[configKey::aesSalt] = QString(encRequestData.salt.toBase64());
|
||||||
|
|
||||||
QByteArray encryptedKeyPayload;
|
QByteArray encryptedKeyPayload;
|
||||||
QByteArray encryptedApiPayload;
|
QByteArray encryptedApiPayload;
|
||||||
@@ -98,24 +100,37 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
} catch (...) {
|
} catch (...) {
|
||||||
Utils::logException();
|
Utils::logException();
|
||||||
qCritical() << "error loading public key from environment variables";
|
qCritical() << "error loading public key from environment variables";
|
||||||
return ErrorCode::ApiMissingAgwPublicKey;
|
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
|
||||||
|
return encRequestData;
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||||
EVP_PKEY_free(publicKey);
|
EVP_PKEY_free(publicKey);
|
||||||
|
|
||||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv, "", encRequestData.salt);
|
||||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
} catch (...) {
|
||||||
Utils::logException();
|
Utils::logException();
|
||||||
qCritical() << "error when encrypting the request body";
|
qCritical() << "error when encrypting the request body";
|
||||||
return ErrorCode::ApiConfigDecryptionError;
|
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
|
||||||
|
return encRequestData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject requestBody;
|
QJsonObject requestBody;
|
||||||
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||||
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||||
|
|
||||||
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
encRequestData.requestBody = QJsonDocument(requestBody).toJson();
|
||||||
|
return encRequestData;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
||||||
|
{
|
||||||
|
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
||||||
|
if (encRequestData.errorCode != ErrorCode::NoError) {
|
||||||
|
return encRequestData.errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||||
|
|
||||||
QEventLoop wait;
|
QEventLoop wait;
|
||||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
@@ -131,19 +146,19 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
|
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, encryptedResponseBody, true, key, iv, salt)) {
|
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
|
||||||
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
|
||||||
request.setUrl(url);
|
encRequestData.request.setUrl(url);
|
||||||
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &key, &iv,
|
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &encRequestData,
|
||||||
&salt, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
|
this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
|
||||||
encryptedResponseBody = reply->readAll();
|
encryptedResponseBody = reply->readAll();
|
||||||
replyErrorString = reply->errorString();
|
replyErrorString = reply->errorString();
|
||||||
replyError = reply->error();
|
replyError = reply->error();
|
||||||
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
if (!sslErrors.isEmpty() || shouldBypassProxy(replyError, encryptedResponseBody, true, key, iv, salt)) {
|
if (!sslErrors.isEmpty() || shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
|
||||||
sslErrors = nestedSslErrors;
|
sslErrors = nestedSslErrors;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -161,7 +176,8 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
|
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||||
Utils::logException();
|
Utils::logException();
|
||||||
@@ -170,6 +186,57 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
|
||||||
|
{
|
||||||
|
auto promise = QSharedPointer<QPromise<QPair<ErrorCode, QByteArray>>>::create();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
||||||
|
if (encRequestData.errorCode != ErrorCode::NoError) {
|
||||||
|
promise->addResult(qMakePair(encRequestData.errorCode, QByteArray()));
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||||
|
|
||||||
|
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) {
|
||||||
|
*sslErrors = errors;
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, [=]() {
|
||||||
|
QByteArray encryptedResponseBody = reply->readAll();
|
||||||
|
QString replyErrorString = reply->errorString();
|
||||||
|
auto replyError = reply->error();
|
||||||
|
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
auto errorCode = apiUtils::checkNetworkReplyErrors(*sslErrors, replyErrorString, replyError, httpStatusCode, encryptedResponseBody);
|
||||||
|
if (errorCode) {
|
||||||
|
promise->addResult(qMakePair(errorCode, QByteArray()));
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
|
try {
|
||||||
|
QByteArray responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
|
||||||
|
promise->addResult(qMakePair(ErrorCode::NoError, responseBody));
|
||||||
|
promise->finish();
|
||||||
|
} catch (...) {
|
||||||
|
Utils::logException();
|
||||||
|
qCritical() << "error when decrypting the request body";
|
||||||
|
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
|
||||||
|
promise->finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
|
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
|
||||||
{
|
{
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
#ifndef GATEWAYCONTROLLER_H
|
#ifndef GATEWAYCONTROLLER_H
|
||||||
#define GATEWAYCONTROLLER_H
|
#define GATEWAYCONTROLLER_H
|
||||||
|
|
||||||
|
#include <QFuture>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QPair>
|
||||||
|
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
|
|
||||||
@@ -19,8 +21,20 @@ public:
|
|||||||
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
||||||
|
|
||||||
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||||
|
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct EncryptedRequestData {
|
||||||
|
QNetworkRequest request;
|
||||||
|
QByteArray requestBody;
|
||||||
|
QByteArray key;
|
||||||
|
QByteArray iv;
|
||||||
|
QByteArray salt;
|
||||||
|
amnezia::ErrorCode errorCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
|
||||||
|
|
||||||
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
|
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
|
||||||
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption,
|
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption,
|
||||||
const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = "");
|
const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = "");
|
||||||
|
|||||||
@@ -202,6 +202,21 @@ bool AndroidController::isOnTv()
|
|||||||
return callActivityMethod<jboolean>("isOnTv", "()Z");
|
return callActivityMethod<jboolean>("isOnTv", "()Z");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AndroidController::isEdgeToEdgeEnabled()
|
||||||
|
{
|
||||||
|
return callActivityMethod<jboolean>("isEdgeToEdgeEnabled", "()Z");
|
||||||
|
}
|
||||||
|
|
||||||
|
int AndroidController::getStatusBarHeight()
|
||||||
|
{
|
||||||
|
return callActivityMethod<jint>("getStatusBarHeight", "()I");
|
||||||
|
}
|
||||||
|
|
||||||
|
int AndroidController::getNavigationBarHeight()
|
||||||
|
{
|
||||||
|
return callActivityMethod<jint>("getNavigationBarHeight", "()I");
|
||||||
|
}
|
||||||
|
|
||||||
void AndroidController::startQrReaderActivity()
|
void AndroidController::startQrReaderActivity()
|
||||||
{
|
{
|
||||||
callActivityMethod("startQrCodeReader", "()V");
|
callActivityMethod("startQrCodeReader", "()V");
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ public:
|
|||||||
QString getFileName(const QString &uri);
|
QString getFileName(const QString &uri);
|
||||||
bool isCameraPresent();
|
bool isCameraPresent();
|
||||||
bool isOnTv();
|
bool isOnTv();
|
||||||
|
bool isEdgeToEdgeEnabled();
|
||||||
|
int getStatusBarHeight();
|
||||||
|
int getNavigationBarHeight();
|
||||||
void startQrReaderActivity();
|
void startQrReaderActivity();
|
||||||
void setSaveLogs(bool enabled);
|
void setSaveLogs(bool enabled);
|
||||||
void exportLogsFile(const QString &fileName);
|
void exportLogsFile(const QString &fileName);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 & 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 & 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,
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ void ApiNewsController::fetchNews()
|
|||||||
}
|
}
|
||||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
||||||
m_settings->isStrictKillSwitchEnabled());
|
m_settings->isStrictKillSwitchEnabled());
|
||||||
QByteArray responseBody;
|
|
||||||
QJsonObject payload;
|
QJsonObject payload;
|
||||||
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());
|
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());
|
||||||
|
|
||||||
@@ -44,22 +43,26 @@ void ApiNewsController::fetchNews()
|
|||||||
payload.insert(configKey::serviceType, stacksJson.value(configKey::serviceType));
|
payload.insert(configKey::serviceType, stacksJson.value(configKey::serviceType));
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode errorCode = gatewayController.post(QString("%1v1/news"), payload, responseBody);
|
auto future = gatewayController.postAsync(QString("%1v1/news"), payload);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
future.then(this, [this](QPair<ErrorCode, QByteArray> result) {
|
||||||
emit errorOccurred(errorCode);
|
auto [errorCode, responseBody] = result;
|
||||||
return;
|
if (errorCode != ErrorCode::NoError) {
|
||||||
}
|
emit errorOccurred(errorCode);
|
||||||
|
return;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
|
|
||||||
QJsonArray newsArray;
|
|
||||||
if (doc.isArray()) {
|
|
||||||
newsArray = doc.array();
|
|
||||||
} else if (doc.isObject()) {
|
|
||||||
QJsonObject obj = doc.object();
|
|
||||||
if (obj.value("news").isArray()) {
|
|
||||||
newsArray = obj.value("news").toArray();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
m_newsModel->updateModel(newsArray);
|
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
|
||||||
|
QJsonArray newsArray;
|
||||||
|
if (doc.isArray()) {
|
||||||
|
newsArray = doc.array();
|
||||||
|
} else if (doc.isObject()) {
|
||||||
|
QJsonObject obj = doc.object();
|
||||||
|
if (obj.value("news").isArray()) {
|
||||||
|
newsArray = obj.value("news").toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_newsModel->updateModel(newsArray);
|
||||||
|
emit fetchNewsFinished();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void errorOccurred(ErrorCode errorCode);
|
void errorOccurred(ErrorCode errorCode);
|
||||||
|
void fetchNewsFinished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSharedPointer<NewsModel> m_newsModel;
|
QSharedPointer<NewsModel> m_newsModel;
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ bool ApiSettingsController::getAccountInfo(bool reload)
|
|||||||
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
|
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
|
||||||
apiPayload[configKey::authData] = authData;
|
apiPayload[configKey::authData] = authData;
|
||||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||||
|
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
|
||||||
|
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
|
|
||||||
|
|||||||
@@ -431,6 +431,63 @@ bool SettingsController::isOnTv()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SettingsController::isEdgeToEdgeEnabled()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
return AndroidController::instance()->isEdgeToEdgeEnabled();
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int SettingsController::getStatusBarHeight()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
if (m_cachedStatusBarHeight < 0) {
|
||||||
|
m_cachedStatusBarHeight = AndroidController::instance()->getStatusBarHeight();
|
||||||
|
}
|
||||||
|
return m_cachedStatusBarHeight;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int SettingsController::getNavigationBarHeight()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
if (m_cachedNavigationBarHeight < 0) {
|
||||||
|
m_cachedNavigationBarHeight = AndroidController::instance()->getNavigationBarHeight();
|
||||||
|
}
|
||||||
|
return m_cachedNavigationBarHeight;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int SettingsController::getSafeAreaTopMargin()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
if (isEdgeToEdgeEnabled()) {
|
||||||
|
int height = getStatusBarHeight();
|
||||||
|
int result = height > 0 ? height : 40; // fallback to 40 if system returns 0
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SettingsController::getSafeAreaBottomMargin()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
if (isEdgeToEdgeEnabled()) {
|
||||||
|
int height = getNavigationBarHeight();
|
||||||
|
int result = height > 0 ? height : 56; // fallback to 56 if system returns 0
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool SettingsController::isHomeAdLabelVisible()
|
bool SettingsController::isHomeAdLabelVisible()
|
||||||
{
|
{
|
||||||
return m_settings->isHomeAdLabelVisible();
|
return m_settings->isHomeAdLabelVisible();
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ public:
|
|||||||
|
|
||||||
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
|
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
|
||||||
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
|
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
|
||||||
|
Q_PROPERTY(int safeAreaTopMargin READ getSafeAreaTopMargin CONSTANT)
|
||||||
|
Q_PROPERTY(int safeAreaBottomMargin READ getSafeAreaBottomMargin CONSTANT)
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void toggleAmneziaDns(bool enable);
|
void toggleAmneziaDns(bool enable);
|
||||||
@@ -96,6 +98,11 @@ public slots:
|
|||||||
void toggleDevGatewayEnv(bool enabled);
|
void toggleDevGatewayEnv(bool enabled);
|
||||||
|
|
||||||
bool isOnTv();
|
bool isOnTv();
|
||||||
|
bool isEdgeToEdgeEnabled();
|
||||||
|
int getStatusBarHeight();
|
||||||
|
int getNavigationBarHeight();
|
||||||
|
int getSafeAreaTopMargin();
|
||||||
|
int getSafeAreaBottomMargin();
|
||||||
|
|
||||||
bool isHomeAdLabelVisible();
|
bool isHomeAdLabelVisible();
|
||||||
void disableHomeAdLabel();
|
void disableHomeAdLabel();
|
||||||
@@ -134,6 +141,9 @@ private:
|
|||||||
QSharedPointer<LanguageModel> m_languageModel;
|
QSharedPointer<LanguageModel> m_languageModel;
|
||||||
QSharedPointer<SitesModel> m_sitesModel;
|
QSharedPointer<SitesModel> m_sitesModel;
|
||||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||||
|
|
||||||
|
mutable int m_cachedStatusBarHeight = -1;
|
||||||
|
mutable int m_cachedNavigationBarHeight = -1;
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
|
||||||
QString m_appVersion;
|
QString m_appVersion;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -158,6 +158,18 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
|
|||||||
QString primaryDns = server.value(config_key::dns1).toString();
|
QString primaryDns = server.value(config_key::dns1).toString();
|
||||||
return primaryDns == protocols::dns::amneziaDnsIp;
|
return primaryDns == protocols::dns::amneziaDnsIp;
|
||||||
}
|
}
|
||||||
|
case IsAdVisibleRole:{
|
||||||
|
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::isAdVisible).toBool(false);
|
||||||
|
}
|
||||||
|
case AdHeaderRole: {
|
||||||
|
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adHeader).toString();
|
||||||
|
}
|
||||||
|
case AdDescriptionRole: {
|
||||||
|
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adDescription).toString();
|
||||||
|
}
|
||||||
|
case AdEndpointRole: {
|
||||||
|
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adEndpoint).toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
@@ -403,6 +415,12 @@ QHash<int, QByteArray> ServersModel::roleNames() const
|
|||||||
roles[IsCountrySelectionAvailableRole] = "isCountrySelectionAvailable";
|
roles[IsCountrySelectionAvailableRole] = "isCountrySelectionAvailable";
|
||||||
roles[ApiAvailableCountriesRole] = "apiAvailableCountries";
|
roles[ApiAvailableCountriesRole] = "apiAvailableCountries";
|
||||||
roles[ApiServerCountryCodeRole] = "apiServerCountryCode";
|
roles[ApiServerCountryCodeRole] = "apiServerCountryCode";
|
||||||
|
|
||||||
|
roles[IsAdVisibleRole] = "isAdVisible";
|
||||||
|
roles[AdHeaderRole] = "adHeader";
|
||||||
|
roles[AdDescriptionRole] = "adDescription";
|
||||||
|
roles[AdEndpointRole] = "adEndpoint";
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -784,22 +802,22 @@ void ServersModel::recomputeGatewayStacks()
|
|||||||
const bool wasEmpty = m_gatewayStacks.isEmpty();
|
const bool wasEmpty = m_gatewayStacks.isEmpty();
|
||||||
GatewayStacks computed;
|
GatewayStacks computed;
|
||||||
bool hasNewTags = false;
|
bool hasNewTags = false;
|
||||||
|
|
||||||
for (int i = 0; i < m_servers.count(); ++i) {
|
for (int i = 0; i < m_servers.count(); ++i) {
|
||||||
if (data(i, IsServerFromGatewayApiRole).toBool()) {
|
if (data(i, IsServerFromGatewayApiRole).toBool()) {
|
||||||
const QJsonObject server = m_servers.at(i).toObject();
|
const QJsonObject server = m_servers.at(i).toObject();
|
||||||
const QJsonObject apiConfig = server.value(configKey::apiConfig).toObject();
|
const QJsonObject apiConfig = server.value(configKey::apiConfig).toObject();
|
||||||
|
|
||||||
const QString userCountryCode = apiConfig.value(configKey::userCountryCode).toString();
|
const QString userCountryCode = apiConfig.value(configKey::userCountryCode).toString();
|
||||||
const QString serviceType = apiConfig.value(configKey::serviceType).toString();
|
const QString serviceType = apiConfig.value(configKey::serviceType).toString();
|
||||||
|
|
||||||
if (!userCountryCode.isEmpty()) {
|
if (!userCountryCode.isEmpty()) {
|
||||||
if (!m_gatewayStacks.userCountryCodes.contains(userCountryCode)) {
|
if (!m_gatewayStacks.userCountryCodes.contains(userCountryCode)) {
|
||||||
hasNewTags = true;
|
hasNewTags = true;
|
||||||
}
|
}
|
||||||
computed.userCountryCodes.insert(userCountryCode);
|
computed.userCountryCodes.insert(userCountryCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!serviceType.isEmpty()) {
|
if (!serviceType.isEmpty()) {
|
||||||
if (!m_gatewayStacks.serviceTypes.contains(serviceType)) {
|
if (!m_gatewayStacks.serviceTypes.contains(serviceType)) {
|
||||||
hasNewTags = true;
|
hasNewTags = true;
|
||||||
@@ -808,12 +826,12 @@ void ServersModel::recomputeGatewayStacks()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_gatewayStacks = std::move(computed);
|
m_gatewayStacks = std::move(computed);
|
||||||
if (hasNewTags) {
|
if (hasNewTags) {
|
||||||
emit gatewayStacksExpanded();
|
emit gatewayStacksExpanded();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wasEmpty != m_gatewayStacks.isEmpty()) {
|
if (wasEmpty != m_gatewayStacks.isEmpty()) {
|
||||||
emit hasServersFromGatewayApiChanged();
|
emit hasServersFromGatewayApiChanged();
|
||||||
}
|
}
|
||||||
@@ -885,3 +903,18 @@ bool ServersModel::processedServerIsPremium() const
|
|||||||
{
|
{
|
||||||
return apiUtils::isPremiumServer(getServerConfig(m_processedServerIndex));
|
return apiUtils::isPremiumServer(getServerConfig(m_processedServerIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ServersModel::isAdVisible()
|
||||||
|
{
|
||||||
|
return data(m_defaultServerIndex, IsAdVisibleRole).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ServersModel::adHeader()
|
||||||
|
{
|
||||||
|
return data(m_defaultServerIndex, AdHeaderRole).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ServersModel::adDescription()
|
||||||
|
{
|
||||||
|
return data(m_defaultServerIndex, AdDescriptionRole).toString();
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Popup {
|
|||||||
|
|
||||||
leftMargin: 25
|
leftMargin: 25
|
||||||
rightMargin: 25
|
rightMargin: 25
|
||||||
bottomMargin: 70
|
bottomMargin: 70 + SettingsController.safeAreaBottomMargin
|
||||||
|
|
||||||
width: parent.width - leftMargin - rightMargin
|
width: parent.width - leftMargin - rightMargin
|
||||||
|
|
||||||
|
|||||||
@@ -28,5 +28,7 @@ QtObject {
|
|||||||
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
|
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
|
||||||
readonly property color pearlGray: '#EAEAEC'
|
readonly property color pearlGray: '#EAEAEC'
|
||||||
readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26)
|
readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26)
|
||||||
|
readonly property color translucentSlateGray: Qt.rgba(85/255, 86/255, 92/255, 0.13)
|
||||||
|
readonly property color translucentOnyxBlack: Qt.rgba(28/255, 29/255, 33/255, 0.13)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,19 +68,9 @@ PageType {
|
|||||||
objectName: "homeColumnLayout"
|
objectName: "homeColumnLayout"
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: 12
|
anchors.topMargin: 12 + SettingsController.safeAreaTopMargin
|
||||||
anchors.bottomMargin: 16
|
anchors.bottomMargin: 16
|
||||||
|
|
||||||
AdLabel {
|
|
||||||
id: adLabel
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: adLabel.contentHeight
|
|
||||||
Layout.leftMargin: 16
|
|
||||||
Layout.rightMargin: 16
|
|
||||||
Layout.bottomMargin: 22
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
id: loggingButton
|
id: loggingButton
|
||||||
objectName: "loggingButton"
|
objectName: "loggingButton"
|
||||||
@@ -189,6 +179,16 @@ PageType {
|
|||||||
parent: root
|
parent: root
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AdLabel {
|
||||||
|
id: adLabel
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: adLabel.contentHeight
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.topMargin: 22
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if(backButton.enabled && backButton.activeFocus) {
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if(backButton.enabled && backButton.activeFocus) {
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if(backButton.enabled && backButton.activeFocus) {
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -14,6 +14,19 @@ import "../Config"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ApiNewsController
|
||||||
|
function onFetchNewsFinished() {
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onErrorOccurred(errorCode) {
|
||||||
|
PageController.showErrorMessage(errorCode)
|
||||||
|
PageController.closePage()
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
@@ -25,7 +38,7 @@ PageType {
|
|||||||
BaseHeaderType {
|
BaseHeaderType {
|
||||||
id: header
|
id: header
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 24
|
Layout.topMargin: 24 + SettingsController.safeAreaTopMargin
|
||||||
Layout.bottomMargin: 16
|
Layout.bottomMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
@@ -140,9 +153,8 @@ PageType {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
ApiNewsController.fetchNews();
|
ApiNewsController.fetchNews()
|
||||||
PageController.goToPage(PageEnum.PageSettingsNewsNotifications)
|
PageController.goToPage(PageEnum.PageSettingsNewsNotifications)
|
||||||
PageController.showBusyIndicator(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if(backButton.enabled && backButton.activeFocus) {
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ PageType {
|
|||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
anchors.bottomMargin: 24
|
anchors.bottomMargin: 24
|
||||||
|
|
||||||
model: ApiDevicesModel
|
model: ApiDevicesModel
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ PageType {
|
|||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
anchors.bottomMargin: 24
|
anchors.bottomMargin: 24
|
||||||
|
|
||||||
model: instructionsModel
|
model: instructionsModel
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if(backButton.enabled && backButton.activeFocus) {
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
@@ -71,7 +71,7 @@ PageType {
|
|||||||
|
|
||||||
text: countryName
|
text: countryName
|
||||||
descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : ""
|
descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : ""
|
||||||
hideDescription: isWorkerExpired ? true : false
|
hideDescription: isWorkerExpired ? false : true
|
||||||
descriptionColor: AmneziaStyle.color.vibrantRed
|
descriptionColor: AmneziaStyle.color.vibrantRed
|
||||||
|
|
||||||
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
|
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ PageType {
|
|||||||
id: backButton
|
id: backButton
|
||||||
objectName: "backButton"
|
objectName: "backButton"
|
||||||
|
|
||||||
Layout.topMargin: 20
|
Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
}
|
}
|
||||||
|
|
||||||
HeaderTypeWithButton {
|
HeaderTypeWithButton {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ PageType {
|
|||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
anchors.bottomMargin: 24
|
anchors.bottomMargin: 24
|
||||||
|
|
||||||
model: supportModel
|
model: supportModel
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ PageType {
|
|||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
BackButtonType {
|
BackButtonType {
|
||||||
id: backButton
|
id: backButton
|
||||||
@@ -216,7 +216,7 @@ PageType {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: addAppButton
|
anchors.fill: addAppButton
|
||||||
anchors.bottomMargin: -24
|
anchors.bottomMargin: -24 - SettingsController.safeAreaBottomMargin
|
||||||
color: AmneziaStyle.color.midnightBlack
|
color: AmneziaStyle.color.midnightBlack
|
||||||
opacity: 0.8
|
opacity: 0.8
|
||||||
}
|
}
|
||||||
@@ -232,7 +232,7 @@ PageType {
|
|||||||
anchors.topMargin: 24
|
anchors.topMargin: 24
|
||||||
anchors.rightMargin: 16
|
anchors.rightMargin: 16
|
||||||
anchors.leftMargin: 16
|
anchors.leftMargin: 16
|
||||||
anchors.bottomMargin: 24
|
anchors.bottomMargin: 24 + SettingsController.safeAreaBottomMargin
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: searchField
|
id: searchField
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if(backButton.enabled && backButton.activeFocus) {
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onFocusChanged: {
|
onFocusChanged: {
|
||||||
if (this.activeFocus) {
|
if (this.activeFocus) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
}
|
}
|
||||||
|
|
||||||
FlickableType {
|
FlickableType {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ PageType {
|
|||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
BackButtonType {
|
BackButtonType {
|
||||||
id: backButton
|
id: backButton
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ PageType {
|
|||||||
objectName: "mainLayout"
|
objectName: "mainLayout"
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
spacing: 4
|
spacing: 4
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onFocusChanged: {
|
onFocusChanged: {
|
||||||
if (this.activeFocus) {
|
if (this.activeFocus) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ PageType {
|
|||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
BackButtonType {
|
BackButtonType {
|
||||||
id: backButton
|
id: backButton
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ PageType {
|
|||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
BackButtonType {
|
BackButtonType {
|
||||||
id: backButton
|
id: backButton
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onFocusChanged: {
|
onFocusChanged: {
|
||||||
if (this.activeFocus) {
|
if (this.activeFocus) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
Layout.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if(backButton.enabled && backButton.activeFocus) {
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ PageType {
|
|||||||
property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible()
|
property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible()
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 24
|
Layout.topMargin: 24 + SettingsController.safeAreaTopMargin
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onFocusChanged: {
|
onFocusChanged: {
|
||||||
if (this.activeFocus) {
|
if (this.activeFocus) {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if(backButton.enabled && backButton.activeFocus) {
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onFocusChanged: {
|
onFocusChanged: {
|
||||||
if (this.activeFocus) {
|
if (this.activeFocus) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ PageType {
|
|||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if(backButton.enabled && backButton.activeFocus) {
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onFocusChanged: {
|
onFocusChanged: {
|
||||||
if (this.activeFocus) {
|
if (this.activeFocus) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if(backButton.enabled && backButton.activeFocus) {
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ PageType {
|
|||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
topPadding: 8
|
topPadding: 8
|
||||||
bottomPadding: 8
|
bottomPadding: 8 + SettingsController.safeAreaBottomMargin
|
||||||
leftPadding: 96
|
leftPadding: 96
|
||||||
rightPadding: 96
|
rightPadding: 96
|
||||||
|
|
||||||
|
|||||||
+21
-2
@@ -16,6 +16,26 @@ Window {
|
|||||||
id: root
|
id: root
|
||||||
objectName: "mainWindow"
|
objectName: "mainWindow"
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Qt.application
|
||||||
|
function onStateChanged() {
|
||||||
|
if (Qt.platform.os === "android" && Qt.application.state === Qt.ApplicationActive) {
|
||||||
|
refreshTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: refreshTimer
|
||||||
|
interval: 150
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (Qt.platform.os === "android" && SettingsController.isEdgeToEdgeEnabled()) {
|
||||||
|
console.log("QML: Application resumed with edge-to-edge")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
visible: true
|
visible: true
|
||||||
width: GC.screenWidth
|
width: GC.screenWidth
|
||||||
height: GC.screenHeight
|
height: GC.screenHeight
|
||||||
@@ -111,7 +131,6 @@ Window {
|
|||||||
|
|
||||||
PageStart {
|
PageStart {
|
||||||
objectName: "pageStart"
|
objectName: "pageStart"
|
||||||
|
|
||||||
width: root.width
|
width: root.width
|
||||||
height: root.height
|
height: root.height
|
||||||
}
|
}
|
||||||
@@ -164,7 +183,7 @@ Window {
|
|||||||
id: privateKeyPassphraseDrawer
|
id: privateKeyPassphraseDrawer
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
expandedHeight: root.height * 0.35
|
expandedHeight: root.height * 0.35 + SettingsController.safeAreaBottomMargin
|
||||||
|
|
||||||
expandedStateContent: ColumnLayout {
|
expandedStateContent: ColumnLayout {
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
|||||||
Reference in New Issue
Block a user