mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
Compare commits
30 Commits
android-7
...
feat/add-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08138c218c | ||
|
|
3119a589fb | ||
|
|
753a549938 | ||
|
|
228dee7680 | ||
|
|
5133b4d6bb | ||
|
|
fa50a07693 | ||
|
|
cf9196777c | ||
|
|
0945997b34 | ||
|
|
105c42db1c | ||
|
|
89818ff63d | ||
|
|
414c422177 | ||
|
|
b39ac8556c | ||
|
|
5e1742262d | ||
|
|
5a07a1274f | ||
|
|
7b8ff1fd6e | ||
|
|
c7221832e0 | ||
|
|
eb7d031c7d | ||
|
|
3b3a0aaceb | ||
|
|
01ec79b7d5 | ||
|
|
3d6339e2dd | ||
|
|
b4d78d865a | ||
|
|
b53cdcff08 | ||
|
|
3cc18c5807 | ||
|
|
5fdce1e49e | ||
|
|
2ee61a040b | ||
|
|
741b5cc0f9 | ||
|
|
aaf0e070dc | ||
|
|
e0e126eda8 | ||
|
|
236daf6b3b | ||
|
|
f1481b1b1f |
221
.github/workflows/deploy.yml
vendored
221
.github/workflows/deploy.yml
vendored
@@ -44,6 +44,13 @@ jobs:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
@@ -55,13 +62,13 @@ jobs:
|
||||
bash deploy/build_linux.sh
|
||||
|
||||
- name: 'Pack installer'
|
||||
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin
|
||||
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin && zip AmneziaVPN_${VERSION}_linux_x64.tar.zip AmneziaVPN_Linux_Installer.tar
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Linux_installer.tar
|
||||
path: deploy/AmneziaVPN_Linux_Installer.tar
|
||||
name: AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
|
||||
path: deploy/AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
@@ -102,6 +109,14 @@ jobs:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
@@ -117,6 +132,8 @@ jobs:
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
set-env: 'true'
|
||||
aqtversion: '==3.1.21'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Setup mvsc'
|
||||
@@ -128,15 +145,20 @@ jobs:
|
||||
shell: cmd
|
||||
run: |
|
||||
set BUILD_ARCH=${{ env.BUILD_ARCH }}
|
||||
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2019_64\\bin"
|
||||
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
|
||||
call deploy\\build_windows.bat
|
||||
set "QT_BIN_DIR=${{ runner.temp }}\Qt\${{ env.QT_VERSION }}\msvc2019_64\bin"
|
||||
set "QIF_BIN_DIR=${{ runner.temp }}\Qt\Tools\QtInstallerFramework\${{ env.QIF_VERSION }}\bin"
|
||||
call deploy\build_windows.bat
|
||||
|
||||
- name: 'Rename Windows installer'
|
||||
shell: cmd
|
||||
run: |
|
||||
copy AmneziaVPN_x${{ env.BUILD_ARCH }}.exe AmneziaVPN_%VERSION%_x${{ env.BUILD_ARCH }}.exe
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Windows_installer
|
||||
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
|
||||
name: AmneziaVPN_${{ env.VERSION }}_x${{ env.BUILD_ARCH }}.exe
|
||||
path: AmneziaVPN_${{ env.VERSION }}_x${{ env.BUILD_ARCH }}.exe
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
@@ -146,6 +168,136 @@ jobs:
|
||||
path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Windows-ARM64:
|
||||
runs-on: windows-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.8.3
|
||||
QIF_VERSION: 4.7
|
||||
BUILD_ARCH: arm64
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Get 3rd-prebuilt'
|
||||
shell: bash
|
||||
run: |
|
||||
rm -rf client/3rd-prebuilt
|
||||
|
||||
- name: 'Checkout 3rd-prebuilt'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: amnezia-vpn/3rd-prebuilt
|
||||
ref: feature/add-support-arm64
|
||||
path: client/3rd-prebuilt
|
||||
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Cleanup Qt directory'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$baseDir = "${{ runner.temp }}"
|
||||
$qtVersion = "${{ env.QT_VERSION }}"
|
||||
# Handle both path formats (with \ and /)
|
||||
$paths = @(
|
||||
"$baseDir\Qt\$qtVersion\msvc2022_64",
|
||||
"$baseDir/Qt/$qtVersion/msvc2022_64",
|
||||
"$baseDir\Qt\$qtVersion\msvc2022_arm64",
|
||||
"$baseDir/Qt/$qtVersion/msvc2022_arm64"
|
||||
)
|
||||
foreach ($path in $paths) {
|
||||
if (Test-Path $path) {
|
||||
Write-Host "Removing $path..."
|
||||
Remove-Item -Path $path -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
- name: 'Install Qt Desktop (host for cross-compilation)'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'windows'
|
||||
target: 'desktop'
|
||||
arch: 'win64_msvc2022_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'false'
|
||||
aqtversion: '==3.3.0'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install Qt ARM64'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'windows'
|
||||
target: 'desktop'
|
||||
arch: 'win64_msvc2022_arm64_cross_compiled'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
set-env: 'false'
|
||||
aqtversion: '==3.3.0'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Setup mvsc'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: 'x64_arm64'
|
||||
|
||||
- name: 'Build project'
|
||||
shell: cmd
|
||||
run: |
|
||||
set BUILD_ARCH=${{ env.BUILD_ARCH }}
|
||||
set "QT_BIN_DIR=${{ runner.temp }}\Qt\${{ env.QT_VERSION }}\msvc2022_arm64\bin"
|
||||
set "QT_HOST_PATH=${{ runner.temp }}\Qt\${{ env.QT_VERSION }}\msvc2022_64"
|
||||
set "QT_HOST_BIN_DIR=${{ runner.temp }}\Qt\${{ env.QT_VERSION }}\msvc2022_64\bin"
|
||||
set "QIF_BIN_DIR=${{ runner.temp }}\Qt\Tools\QtInstallerFramework\${{ env.QIF_VERSION }}\bin"
|
||||
call deploy\build_windows.bat
|
||||
|
||||
- name: 'Rename Windows installer'
|
||||
shell: cmd
|
||||
run: |
|
||||
copy AmneziaVPN_x${{ env.BUILD_ARCH }}.exe AmneziaVPN_%VERSION%_x${{ env.BUILD_ARCH }}.exe
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_${{ env.VERSION }}_x${{ env.BUILD_ARCH }}.exe
|
||||
path: AmneziaVPN_${{ env.VERSION }}_x${{ env.BUILD_ARCH }}.exe
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Windows_ARM64_unpacked
|
||||
path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release
|
||||
retention-days: 7
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-iOS:
|
||||
@@ -380,6 +532,13 @@ jobs:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
@@ -388,11 +547,17 @@ jobs:
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||
bash deploy/build_macos.sh -n
|
||||
|
||||
- name: 'Pack macOS installer'
|
||||
run: |
|
||||
cd deploy/build/pkg
|
||||
zip -r ../../AmneziaVPN_${VERSION}_macos.zip AmneziaVPN.pkg
|
||||
cd ../../..
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_installer
|
||||
path: deploy/build/pkg/AmneziaVPN.pkg
|
||||
name: AmneziaVPN_${{ env.VERSION }}_macos.zip
|
||||
path: deploy/AmneziaVPN_${{ env.VERSION }}_macos.zip
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
@@ -469,8 +634,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
ANDROID_BUILD_PLATFORM: android-34
|
||||
QT_VERSION: 6.7.3
|
||||
ANDROID_BUILD_PLATFORM: android-36
|
||||
QT_VERSION: 6.8.3
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
@@ -551,6 +716,13 @@ jobs:
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
@@ -584,35 +756,44 @@ jobs:
|
||||
shell: bash
|
||||
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
|
||||
|
||||
- name: 'Rename Android APKs'
|
||||
run: |
|
||||
cd deploy/build
|
||||
mv AmneziaVPN-x86_64-release.apk AmneziaVPN_${VERSION}_android9+_x86_64.apk
|
||||
mv AmneziaVPN-x86-release.apk AmneziaVPN_${VERSION}_android9+_x86.apk
|
||||
mv AmneziaVPN-arm64-v8a-release.apk AmneziaVPN_${VERSION}_android9+_arm64-v8a.apk
|
||||
mv AmneziaVPN-armeabi-v7a-release.apk AmneziaVPN_${VERSION}_android9+_armeabi-v7a.apk
|
||||
cd ../..
|
||||
|
||||
- name: 'Upload x86_64 apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-x86_64
|
||||
path: deploy/build/AmneziaVPN-x86_64-release.apk
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload x86 apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-x86
|
||||
path: deploy/build/AmneziaVPN-x86-release.apk
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload arm64-v8a apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-arm64-v8a
|
||||
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload armeabi-v7a apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-armeabi-v7a
|
||||
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.8.11.0)
|
||||
set(AMNEZIAVPN_VERSION 4.8.11.5)
|
||||
|
||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
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 2100)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
@@ -39,6 +39,20 @@ endif()
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES})
|
||||
|
||||
# Diagnostic: Print Qt configuration
|
||||
message(STATUS "=== Qt Configuration Diagnostics ===")
|
||||
message(STATUS "Qt6_DIR: ${Qt6_DIR}")
|
||||
message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}")
|
||||
message(STATUS "CMAKE_GENERATOR_PLATFORM: ${CMAKE_GENERATOR_PLATFORM}")
|
||||
message(STATUS "CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}")
|
||||
if(TARGET Qt6::Core)
|
||||
get_target_property(Qt6Core_LOCATION Qt6::Core LOCATION)
|
||||
message(STATUS "Qt6::Core location: ${Qt6Core_LOCATION}")
|
||||
get_target_property(Qt6Core_IMPLIB Qt6::Core IMPORTED_IMPLIB_RELEASE)
|
||||
message(STATUS "Qt6::Core import library: ${Qt6Core_IMPLIB}")
|
||||
endif()
|
||||
message(STATUS "====================================")
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
Qt6::Core Qt6::Gui
|
||||
Qt6::Network Qt6::Xml Qt6::RemoteObjects
|
||||
@@ -56,7 +70,7 @@ target_include_directories(${PROJECT} PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
|
||||
)
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
|
||||
@@ -199,7 +213,9 @@ target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAM
|
||||
|
||||
# deploy artifacts required to run the application to the debug build folder
|
||||
if(WIN32)
|
||||
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
|
||||
if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ARM64")
|
||||
set(DEPLOY_PLATFORM_PATH "windows/arm64")
|
||||
elseif("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
|
||||
set(DEPLOY_PLATFORM_PATH "windows/x64")
|
||||
else()
|
||||
set(DEPLOY_PLATFORM_PATH "windows/x32")
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
#include <QEvent>
|
||||
#include <QDir>
|
||||
#include <QSettings>
|
||||
|
||||
#include "logger.h"
|
||||
#include "ui/controllers/pageController.h"
|
||||
@@ -53,16 +55,44 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
|
||||
|
||||
AmneziaApplication::~AmneziaApplication()
|
||||
{
|
||||
if (m_vpnConnection) {
|
||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::QueuedConnection);
|
||||
QThread::msleep(2000);
|
||||
}
|
||||
|
||||
m_vpnConnectionThread.requestInterruption();
|
||||
m_vpnConnectionThread.quit();
|
||||
|
||||
if (!m_vpnConnectionThread.wait(3000)) {
|
||||
m_vpnConnectionThread.terminate();
|
||||
m_vpnConnectionThread.wait(500);
|
||||
}
|
||||
|
||||
if (m_engine) {
|
||||
QObject::disconnect(m_engine, 0, 0, 0);
|
||||
delete m_engine;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
namespace {
|
||||
static void clearQtCaches()
|
||||
{
|
||||
const QString cacheRoot = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
|
||||
if (!cacheRoot.isEmpty()) {
|
||||
QDir(cacheRoot + "/QtShaderCache").removeRecursively();
|
||||
QDir(cacheRoot + "/qmlcache").removeRecursively();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void AmneziaApplication::init()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
clearQtCaches();
|
||||
#endif
|
||||
m_engine = new QQmlApplicationEngine;
|
||||
|
||||
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
|
||||
|
||||
@@ -45,7 +45,8 @@
|
||||
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|
||||
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
||||
android:launchMode="singleInstance"
|
||||
android:windowSoftInputMode="stateUnchanged|adjustResize"
|
||||
android:windowSoftInputMode="adjustResize|stateUnchanged"
|
||||
android:enableOnBackInvokedCallback="false"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
@@ -214,4 +215,4 @@
|
||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
</manifest>
|
||||
@@ -6,6 +6,9 @@
|
||||
<item name="android:colorBackground">@color/black</item>
|
||||
<item name="android:windowActionBar">false</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 name="Translucent" parent="NoActionBar">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
|
||||
@@ -35,6 +35,11 @@ import android.widget.Toast
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.OnApplyWindowInsetsListener
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import java.io.IOException
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@@ -170,10 +175,9 @@ class AmneziaActivity : QtActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(TAG, "Create Amnezia activity")
|
||||
loadLibs()
|
||||
window.apply {
|
||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
statusBarColor = getColor(R.color.black)
|
||||
}
|
||||
|
||||
// Configure window for edge-to-edge display
|
||||
configureWindowForEdgeToEdge()
|
||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
val proto = mainScope.async(Dispatchers.IO) {
|
||||
VpnStateStore.getVpnState().vpnProto
|
||||
@@ -265,6 +269,82 @@ class AmneziaActivity : QtActivity() {
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
window.decorView.apply {
|
||||
invalidate()
|
||||
|
||||
postDelayed({
|
||||
sendTouch(1f, 1f)
|
||||
}, 100)
|
||||
|
||||
postDelayed({
|
||||
sendTouch(2f, 2f)
|
||||
}, 200)
|
||||
|
||||
postDelayed({
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}, 250)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureWindowForEdgeToEdge() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
window.apply {
|
||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
addFlags(LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
||||
statusBarColor = android.graphics.Color.TRANSPARENT
|
||||
navigationBarColor = android.graphics.Color.TRANSPARENT
|
||||
}
|
||||
|
||||
WindowInsetsControllerCompat(window, window.decorView).apply {
|
||||
isAppearanceLightStatusBars = false
|
||||
isAppearanceLightNavigationBars = false
|
||||
}
|
||||
|
||||
// Workaround for Android 14 (API 34+) IME adjustResize bug
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
setupImeInsetsListener()
|
||||
}
|
||||
} else {
|
||||
window.apply {
|
||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
statusBarColor = getColor(R.color.black)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupImeInsetsListener() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
|
||||
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
|
||||
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
|
||||
|
||||
val imeHeight = if (imeVisible) imeInsets.bottom else 0
|
||||
|
||||
val density = resources.displayMetrics.density
|
||||
val imeHeightDp = (imeHeight / density).toInt()
|
||||
|
||||
// Also track system bars (navigation bar, status bar) changes
|
||||
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val navBarHeight = systemBarsInsets.bottom
|
||||
val navBarHeightDp = (navBarHeight / density).toInt()
|
||||
val statusBarHeight = systemBarsInsets.top
|
||||
val statusBarHeightDp = (statusBarHeight / density).toInt()
|
||||
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onImeInsetsChanged(imeHeightDp)
|
||||
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
|
||||
}
|
||||
|
||||
// Return windowInsets instead of CONSUMED to allow proper handling
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.d(TAG, "Destroy Amnezia activity")
|
||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||
@@ -666,6 +746,43 @@ class AmneziaActivity : QtActivity() {
|
||||
@Suppress("unused")
|
||||
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")
|
||||
fun startQrCodeReader() {
|
||||
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 packageName: String = pi.packageName
|
||||
val icon: Boolean = ai.icon != 0
|
||||
val icon: Boolean = (ai?.icon ?: 0) != 0
|
||||
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
|
||||
|
||||
init {
|
||||
val name = ai.loadLabel(pm).toString()
|
||||
this.name = if (name != packageName) name else null
|
||||
val name = ai?.loadLabel(pm)?.toString()
|
||||
this.name = name?.takeIf { it != packageName }
|
||||
}
|
||||
|
||||
override fun compareTo(other: App): Int {
|
||||
|
||||
@@ -28,4 +28,7 @@ object QtAndroidController {
|
||||
external fun onAuthResult(result: Boolean)
|
||||
|
||||
external fun decodeQrCode(data: String): Boolean
|
||||
|
||||
external fun onImeInsetsChanged(heightDp: Int)
|
||||
external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int)
|
||||
}
|
||||
@@ -15,17 +15,35 @@ set(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/lib")
|
||||
|
||||
if(WIN32)
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/windows/include")
|
||||
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
|
||||
# Check for ARM64 architecture first (CMAKE_GENERATOR_PLATFORM is set to ARM64 for cross-compilation)
|
||||
message(STATUS "=== 3rd Party Libraries Configuration ===")
|
||||
message(STATUS "CMAKE_GENERATOR_PLATFORM: ${CMAKE_GENERATOR_PLATFORM}")
|
||||
message(STATUS "CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}")
|
||||
message(STATUS "CMAKE_SIZEOF_VOID_P: ${CMAKE_SIZEOF_VOID_P}")
|
||||
if("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "ARM64" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ARM64")
|
||||
# ARM64: use ARM64 versions for both OpenSSL and libssh
|
||||
message(STATUS "Building for Windows ARM64")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/windows/arm64/ssh.lib")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/windows/arm64")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/winarm64/libssl.lib")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/winarm64/libcrypto.lib")
|
||||
message(STATUS "libssh: ${LIBSSH_LIB_PATH}")
|
||||
message(STATUS "OpenSSL SSL: ${OPENSSL_LIB_SSL_PATH}")
|
||||
message(STATUS "OpenSSL Crypto: ${OPENSSL_LIB_CRYPTO_PATH}")
|
||||
elseif("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
|
||||
message(STATUS "Building for Windows x64")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/windows/x86_64/ssh.lib")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/windows/x86_64")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/win64/libssl.lib")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win64/libcrypto.lib")
|
||||
else()
|
||||
message(STATUS "Building for Windows x86")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/windows/x86/ssh.lib")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/windows/x86")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libssl.lib")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
|
||||
endif()
|
||||
message(STATUS "==========================================")
|
||||
elseif(APPLE AND NOT IOS)
|
||||
if(MACOS_NE)
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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
|
||||
"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_CODE ${APP_ANDROID_VERSION_CODE}
|
||||
QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK}
|
||||
QT_ANDROID_TARGET_SDK_VERSION 34
|
||||
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0
|
||||
QT_ANDROID_TARGET_SDK_VERSION 36
|
||||
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 36.0.0
|
||||
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
|
||||
)
|
||||
|
||||
|
||||
@@ -136,21 +136,10 @@ set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||
add_subdirectory(ios/networkextension)
|
||||
add_dependencies(${PROJECT} networkextension)
|
||||
|
||||
set(OPENVPN_FRAMEWORK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios")
|
||||
set(OPENVPN_EMBEDDED_FRAMEWORKS
|
||||
"${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
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework"
|
||||
)
|
||||
|
||||
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS "${OPENVPN_EMBEDDED_FRAMEWORKS}")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS "$(inherited) ${OPENVPN_FRAMEWORK_DIR}")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/)
|
||||
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}")
|
||||
|
||||
@@ -28,6 +28,7 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/../common/logger/logger.h
|
||||
${CLIENT_ROOT_DIR}/utils/qmlUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/api/apiUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/osSignalHandler.h
|
||||
)
|
||||
|
||||
# Mozilla headres
|
||||
@@ -79,6 +80,7 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
|
||||
${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/osSignalHandler.cpp
|
||||
)
|
||||
|
||||
# Mozilla sources
|
||||
@@ -175,7 +177,7 @@ if(WIN32)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
message("Client desktop build")
|
||||
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||
|
||||
@@ -203,3 +205,14 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE AND MACOS_NE)
|
||||
# Include only the tray notification handler in NE builds
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -47,12 +47,14 @@ namespace apiDefs
|
||||
constexpr QLatin1String serverCountryName("server_country_name");
|
||||
|
||||
constexpr QLatin1String osVersion("os_version");
|
||||
constexpr QLatin1String appLanguage("app_language");
|
||||
|
||||
constexpr QLatin1String availableCountries("available_countries");
|
||||
constexpr QLatin1String activeDeviceCount("active_device_count");
|
||||
constexpr QLatin1String maxDeviceCount("max_device_count");
|
||||
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
|
||||
constexpr QLatin1String issuedConfigs("issued_configs");
|
||||
constexpr QLatin1String subscriptionDescription("subscription_description");
|
||||
|
||||
constexpr QLatin1String supportInfo("support_info");
|
||||
constexpr QLatin1String email("email");
|
||||
@@ -68,6 +70,12 @@ namespace apiDefs
|
||||
constexpr QLatin1String transactionId("transaction_id");
|
||||
|
||||
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
|
||||
|
||||
@@ -324,7 +324,7 @@ void CoreController::initContainerModelUpdateHandler()
|
||||
&ContainersModel::updateModel);
|
||||
connect(m_serversModel.get(), &ServersModel::gatewayStacksExpanded, this, [this]() {
|
||||
if (m_serversModel->hasServersFromGatewayApi()) {
|
||||
m_apiNewsController->fetchNews();
|
||||
m_apiNewsController->fetchNews(false);
|
||||
}
|
||||
});
|
||||
m_serversModel->resetModel();
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
#include "gatewayController.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <random>
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QPromise>
|
||||
#include <QUrl>
|
||||
|
||||
#include "QBlockCipher.h"
|
||||
@@ -50,24 +53,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
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||
|
||||
request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
||||
encRequestData.request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
encRequestData.request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
encRequestData.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));
|
||||
|
||||
// bypass killSwitch exceptions for API-gateway
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (m_isStrictKillSwitchEnabled) {
|
||||
QString host = QUrl(request.url()).host();
|
||||
QString host = QUrl(encRequestData.request.url()).host();
|
||||
QString ip = NetworkUtilities::getIPAddress(host);
|
||||
if (!ip.isEmpty()) {
|
||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||
@@ -76,14 +80,14 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
#endif
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
||||
QByteArray salt = blockCipher.generatePrivateSalt(8);
|
||||
encRequestData.key = blockCipher.generatePrivateSalt(32);
|
||||
encRequestData.iv = blockCipher.generatePrivateSalt(32);
|
||||
encRequestData.salt = blockCipher.generatePrivateSalt(8);
|
||||
|
||||
QJsonObject keyPayload;
|
||||
keyPayload[configKey::aesKey] = QString(key.toBase64());
|
||||
keyPayload[configKey::aesIv] = QString(iv.toBase64());
|
||||
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
|
||||
keyPayload[configKey::aesKey] = QString(encRequestData.key.toBase64());
|
||||
keyPayload[configKey::aesIv] = QString(encRequestData.iv.toBase64());
|
||||
keyPayload[configKey::aesSalt] = QString(encRequestData.salt.toBase64());
|
||||
|
||||
QByteArray encryptedKeyPayload;
|
||||
QByteArray encryptedApiPayload;
|
||||
@@ -98,31 +102,45 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
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);
|
||||
EVP_PKEY_free(publicKey);
|
||||
|
||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv,
|
||||
"", encRequestData.salt);
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error when encrypting the request body";
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
|
||||
return encRequestData;
|
||||
}
|
||||
|
||||
QJsonObject requestBody;
|
||||
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.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;
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
|
||||
QList<QSslError> sslErrors;
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
QString replyErrorString = reply->errorString();
|
||||
@@ -131,19 +149,21 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, encryptedResponseBody, true, key, iv, salt)) {
|
||||
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
||||
request.setUrl(url);
|
||||
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||
if (sslErrors.isEmpty()
|
||||
&& shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
|
||||
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
|
||||
encRequestData.request.setUrl(url);
|
||||
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||
};
|
||||
|
||||
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &key, &iv,
|
||||
&salt, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
|
||||
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors,
|
||||
&encRequestData, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
|
||||
encryptedResponseBody = reply->readAll();
|
||||
replyErrorString = reply->errorString();
|
||||
replyError = reply->error();
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
@@ -161,7 +181,9 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
}
|
||||
|
||||
try {
|
||||
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
responseBody =
|
||||
blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
|
||||
return ErrorCode::NoError;
|
||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||
Utils::logException();
|
||||
@@ -170,6 +192,93 @@ 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, [promise, sslErrors, encRequestData, endpoint, apiPayload, reply, this]() mutable {
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
QString replyErrorString = reply->errorString();
|
||||
auto replyError = reply->error();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
auto processResponse = [promise, encRequestData](const QByteArray &ecryptedResponseBody, const QList<QSslError> &sslErrors,
|
||||
QNetworkReply::NetworkError replyError, const QString &replyErrorString,
|
||||
int httpStatusCode) {
|
||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, ecryptedResponseBody);
|
||||
if (errorCode) {
|
||||
promise->addResult(qMakePair(errorCode, QByteArray()));
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
try {
|
||||
QByteArray responseBody = blockCipher.decryptAesBlockCipher(ecryptedResponseBody, 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();
|
||||
}
|
||||
};
|
||||
|
||||
if (sslErrors->isEmpty()
|
||||
&& shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
|
||||
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
|
||||
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
|
||||
|
||||
QStringList baseUrls;
|
||||
if (m_isDevEnvironment) {
|
||||
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
||||
} else {
|
||||
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
||||
}
|
||||
|
||||
QStringList proxyStorageUrls;
|
||||
if (!serviceType.isEmpty()) {
|
||||
for (const auto &baseUrl : baseUrls) {
|
||||
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
|
||||
proxyStorageUrls.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)
|
||||
+ ".json");
|
||||
}
|
||||
}
|
||||
for (const auto &baseUrl : baseUrls)
|
||||
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
|
||||
|
||||
getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
|
||||
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrls) {
|
||||
bypassProxyAsync(endpoint, proxyUrls, encRequestData, processResponse);
|
||||
});
|
||||
});
|
||||
|
||||
} else {
|
||||
processResponse(encryptedResponseBody, *sslErrors, replyError, replyErrorString, httpStatusCode);
|
||||
}
|
||||
});
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
|
||||
{
|
||||
QNetworkRequest request;
|
||||
@@ -206,7 +315,7 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
@@ -318,7 +427,7 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
auto result = replyProcessingFunction(reply, sslErrors);
|
||||
reply->deleteLater();
|
||||
@@ -340,7 +449,7 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||
reply->deleteLater();
|
||||
@@ -368,3 +477,134 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||
std::function<void(const QStringList &)> onComplete)
|
||||
{
|
||||
if (currentProxyStorageIndex >= proxyStorageUrls.size()) {
|
||||
onComplete({});
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setUrl(proxyStorageUrls[currentProxyStorageIndex]);
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->get(request);
|
||||
|
||||
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) { *(state->sslErrors) = e; });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, proxyStorageUrls, currentProxyStorageIndex, onComplete, reply]() {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray encrypted = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
QByteArray responseBody;
|
||||
try {
|
||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
if (!m_isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray h = hash.result().toHex();
|
||||
|
||||
QByteArray decKey = QByteArray::fromHex(h.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
|
||||
QByteArray ba = QByteArray::fromBase64(encrypted);
|
||||
|
||||
QSimpleCrypto::QBlockCipher cipher;
|
||||
responseBody = cipher.decryptAesBlockCipher(ba, decKey, iv);
|
||||
} else {
|
||||
responseBody = encrypted;
|
||||
}
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error decrypting payload";
|
||||
QMetaObject::invokeMethod(
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||
QStringList endpoints;
|
||||
for (const QJsonValue &endpoint : endpointsArray)
|
||||
endpoints.push_back(endpoint.toString());
|
||||
|
||||
QStringList shuffled = endpoints;
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(shuffled.begin(), shuffled.end(), generator);
|
||||
|
||||
onComplete(shuffled);
|
||||
return;
|
||||
}
|
||||
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << httpStatusCode;
|
||||
qDebug() << "go to the next storage endpoint";
|
||||
reply->deleteLater();
|
||||
QMetaObject::invokeMethod(
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
||||
void GatewayController::getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete)
|
||||
{
|
||||
if (currentProxyIndex >= proxyUrls.size()) {
|
||||
onComplete("");
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(1000);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setUrl(proxyUrls[currentProxyIndex] + "lmbd-health");
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->get(request);
|
||||
|
||||
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) {
|
||||
// *(state->sslErrors) = e;
|
||||
// });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, proxyUrls, currentProxyIndex, onComplete, reply]() {
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
m_proxyUrl = proxyUrls[currentProxyIndex];
|
||||
onComplete(m_proxyUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "go to the next proxy endpoint";
|
||||
QMetaObject::invokeMethod(this, [=]() { getProxyUrlAsync(proxyUrls, currentProxyIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
||||
void GatewayController::bypassProxyAsync(
|
||||
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
|
||||
std::function<void(const QByteArray &, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete)
|
||||
{
|
||||
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
|
||||
if (proxyUrl.isEmpty()) {
|
||||
onComplete(QByteArray(), *sslErrors, QNetworkReply::InternalServerError, "empty proxy url", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request = encRequestData.request;
|
||||
request.setUrl(endpoint.arg(proxyUrl));
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(request, encRequestData.requestBody);
|
||||
|
||||
connect(reply, &QNetworkReply::sslErrors, this, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [sslErrors, onComplete, reply]() {
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
QString replyErrorString = reply->errorString();
|
||||
auto replyError = reply->error();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
onComplete(encryptedResponseBody, *sslErrors, replyError, replyErrorString, httpStatusCode);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#ifndef GATEWAYCONTROLLER_H
|
||||
#define GATEWAYCONTROLLER_H
|
||||
|
||||
#include <QFuture>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QPromise>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "core/defs.h"
|
||||
|
||||
@@ -19,8 +23,21 @@ public:
|
||||
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
||||
|
||||
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
|
||||
|
||||
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);
|
||||
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption,
|
||||
const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = "");
|
||||
@@ -28,6 +45,13 @@ private:
|
||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||
|
||||
void getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||
std::function<void(const QStringList &)> onComplete);
|
||||
void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete);
|
||||
void bypassProxyAsync(
|
||||
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
|
||||
std::function<void(const QByteArray &, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete);
|
||||
|
||||
int m_requestTimeoutMsecs;
|
||||
QString m_gatewayEndpoint;
|
||||
bool m_isDevEnvironment = false;
|
||||
|
||||
@@ -85,8 +85,9 @@ bool IpcClient::init(IpcClient *instance)
|
||||
}
|
||||
|
||||
qDebug() << "IpcClient::init succeed";
|
||||
instance->m_isSocketConnected = (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
|
||||
|
||||
return (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
|
||||
return Instance()->isSocketConnected();
|
||||
}
|
||||
|
||||
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
||||
|
||||
@@ -170,7 +170,7 @@ int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
|
||||
#ifdef Q_OS_WIN
|
||||
qDebug() << "Getting Current Internet Adapter that routes to"
|
||||
<< dst.toString();
|
||||
quint32_be ipBigEndian;
|
||||
quint32 ipBigEndian;
|
||||
quint32 ip = dst.toIPv4Address();
|
||||
qToBigEndian(ip, &ipBigEndian);
|
||||
_MIB_IPFORWARDROW routeInfo;
|
||||
|
||||
159
client/core/osSignalHandler.cpp
Normal file
159
client/core/osSignalHandler.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#include "osSignalHandler.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QSocketNotifier>
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <sys/signalfd.h>
|
||||
#include <unistd.h>
|
||||
#elif defined(Q_OS_MACOS)
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QMetaObject>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
static bool initialized = false;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
static BOOL WINAPI consoleHandler(DWORD signal)
|
||||
{
|
||||
switch (signal) {
|
||||
case CTRL_CLOSE_EVENT:
|
||||
case CTRL_C_EVENT:
|
||||
case CTRL_BREAK_EVENT:
|
||||
case CTRL_LOGOFF_EVENT:
|
||||
case CTRL_SHUTDOWN_EVENT:
|
||||
if (QCoreApplication::instance()) {
|
||||
QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection);
|
||||
}
|
||||
return TRUE;
|
||||
default: return FALSE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
static int signalFd = -1;
|
||||
static QSocketNotifier *socketNotifier = nullptr;
|
||||
|
||||
static void setupUnixSignalHandler()
|
||||
{
|
||||
sigset_t set;
|
||||
sigemptyset(&set);
|
||||
sigaddset(&set, SIGINT);
|
||||
sigaddset(&set, SIGTERM);
|
||||
|
||||
pthread_sigmask(SIBLOCK, &set, nullptr);
|
||||
|
||||
signalFd = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);
|
||||
if (signalFd < 0)
|
||||
return;
|
||||
|
||||
socketNotifier = new QSocketNotifier(signalFd, QSocketNotifier::Read, QCoreApplication::instance());
|
||||
|
||||
QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) {
|
||||
signalfd_siginfo fdsi;
|
||||
::read(signalFd, &fdsi, sizeof(fdsi));
|
||||
|
||||
if (fdsi.ssi_signo == SIGINT || fdsi.ssi_signo == SIGTERM) {
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
});
|
||||
}
|
||||
#elif defined(Q_OS_MACX)
|
||||
static int signalPipe[2] = { -1, -1 };
|
||||
static QSocketNotifier *socketNotifier = nullptr;
|
||||
|
||||
static void macSignalHandler(int)
|
||||
{
|
||||
const char ch = 1;
|
||||
::write(signalPipe[1], &ch, sizeof(ch));
|
||||
}
|
||||
|
||||
static void setupUnixSignalHandler()
|
||||
{
|
||||
if (::pipe(signalPipe) != 0)
|
||||
return;
|
||||
|
||||
::fcntl(signalPipe[0], F_SETFL, O_NONBLOCK);
|
||||
::fcntl(signalPipe[1], F_SETFL, O_NONBLOCK);
|
||||
|
||||
struct sigaction sa {};
|
||||
sa.sa_handler = macSignalHandler;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
|
||||
sigaction(SIGINT, &sa, nullptr);
|
||||
sigaction(SIGTERM, &sa, nullptr);
|
||||
|
||||
socketNotifier = new QSocketNotifier(signalPipe[0], QSocketNotifier::Read, QCoreApplication::instance());
|
||||
|
||||
QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) {
|
||||
char buf[16];
|
||||
::read(signalPipe[0], buf, sizeof(buf));
|
||||
QCoreApplication::quit();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
static void cleanupUnixSignalHandler()
|
||||
{
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
if (socketNotifier) {
|
||||
socketNotifier->setEnabled(false);
|
||||
}
|
||||
|
||||
if (signalFd >= 0) {
|
||||
::close(signalFd);
|
||||
signalFd = -1;
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_MACOS)
|
||||
if (socketNotifier) {
|
||||
socketNotifier->setEnabled(false);
|
||||
}
|
||||
|
||||
if (signalPipe[0] >= 0) {
|
||||
::close(signalPipe[0]);
|
||||
signalPipe[0] = -1;
|
||||
}
|
||||
|
||||
if (signalPipe[1] >= 0) {
|
||||
::close(signalPipe[1]);
|
||||
signalPipe[1] = -1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
OsSignalHandler::OsSignalHandler(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void OsSignalHandler::setup()
|
||||
{
|
||||
if (initialized)
|
||||
return;
|
||||
|
||||
initialized = true;
|
||||
|
||||
#if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || defined(Q_OS_MACX)
|
||||
setupUnixSignalHandler();
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
SetConsoleCtrlHandler(consoleHandler, TRUE);
|
||||
#endif
|
||||
|
||||
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [] { cleanupUnixSignalHandler(); });
|
||||
}
|
||||
17
client/core/osSignalHandler.h
Normal file
17
client/core/osSignalHandler.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef OSSIGNALHANDLER_H
|
||||
#define OSSIGNALHANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class OsSignalHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static void setup();
|
||||
|
||||
private:
|
||||
explicit OsSignalHandler(QObject *parent = nullptr);
|
||||
static void handleSignal(int signal);
|
||||
};
|
||||
|
||||
#endif // OSSIGNALHANDLER_H
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <QTimer>
|
||||
|
||||
#include "amnezia_application.h"
|
||||
#include "core/osSignalHandler.h"
|
||||
#include "migrations.h"
|
||||
#include "version.h"
|
||||
|
||||
@@ -44,6 +45,7 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
AmneziaApplication app(argc, argv);
|
||||
OsSignalHandler::setup();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
if (isAnotherInstanceRunning()) {
|
||||
|
||||
@@ -99,7 +99,9 @@ bool AndroidController::initialize()
|
||||
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
|
||||
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
|
||||
{"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)},
|
||||
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}
|
||||
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)},
|
||||
{"onImeInsetsChanged", "(I)V", reinterpret_cast<void *>(onImeInsetsChanged)},
|
||||
{"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast<void *>(onSystemBarsInsetsChanged)}
|
||||
};
|
||||
|
||||
QJniEnvironment env;
|
||||
@@ -202,6 +204,21 @@ bool AndroidController::isOnTv()
|
||||
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()
|
||||
{
|
||||
callActivityMethod("startQrCodeReader", "()V");
|
||||
@@ -521,3 +538,23 @@ bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data)
|
||||
|
||||
return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data));
|
||||
}
|
||||
// static
|
||||
void AndroidController::onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(thiz);
|
||||
|
||||
qDebug() << "Android IME insets changed: height =" << heightDp << "dp";
|
||||
emit AndroidController::instance()->imeInsetsChanged(heightDp);
|
||||
}
|
||||
|
||||
// static
|
||||
void AndroidController::onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(thiz);
|
||||
|
||||
qDebug() << "Android system bars insets changed: nav bar =" << navBarHeightDp << "dp, status bar =" << statusBarHeightDp << "dp";
|
||||
emit AndroidController::instance()->systemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ public:
|
||||
QString getFileName(const QString &uri);
|
||||
bool isCameraPresent();
|
||||
bool isOnTv();
|
||||
bool isEdgeToEdgeEnabled();
|
||||
int getStatusBarHeight();
|
||||
int getNavigationBarHeight();
|
||||
void startQrReaderActivity();
|
||||
void setSaveLogs(bool enabled);
|
||||
void exportLogsFile(const QString &fileName);
|
||||
@@ -70,6 +73,8 @@ signals:
|
||||
void importConfigFromOutside(QString config);
|
||||
void initConnectionState(Vpn::ConnectionState state);
|
||||
void authenticationResult(bool result);
|
||||
void imeInsetsChanged(int heightDp);
|
||||
void systemBarsInsetsChanged(int navBarHeightDp, int statusBarHeightDp);
|
||||
|
||||
private:
|
||||
bool isWaitingStatus = true;
|
||||
@@ -98,6 +103,8 @@ private:
|
||||
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
|
||||
static void onAuthResult(JNIEnv *env, jobject thiz, jboolean result);
|
||||
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
|
||||
static void onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp);
|
||||
static void onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp);
|
||||
|
||||
template <typename Ret, typename ...Args>
|
||||
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
import WireGuardKitGo
|
||||
|
||||
enum XrayErrors: Error {
|
||||
case noXrayConfig
|
||||
|
||||
@@ -56,7 +56,8 @@ void OpenVpnProtocol::stop()
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
IpcClient::Interface()->disableKillSwitch();
|
||||
QRemoteObjectPendingReply<bool> disableKillSwitchResp = IpcClient::Interface()->disableKillSwitch();
|
||||
disableKillSwitchResp.waitForFinished(1000);
|
||||
#endif
|
||||
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
|
||||
@@ -166,10 +166,17 @@ ErrorCode XrayProtocol::startTun2Sock()
|
||||
|
||||
void XrayProtocol::stop()
|
||||
{
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
IpcClient::Interface()->disableKillSwitch();
|
||||
IpcClient::Interface()->StartRoutingIpv6();
|
||||
IpcClient::Interface()->restoreResolvers();
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
QRemoteObjectPendingReply<bool> disableKillSwitchResp = IpcClient::Interface()->disableKillSwitch();
|
||||
disableKillSwitchResp.waitForFinished(1000);
|
||||
QRemoteObjectPendingReply<bool> StartRoutingIpv6Resp = IpcClient::Interface()->StartRoutingIpv6();
|
||||
StartRoutingIpv6Resp.waitForFinished(1000);
|
||||
QRemoteObjectPendingReply<bool> restoreResolvers = IpcClient::Interface()->restoreResolvers();
|
||||
restoreResolvers.waitForFinished(1000);
|
||||
#if !defined(Q_OS_MACOS)
|
||||
QRemoteObjectPendingReply<bool> deleteTunResp = IpcClient::Interface()->deleteTun("tun2");
|
||||
deleteTunResp.waitForFinished(1000);
|
||||
#endif
|
||||
#endif
|
||||
qDebug() << "XrayProtocol::stop()";
|
||||
m_xrayProcess.disconnect();
|
||||
@@ -177,6 +184,7 @@ void XrayProtocol::stop()
|
||||
m_xrayProcess.waitForFinished(3000);
|
||||
if (m_t2sProcess) {
|
||||
m_t2sProcess->stop();
|
||||
QThread::msleep(200);
|
||||
}
|
||||
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
|
||||
@@ -131,7 +131,6 @@
|
||||
<file>ui/qml/Components/SelectLanguageDrawer.qml</file>
|
||||
<file>ui/qml/Components/ServersListView.qml</file>
|
||||
<file>ui/qml/Components/SettingsContainersListView.qml</file>
|
||||
|
||||
<file>ui/qml/Components/TransportProtoSelector.qml</file>
|
||||
<file>ui/qml/Components/AddSitePanel.qml</file>
|
||||
<file>ui/qml/Config/GlobalConfig.qml</file>
|
||||
@@ -248,6 +247,7 @@
|
||||
<file>ui/qml/Components/OtpCodeDrawer.qml</file>
|
||||
<file>ui/qml/Components/AwgTextField.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
|
||||
<file>ui/qml/Components/SmartScroll.qml</file>
|
||||
</qresource>
|
||||
<qresource prefix="/countriesFlags">
|
||||
<file>images/flagKit/ZW.svg</file>
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
<context>
|
||||
<name>AdLabel</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/Components/AdLabel.qml" line="57"/>
|
||||
<source>Amnezia Premium - for access to all websites and online resources</source>
|
||||
<translation>Amnezia Premium - доступ ко всем сайтам и онлайн ресурсам</translation>
|
||||
<translation type="vanished">Amnezia Premium - доступ ко всем сайтам и онлайн ресурсам</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -61,7 +60,7 @@
|
||||
<name>ApiAccountInfoModel</name>
|
||||
<message>
|
||||
<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>
|
||||
<translation>Активна</translation>
|
||||
</message>
|
||||
@@ -71,35 +70,33 @@
|
||||
<translation>Не активна</translation>
|
||||
</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>
|
||||
<translation>%1 из %2</translation>
|
||||
</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>
|
||||
<translation>Классический VPN для комфортной работы, загрузки больших файлов и просмотра видео. Доступ ко всем сайтам и онлайн-ресурсам. Скорость — до 200 Мбит/с</translation>
|
||||
<translation type="vanished">Классический VPN для комфортной работы, загрузки больших файлов и просмотра видео. Доступ ко всем сайтам и онлайн-ресурсам. Скорость — до 200 Мбит/с</translation>
|
||||
</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>
|
||||
<translation>Бесплатный неограниченный доступ к базовому набору сайтов и приложений, таким как Facebook, Instagram, Twitter (X), Discord, Telegram и другим. YouTube не включён в бесплатный тариф.</translation>
|
||||
<translation type="vanished">Бесплатный неограниченный доступ к базовому набору сайтов и приложений, таким как Facebook, Instagram, Twitter (X), Discord, Telegram и другим. YouTube не включён в бесплатный тариф.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ApiConfigsController</name>
|
||||
<message>
|
||||
<location filename="../ui/controllers/api/apiConfigsController.cpp" line="413"/>
|
||||
<location filename="../ui/controllers/api/apiConfigsController.cpp" line="448"/>
|
||||
<source>%1 installed successfully.</source>
|
||||
<translation>%1 успешно установлен.</translation>
|
||||
</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>
|
||||
<translation>Конфигурация API перезагружена</translation>
|
||||
</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>
|
||||
<translation>Страна подключения изменена на %1</translation>
|
||||
</message>
|
||||
@@ -627,27 +624,32 @@ Thank you for staying with us!</source>
|
||||
<translation>Продолжить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="102"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="92"/>
|
||||
<source>Logging enabled</source>
|
||||
<translation>Логирование включено</translation>
|
||||
</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>
|
||||
<translation>Раздельное туннелирование включено</translation>
|
||||
</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>
|
||||
<translation>Раздельное туннелирование выключено</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="381"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="409"/>
|
||||
<source>VPN protocol</source>
|
||||
<translation>VPN-протокол</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="434"/>
|
||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="462"/>
|
||||
<source>Servers</source>
|
||||
<translation>Серверы</translation>
|
||||
</message>
|
||||
@@ -1579,32 +1581,37 @@ Thank you for staying with us!</source>
|
||||
<translation>Настройки</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="101"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="102"/>
|
||||
<source>Servers</source>
|
||||
<translation>Серверы</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="112"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="113"/>
|
||||
<source>Connection</source>
|
||||
<translation>Соединение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="123"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="124"/>
|
||||
<source>Application</source>
|
||||
<translation>Приложение</translation>
|
||||
</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>
|
||||
<translation>Резервное копирование</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="145"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="163"/>
|
||||
<source>About AmneziaVPN</source>
|
||||
<translation>Об AmneziaVPN</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="156"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="174"/>
|
||||
<source>Dev console</source>
|
||||
<translation>Dev console</translation>
|
||||
</message>
|
||||
@@ -2763,6 +2770,14 @@ Thank you for staying with us!</source>
|
||||
<translation>Очистить логи</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PageSettingsNewsNotifications</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsNewsNotifications.qml" line="33"/>
|
||||
<source>News & Notifications</source>
|
||||
<translation>Новости и Уведомления</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PageSettingsServerData</name>
|
||||
<message>
|
||||
@@ -3012,13 +3027,13 @@ Thank you for staying with us!</source>
|
||||
</message>
|
||||
<message>
|
||||
<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>
|
||||
<translation>Продолжить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<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>
|
||||
<translation>Отменить</translation>
|
||||
</message>
|
||||
@@ -3059,8 +3074,8 @@ Thank you for staying with us!</source>
|
||||
</message>
|
||||
<message>
|
||||
<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="472"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="458"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="471"/>
|
||||
<source>Sites files (*.json)</source>
|
||||
<translation>Файлы сайтов (*.json)</translation>
|
||||
</message>
|
||||
@@ -3070,33 +3085,33 @@ Thank you for staying with us!</source>
|
||||
<translation>Очистить список сайтов</translation>
|
||||
</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>
|
||||
<translation>Очистить список сайтов?</translation>
|
||||
</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>
|
||||
<translation>Все сайты будут удалены из списка.</translation>
|
||||
</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>
|
||||
<translation>Импортировать список с сайтами</translation>
|
||||
</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>
|
||||
<translation>Заменить список с сайтами</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="458"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="471"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="457"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="470"/>
|
||||
<source>Open sites file</source>
|
||||
<translation>Открыть список с сайтами</translation>
|
||||
</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>
|
||||
<translation>Добавить импортированные сайты к существующим</translation>
|
||||
</message>
|
||||
@@ -3521,32 +3536,32 @@ Thank you for staying with us!</source>
|
||||
<context>
|
||||
<name>PageSetupWizardViewConfig</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="70"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="72"/>
|
||||
<source>New connection</source>
|
||||
<translation>Новое соединение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="112"/>
|
||||
<source>Collapse content</source>
|
||||
<translation>Свернуть</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="112"/>
|
||||
<source>Show content</source>
|
||||
<translation>Показать</translation>
|
||||
</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>
|
||||
<translation>Включить обфускацию WireGuard. Это может быть полезно, если WireGuard блокируется вашим провайдером.</translation>
|
||||
</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>
|
||||
<translation>Используйте файлы конфигурации только из тех источников, которым вы доверяете. Файлы из общедоступных источников могли быть созданы с целью перехвата ваших личных данных.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="202"/>
|
||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="204"/>
|
||||
<source>Connect</source>
|
||||
<translation>Подключиться</translation>
|
||||
</message>
|
||||
@@ -4950,12 +4965,12 @@ FileZilla или другие SFTP-клиенты, а также смонтир
|
||||
<context>
|
||||
<name>SettingsController</name>
|
||||
<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>
|
||||
<translation>Все настройки сброшены до значений по умолчанию</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="227"/>
|
||||
<location filename="../ui/controllers/settingsController.cpp" line="235"/>
|
||||
<source>Backup file is corrupted</source>
|
||||
<translation>Файл резервной копии поврежден</translation>
|
||||
</message>
|
||||
|
||||
@@ -29,7 +29,6 @@ namespace
|
||||
constexpr char uuid[] = "installation_uuid";
|
||||
constexpr char osVersion[] = "os_version";
|
||||
constexpr char appVersion[] = "app_version";
|
||||
constexpr char appLanguage[] = "app_language";
|
||||
|
||||
constexpr char userCountryCode[] = "user_country_code";
|
||||
constexpr char serverCountryCode[] = "server_country_code";
|
||||
@@ -65,6 +64,7 @@ namespace
|
||||
{
|
||||
QString osVersion;
|
||||
QString appVersion;
|
||||
QString appLanguage;
|
||||
|
||||
QString installationUuid;
|
||||
|
||||
@@ -84,6 +84,9 @@ namespace
|
||||
if (!appVersion.isEmpty()) {
|
||||
obj[configKey::appVersion] = appVersion;
|
||||
}
|
||||
if (!appLanguage.isEmpty()) {
|
||||
obj[apiDefs::key::appLanguage] = appLanguage;
|
||||
}
|
||||
if (!installationUuid.isEmpty()) {
|
||||
obj[configKey::uuid] = installationUuid;
|
||||
}
|
||||
@@ -223,6 +226,9 @@ namespace
|
||||
if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) {
|
||||
apiConfig.insert(apiDefs::key::supportedProtocols,
|
||||
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;
|
||||
@@ -285,6 +291,7 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getAppLanguage().name().split("_").first(),
|
||||
m_settings->getInstallationUuid(true),
|
||||
apiConfigObject.value(configKey::userCountryCode).toString(),
|
||||
serverCountryCode,
|
||||
@@ -325,6 +332,7 @@ bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getAppLanguage().name().split("_").first(),
|
||||
m_settings->getInstallationUuid(true),
|
||||
apiConfigObject.value(configKey::userCountryCode).toString(),
|
||||
serverCountryCode,
|
||||
@@ -375,7 +383,7 @@ bool ApiConfigsController::fillAvailableServices()
|
||||
{
|
||||
QJsonObject apiPayload;
|
||||
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;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
|
||||
@@ -399,6 +407,7 @@ bool ApiConfigsController::importServiceFromGateway()
|
||||
{
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getAppLanguage().name().split("_").first(),
|
||||
m_settings->getInstallationUuid(true),
|
||||
m_apiServicesModel->getCountryCode(),
|
||||
"",
|
||||
@@ -457,6 +466,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getAppLanguage().name().split("_").first(),
|
||||
m_settings->getInstallationUuid(true),
|
||||
apiConfig.value(configKey::userCountryCode).toString(),
|
||||
newCountryCode,
|
||||
@@ -577,6 +587,7 @@ bool ApiConfigsController::deactivateDevice(const bool isRemoveEvent)
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getAppLanguage().name().split("_").first(),
|
||||
m_settings->getInstallationUuid(true),
|
||||
apiConfigObject.value(configKey::userCountryCode).toString(),
|
||||
apiConfigObject.value(configKey::serverCountryCode).toString(),
|
||||
@@ -616,6 +627,7 @@ bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const Q
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getAppLanguage().name().split("_").first(),
|
||||
uuid,
|
||||
apiConfigObject.value(configKey::userCountryCode).toString(),
|
||||
serverCountryCode,
|
||||
|
||||
@@ -19,7 +19,7 @@ ApiNewsController::ApiNewsController(const QSharedPointer<NewsModel> &newsModel,
|
||||
{
|
||||
}
|
||||
|
||||
void ApiNewsController::fetchNews()
|
||||
void ApiNewsController::fetchNews(bool showError)
|
||||
{
|
||||
if (m_serversModel.isNull()) {
|
||||
qWarning() << "ServersModel is null, skip fetchNews";
|
||||
@@ -30,9 +30,9 @@ void ApiNewsController::fetchNews()
|
||||
qDebug() << "No Gateway stacks, skip fetchNews";
|
||||
return;
|
||||
}
|
||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
||||
m_settings->isStrictKillSwitchEnabled());
|
||||
QByteArray responseBody;
|
||||
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(),
|
||||
apiDefs::requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
|
||||
QJsonObject payload;
|
||||
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());
|
||||
|
||||
@@ -44,22 +44,26 @@ void ApiNewsController::fetchNews()
|
||||
payload.insert(configKey::serviceType, stacksJson.value(configKey::serviceType));
|
||||
}
|
||||
|
||||
ErrorCode errorCode = gatewayController.post(QString("%1v1/news"), payload, responseBody);
|
||||
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();
|
||||
auto future = gatewayController->postAsync(QString("%1v1/news"), payload);
|
||||
future.then(this, [this, showError, gatewayController](QPair<ErrorCode, QByteArray> result) {
|
||||
auto [errorCode, responseBody] = result;
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit errorOccurred(errorCode, showError);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,10 +19,11 @@ public:
|
||||
explicit ApiNewsController(const QSharedPointer<NewsModel> &newsModel, const std::shared_ptr<Settings> &settings,
|
||||
const QSharedPointer<ServersModel> &serversModel, QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void fetchNews();
|
||||
Q_INVOKABLE void fetchNews(bool showError);
|
||||
|
||||
signals:
|
||||
void errorOccurred(ErrorCode errorCode);
|
||||
void errorOccurred(ErrorCode errorCode, bool showError);
|
||||
void fetchNewsFinished();
|
||||
|
||||
private:
|
||||
QSharedPointer<NewsModel> m_newsModel;
|
||||
|
||||
@@ -82,7 +82,7 @@ void ApiPremV1MigrationController::sendMigrationCode(const int subscriptionIndex
|
||||
{
|
||||
QEventLoop wait;
|
||||
QTimer::singleShot(1000, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
||||
m_settings->isStrictKillSwitchEnabled());
|
||||
|
||||
@@ -46,7 +46,7 @@ bool ApiSettingsController::getAccountInfo(bool reload)
|
||||
if (reload) {
|
||||
QEventLoop wait;
|
||||
QTimer::singleShot(1000, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
|
||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), requestTimeoutMsecs,
|
||||
@@ -62,6 +62,7 @@ bool ApiSettingsController::getAccountInfo(bool reload)
|
||||
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
|
||||
apiPayload[configKey::authData] = authData;
|
||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
|
||||
|
||||
QByteArray responseBody;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "settingsController.h"
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QOperatingSystemVersion>
|
||||
|
||||
#include "logger.h"
|
||||
#include "systemController.h"
|
||||
@@ -33,6 +34,17 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
|
||||
checkIfNeedDisableLogs();
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsController::onNotificationStateChanged);
|
||||
connect(AndroidController::instance(), &AndroidController::imeInsetsChanged, this, [this](int heightDp) {
|
||||
m_imeHeight = heightDp;
|
||||
emit imeHeightChanged(heightDp);
|
||||
emit safeAreaBottomMarginChanged();
|
||||
});
|
||||
connect(AndroidController::instance(), &AndroidController::systemBarsInsetsChanged, this, [this](int navBarHeightDp, int statusBarHeightDp) {
|
||||
m_cachedNavigationBarHeight = navBarHeightDp;
|
||||
m_cachedStatusBarHeight = statusBarHeightDp;
|
||||
emit safeAreaBottomMarginChanged();
|
||||
emit safeAreaTopMarginChanged();
|
||||
});
|
||||
#endif
|
||||
|
||||
m_isDevModeEnabled = m_settings->isDevGatewayEnv();
|
||||
@@ -431,6 +443,76 @@ bool SettingsController::isOnTv()
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SettingsController::isEdgeToEdgeEnabled()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (!m_edgeToEdgeCached) {
|
||||
m_cachedEdgeToEdgeEnabled = AndroidController::instance()->isEdgeToEdgeEnabled();
|
||||
m_edgeToEdgeCached = true;
|
||||
}
|
||||
return m_cachedEdgeToEdgeEnabled;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
int SettingsController::getStatusBarHeight()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (m_cachedStatusBarHeight < 0) {
|
||||
m_cachedStatusBarHeight = AndroidController::instance()->getStatusBarHeight();
|
||||
}
|
||||
return m_cachedStatusBarHeight;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int SettingsController::getNavigationBarHeight()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (m_cachedNavigationBarHeight < 0) {
|
||||
m_cachedNavigationBarHeight = AndroidController::instance()->getNavigationBarHeight();
|
||||
}
|
||||
return m_cachedNavigationBarHeight;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int SettingsController::getSafeAreaTopMargin()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (isEdgeToEdgeEnabled()) {
|
||||
int height = getStatusBarHeight();
|
||||
int result = height > 0 ? height : 40; // fallback to 40 if system returns 0
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SettingsController::getSafeAreaBottomMargin()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (isEdgeToEdgeEnabled()) {
|
||||
if (m_imeHeight > 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int height = getNavigationBarHeight();
|
||||
int result = height > 0 ? height : 56; // fallback to 56 if system returns 0
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SettingsController::getImeHeight()
|
||||
{
|
||||
return m_imeHeight;
|
||||
}
|
||||
|
||||
bool SettingsController::isHomeAdLabelVisible()
|
||||
{
|
||||
return m_settings->isHomeAdLabelVisible();
|
||||
|
||||
@@ -33,6 +33,9 @@ public:
|
||||
|
||||
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
|
||||
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
|
||||
Q_PROPERTY(int safeAreaTopMargin READ getSafeAreaTopMargin NOTIFY safeAreaTopMarginChanged)
|
||||
Q_PROPERTY(int safeAreaBottomMargin READ getSafeAreaBottomMargin NOTIFY safeAreaBottomMarginChanged)
|
||||
Q_PROPERTY(int imeHeight READ getImeHeight NOTIFY imeHeightChanged)
|
||||
|
||||
public slots:
|
||||
void toggleAmneziaDns(bool enable);
|
||||
@@ -96,6 +99,12 @@ public slots:
|
||||
void toggleDevGatewayEnv(bool enabled);
|
||||
|
||||
bool isOnTv();
|
||||
bool isEdgeToEdgeEnabled();
|
||||
int getStatusBarHeight();
|
||||
int getNavigationBarHeight();
|
||||
int getSafeAreaTopMargin();
|
||||
int getSafeAreaBottomMargin();
|
||||
int getImeHeight();
|
||||
|
||||
bool isHomeAdLabelVisible();
|
||||
void disableHomeAdLabel();
|
||||
@@ -124,6 +133,10 @@ signals:
|
||||
void devModeEnabled();
|
||||
void gatewayEndpointChanged(const QString &endpoint);
|
||||
void devGatewayEnvChanged(bool enabled);
|
||||
|
||||
void imeHeightChanged(int height);
|
||||
void safeAreaTopMarginChanged();
|
||||
void safeAreaBottomMarginChanged();
|
||||
|
||||
void isHomeAdLabelVisibleChanged(bool visible);
|
||||
void startMinimizedChanged();
|
||||
@@ -134,6 +147,12 @@ private:
|
||||
QSharedPointer<LanguageModel> m_languageModel;
|
||||
QSharedPointer<SitesModel> m_sitesModel;
|
||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||
|
||||
mutable int m_cachedStatusBarHeight = -1;
|
||||
mutable int m_cachedNavigationBarHeight = -1;
|
||||
mutable bool m_cachedEdgeToEdgeEnabled = false;
|
||||
mutable bool m_edgeToEdgeCached = false;
|
||||
int m_imeHeight = 0;
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
|
||||
QString m_appVersion;
|
||||
|
||||
@@ -31,7 +31,8 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
||||
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: {
|
||||
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);
|
||||
}
|
||||
case ServiceDescriptionRole: {
|
||||
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) {
|
||||
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 "";
|
||||
}
|
||||
return m_accountInfoData.subscriptionDescription;
|
||||
}
|
||||
case IsComponentVisibleRole: {
|
||||
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2
|
||||
@@ -101,6 +93,8 @@ void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, cons
|
||||
|
||||
accountInfoData.configType = apiUtils::getConfigType(serverConfig);
|
||||
|
||||
accountInfoData.subscriptionDescription = accountInfoObject.value(apiDefs::key::subscriptionDescription).toString();
|
||||
|
||||
for (const auto &protocol : accountInfoObject.value(apiDefs::key::supportedProtocols).toArray()) {
|
||||
accountInfoData.supportedProtocols.push_back(protocol.toString());
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ private:
|
||||
apiDefs::ConfigType configType;
|
||||
|
||||
QStringList supportedProtocols;
|
||||
|
||||
QString subscriptionDescription;
|
||||
};
|
||||
|
||||
AccountInfoData m_accountInfoData;
|
||||
|
||||
@@ -158,6 +158,18 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
|
||||
QString primaryDns = server.value(config_key::dns1).toString();
|
||||
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();
|
||||
@@ -403,6 +415,12 @@ QHash<int, QByteArray> ServersModel::roleNames() const
|
||||
roles[IsCountrySelectionAvailableRole] = "isCountrySelectionAvailable";
|
||||
roles[ApiAvailableCountriesRole] = "apiAvailableCountries";
|
||||
roles[ApiServerCountryCodeRole] = "apiServerCountryCode";
|
||||
|
||||
roles[IsAdVisibleRole] = "isAdVisible";
|
||||
roles[AdHeaderRole] = "adHeader";
|
||||
roles[AdDescriptionRole] = "adDescription";
|
||||
roles[AdEndpointRole] = "adEndpoint";
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -784,22 +802,22 @@ void ServersModel::recomputeGatewayStacks()
|
||||
const bool wasEmpty = m_gatewayStacks.isEmpty();
|
||||
GatewayStacks computed;
|
||||
bool hasNewTags = false;
|
||||
|
||||
|
||||
for (int i = 0; i < m_servers.count(); ++i) {
|
||||
if (data(i, IsServerFromGatewayApiRole).toBool()) {
|
||||
const QJsonObject server = m_servers.at(i).toObject();
|
||||
const QJsonObject apiConfig = server.value(configKey::apiConfig).toObject();
|
||||
|
||||
|
||||
const QString userCountryCode = apiConfig.value(configKey::userCountryCode).toString();
|
||||
const QString serviceType = apiConfig.value(configKey::serviceType).toString();
|
||||
|
||||
|
||||
if (!userCountryCode.isEmpty()) {
|
||||
if (!m_gatewayStacks.userCountryCodes.contains(userCountryCode)) {
|
||||
hasNewTags = true;
|
||||
}
|
||||
computed.userCountryCodes.insert(userCountryCode);
|
||||
}
|
||||
|
||||
|
||||
if (!serviceType.isEmpty()) {
|
||||
if (!m_gatewayStacks.serviceTypes.contains(serviceType)) {
|
||||
hasNewTags = true;
|
||||
@@ -808,12 +826,12 @@ void ServersModel::recomputeGatewayStacks()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
m_gatewayStacks = std::move(computed);
|
||||
if (hasNewTags) {
|
||||
emit gatewayStacksExpanded();
|
||||
}
|
||||
|
||||
|
||||
if (wasEmpty != m_gatewayStacks.isEmpty()) {
|
||||
emit hasServersFromGatewayApiChanged();
|
||||
}
|
||||
@@ -885,3 +903,18 @@ bool ServersModel::processedServerIsPremium() const
|
||||
{
|
||||
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,
|
||||
ApiAvailableCountriesRole,
|
||||
ApiServerCountryCodeRole,
|
||||
IsAdVisibleRole,
|
||||
AdHeaderRole,
|
||||
AdDescriptionRole,
|
||||
AdEndpointRole,
|
||||
|
||||
HasAmneziaDns
|
||||
};
|
||||
@@ -79,6 +83,10 @@ public:
|
||||
Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged)
|
||||
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;
|
||||
|
||||
public slots:
|
||||
@@ -144,6 +152,10 @@ public slots:
|
||||
bool isApiKeyExpired(const int serverIndex);
|
||||
void removeApiConfig(const int serverIndex);
|
||||
|
||||
bool isAdVisible();
|
||||
QString adHeader();
|
||||
QString adDescription();
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Shapes
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import Style 1.0
|
||||
|
||||
@@ -13,61 +12,139 @@ import "../Controls2/TextTypes"
|
||||
Rectangle {
|
||||
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.color: AmneziaStyle.color.goldenApricot
|
||||
color: AmneziaStyle.color.transparent
|
||||
border.color: AmneziaStyle.color.onyxBlack
|
||||
radius: 13
|
||||
|
||||
visible: false
|
||||
// visible: GC.isDesktop() && ServersModel.isDefaultServerFromApi
|
||||
// && ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && SettingsController.isHomeAdLabelVisible
|
||||
visible: ServersModel.isAdVisible
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
Keys.onTabPressed: {
|
||||
FocusController.nextKeyTabItem()
|
||||
}
|
||||
|
||||
onClicked: function() {
|
||||
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl("premium"))
|
||||
}
|
||||
Keys.onBacktabPressed: {
|
||||
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 {
|
||||
id: ad
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 12
|
||||
anchors.topMargin: 12
|
||||
anchors.bottomMargin: 12
|
||||
spacing: 20
|
||||
|
||||
Image {
|
||||
source: "qrc:/images/controls/amnezia.svg"
|
||||
sourceSize: Qt.size(36, 36)
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
|
||||
layer {
|
||||
effect: ColorOverlay {
|
||||
color: AmneziaStyle.color.paleGray
|
||||
}
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
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 {
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 10
|
||||
Layout.leftMargin: 10
|
||||
Item {
|
||||
implicitWidth: 40
|
||||
implicitHeight: 40
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
text: qsTr("Amnezia Premium - for access to all websites and online resources")
|
||||
color: AmneziaStyle.color.pearlGray
|
||||
Rectangle {
|
||||
id: chevronBackground
|
||||
anchors.fill: parent
|
||||
radius: 12
|
||||
color: AmneziaStyle.color.transparent
|
||||
border.width: root.activeFocus ? 1 : 0
|
||||
border.color: AmneziaStyle.color.paleGray
|
||||
|
||||
lineHeight: 18
|
||||
font.pixelSize: 15
|
||||
}
|
||||
Behavior on color {
|
||||
PropertyAnimation { duration: 200 }
|
||||
}
|
||||
|
||||
ImageButtonType {
|
||||
image: "qrc:/images/controls/close.svg"
|
||||
imageColor: AmneziaStyle.color.paleGray
|
||||
Behavior on border.width {
|
||||
PropertyAnimation { duration: 200 }
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: function() {
|
||||
SettingsController.disableHomeAdLabel()
|
||||
Image {
|
||||
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
|
||||
|
||||
onImplicitHeightChanged: {
|
||||
root.expandedHeight = content.implicitHeight + 32
|
||||
root.expandedHeight = content.implicitHeight + 32 + SettingsController.safeAreaBottomMargin
|
||||
}
|
||||
|
||||
Header2TextType {
|
||||
|
||||
55
client/ui/qml/Components/SmartScroll.qml
Normal file
55
client/ui/qml/Components/SmartScroll.qml
Normal file
@@ -0,0 +1,55 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property var listView: null
|
||||
property var scrollToItemTarget: null
|
||||
|
||||
property Connections imeConnection: Connections {
|
||||
target: SettingsController
|
||||
function onImeHeightChanged() {
|
||||
if (root.scrollToItemTarget && SettingsController.imeHeight > 0) {
|
||||
scrollTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Timer scrollTimer: Timer {
|
||||
interval: 100
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.scrollToItemTarget && root.listView) {
|
||||
if (SettingsController.imeHeight > 0) {
|
||||
var item = root.scrollToItemTarget
|
||||
var itemY = item.mapToItem(root.listView.contentItem, 0, 0).y
|
||||
var itemHeight = item.height
|
||||
var keyboardHeight = SettingsController.imeHeight + SettingsController.safeAreaBottomMargin
|
||||
var visibleHeight = root.listView.height - keyboardHeight
|
||||
|
||||
var desiredTopOffset = visibleHeight * 0.25
|
||||
var targetContentY = itemY - desiredTopOffset
|
||||
|
||||
if (targetContentY < 0) {
|
||||
targetContentY = 0
|
||||
}
|
||||
|
||||
var maxContentY = root.listView.contentHeight - root.listView.height
|
||||
if (targetContentY > maxContentY) {
|
||||
targetContentY = maxContentY
|
||||
}
|
||||
|
||||
root.listView.contentY = targetContentY
|
||||
root.scrollToItemTarget = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToItem(item) {
|
||||
scrollToItemTarget = item
|
||||
scrollTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,22 @@ Item {
|
||||
return drawerContent.state === stateName
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Qt.application
|
||||
|
||||
function onStateChanged() {
|
||||
if (Qt.application.state !== Qt.ApplicationActive) {
|
||||
if (dragArea.drag.active) {
|
||||
dragArea.drag.target = null
|
||||
dragArea.drag.target = drawerContent
|
||||
}
|
||||
if (isOpened && !isCollapsedStateActive()) {
|
||||
root.closeTriggered()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PageController
|
||||
|
||||
|
||||
@@ -74,6 +74,18 @@ Item {
|
||||
FocusController.nextKeyRightItem()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Qt.application
|
||||
|
||||
function onStateChanged() {
|
||||
if (Qt.application.state !== Qt.ApplicationActive) {
|
||||
if (!menu.isClosed) {
|
||||
menu.closeTriggered()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
implicitWidth: rootButtonContent.implicitWidth
|
||||
implicitHeight: rootButtonContent.implicitHeight
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ Popup {
|
||||
|
||||
leftMargin: 25
|
||||
rightMargin: 25
|
||||
bottomMargin: 70
|
||||
bottomMargin: 70 + SettingsController.safeAreaBottomMargin
|
||||
|
||||
width: parent.width - leftMargin - rightMargin
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ Item {
|
||||
|
||||
property string buttonText
|
||||
property string buttonImageSource
|
||||
property string buttonImageColor: AmneziaStyle.color.midnightBlack
|
||||
property string buttonBackgroundColor: AmneziaStyle.color.paleGray
|
||||
property string buttonHoveredColor: AmneziaStyle.color.lightGray
|
||||
property var clickedFunc
|
||||
|
||||
property alias textField: textField
|
||||
@@ -48,7 +51,7 @@ Item {
|
||||
Keys.onUpPressed: {
|
||||
FocusController.nextKeyUpItem()
|
||||
}
|
||||
|
||||
|
||||
Keys.onDownPressed: {
|
||||
FocusController.nextKeyDownItem()
|
||||
}
|
||||
@@ -67,7 +70,7 @@ Item {
|
||||
border.width: 1
|
||||
|
||||
Behavior on border.color {
|
||||
PropertyAnimation { duration: 200 }
|
||||
PropertyAnimation { duration: 100 }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -194,6 +197,14 @@ Item {
|
||||
focusPolicy: Qt.NoFocus
|
||||
text: root.buttonText
|
||||
leftImageSource: root.buttonImageSource
|
||||
leftImageColor: root.buttonImageColor
|
||||
|
||||
defaultColor: root.buttonBackgroundColor
|
||||
hoveredColor: root.buttonHoveredColor
|
||||
pressedColor: root.buttonHoveredColor
|
||||
disabledColor: AmneziaStyle.color.transparent
|
||||
|
||||
borderWidth: 0
|
||||
|
||||
anchors.top: content.top
|
||||
anchors.bottom: content.bottom
|
||||
@@ -201,7 +212,7 @@ Item {
|
||||
|
||||
height: content.implicitHeight
|
||||
width: content.implicitHeight
|
||||
squareLeftSide: true
|
||||
squareLeftSide: false
|
||||
|
||||
clickedFunc: function() {
|
||||
if (root.clickedFunc && typeof root.clickedFunc === "function") {
|
||||
|
||||
@@ -28,5 +28,7 @@ QtObject {
|
||||
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
|
||||
readonly property color pearlGray: '#EAEAEC'
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ PageType {
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 20
|
||||
Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
|
||||
@@ -20,6 +20,21 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
Connections {
|
||||
target: Qt.application
|
||||
|
||||
function onStateChanged() {
|
||||
if (Qt.application.state !== Qt.ApplicationActive) {
|
||||
if (drawer.isOpened) {
|
||||
drawer.closeTriggered()
|
||||
}
|
||||
if (homeSplitTunnelingDrawer.isOpened) {
|
||||
homeSplitTunnelingDrawer.closeTriggered()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
objectName: "pageControllerConnections"
|
||||
|
||||
@@ -68,19 +83,9 @@ PageType {
|
||||
objectName: "homeColumnLayout"
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 12
|
||||
anchors.topMargin: 12 + SettingsController.safeAreaTopMargin
|
||||
anchors.bottomMargin: 16
|
||||
|
||||
AdLabel {
|
||||
id: adLabel
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: adLabel.contentHeight
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 22
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: loggingButton
|
||||
objectName: "loggingButton"
|
||||
@@ -189,6 +194,16 @@ PageType {
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
|
||||
AdLabel {
|
||||
id: adLabel
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: adLabel.contentHeight
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 22
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
@@ -31,6 +31,11 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
SmartScroll {
|
||||
id: smartScroll
|
||||
listView: listView
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
@@ -80,6 +85,13 @@ PageType {
|
||||
clientMtu = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(mtuTextField)
|
||||
}
|
||||
}
|
||||
|
||||
checkEmptyText: true
|
||||
}
|
||||
|
||||
@@ -97,6 +109,12 @@ PageType {
|
||||
clientJunkPacketCount = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(junkPacketCountTextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -113,6 +131,12 @@ PageType {
|
||||
clientJunkPacketMinSize = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(junkPacketMinSizeTextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -129,6 +153,12 @@ PageType {
|
||||
clientJunkPacketMaxSize = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(junkPacketMaxSizeTextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -147,6 +177,12 @@ PageType {
|
||||
clientSpecialJunk1 = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(specialJunk1TextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -165,6 +201,12 @@ PageType {
|
||||
clientSpecialJunk2 = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(specialJunk2TextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -183,6 +225,12 @@ PageType {
|
||||
clientSpecialJunk3 = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(specialJunk3TextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -201,6 +249,12 @@ PageType {
|
||||
clientSpecialJunk4 = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(specialJunk4TextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -219,6 +273,12 @@ PageType {
|
||||
clientSpecialJunk5 = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(specialJunk5TextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -237,6 +297,12 @@ PageType {
|
||||
clientControlledJunk1 = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(controlledJunk1TextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -255,6 +321,12 @@ PageType {
|
||||
clientControlledJunk2 = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(controlledJunk2TextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -273,6 +345,12 @@ PageType {
|
||||
clientControlledJunk3 = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(controlledJunk3TextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -290,6 +368,12 @@ PageType {
|
||||
clientSpecialHandshakeTimeout = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(iTimeTextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Header2TextType {
|
||||
|
||||
@@ -25,7 +25,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
@@ -34,6 +34,11 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
SmartScroll {
|
||||
id: smartScroll
|
||||
listView: listView
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
@@ -81,6 +86,12 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(vpnAddressSubnetTextField)
|
||||
}
|
||||
}
|
||||
|
||||
checkEmptyText: true
|
||||
}
|
||||
|
||||
@@ -104,6 +115,12 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(portTextField)
|
||||
}
|
||||
}
|
||||
|
||||
checkEmptyText: true
|
||||
}
|
||||
|
||||
@@ -121,6 +138,12 @@ PageType {
|
||||
serverJunkPacketCount = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(junkPacketCountTextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -137,6 +160,12 @@ PageType {
|
||||
serverJunkPacketMinSize = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(junkPacketMinSizeTextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -153,6 +182,12 @@ PageType {
|
||||
serverJunkPacketMaxSize = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(junkPacketMaxSizeTextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -169,6 +204,12 @@ PageType {
|
||||
serverInitPacketJunkSize = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(initPacketJunkSizeTextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -185,6 +226,12 @@ PageType {
|
||||
serverResponsePacketJunkSize = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(responsePacketJunkSizeTextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AwgTextField {
|
||||
@@ -233,6 +280,12 @@ PageType {
|
||||
serverInitPacketMagicHeader = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(initPacketMagicHeaderTextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -249,6 +302,12 @@ PageType {
|
||||
serverResponsePacketMagicHeader = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(responsePacketMagicHeaderTextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -265,6 +324,12 @@ PageType {
|
||||
serverUnderloadPacketMagicHeader = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(underloadPacketMagicHeaderTextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AwgTextField {
|
||||
@@ -281,6 +346,12 @@ PageType {
|
||||
serverTransportPacketMagicHeader = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus) {
|
||||
smartScroll.scrollToItem(transportPacketMagicHeaderTextField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
|
||||
@@ -22,7 +22,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
|
||||
@@ -23,7 +23,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
|
||||
@@ -25,7 +25,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -22,7 +22,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -22,7 +22,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -22,7 +22,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -23,7 +23,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -22,7 +22,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -30,7 +30,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -31,7 +31,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -31,7 +31,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -14,6 +14,21 @@ import "../Config"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
Connections {
|
||||
target: ApiNewsController
|
||||
function onFetchNewsFinished() {
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
|
||||
function onErrorOccurred(errorCode, showError) {
|
||||
if (showError) {
|
||||
PageController.showErrorMessage(errorCode)
|
||||
PageController.closePage()
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
@@ -25,7 +40,7 @@ PageType {
|
||||
BaseHeaderType {
|
||||
id: header
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.topMargin: 24 + SettingsController.safeAreaTopMargin
|
||||
Layout.bottomMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
@@ -140,9 +155,8 @@ PageType {
|
||||
return;
|
||||
}
|
||||
PageController.showBusyIndicator(true)
|
||||
ApiNewsController.fetchNews();
|
||||
ApiNewsController.fetchNews(true)
|
||||
PageController.goToPage(PageEnum.PageSettingsNewsNotifications)
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
|
||||
@@ -66,7 +66,7 @@ PageType {
|
||||
id: backButton
|
||||
objectName: "backButton"
|
||||
|
||||
Layout.topMargin: 20
|
||||
Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
}
|
||||
|
||||
HeaderTypeWithButton {
|
||||
|
||||
@@ -23,7 +23,7 @@ PageType {
|
||||
id: listView
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
anchors.bottomMargin: 24
|
||||
|
||||
model: ApiDevicesModel
|
||||
|
||||
@@ -79,7 +79,7 @@ PageType {
|
||||
id: listView
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
anchors.bottomMargin: 24
|
||||
|
||||
model: instructionsModel
|
||||
|
||||
@@ -28,7 +28,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
@@ -71,7 +71,7 @@ PageType {
|
||||
|
||||
text: countryName
|
||||
descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : ""
|
||||
hideDescription: isWorkerExpired ? true : false
|
||||
hideDescription: isWorkerExpired ? false : true
|
||||
descriptionColor: AmneziaStyle.color.vibrantRed
|
||||
|
||||
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
|
||||
|
||||
@@ -93,7 +93,7 @@ PageType {
|
||||
id: backButton
|
||||
objectName: "backButton"
|
||||
|
||||
Layout.topMargin: 20
|
||||
Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
}
|
||||
|
||||
HeaderTypeWithButton {
|
||||
|
||||
@@ -61,7 +61,7 @@ PageType {
|
||||
width: root.width
|
||||
|
||||
BackButtonType {
|
||||
Layout.topMargin: 20
|
||||
Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
}
|
||||
|
||||
Label {
|
||||
|
||||
@@ -59,7 +59,7 @@ PageType {
|
||||
id: listView
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
anchors.bottomMargin: 24
|
||||
|
||||
model: supportModel
|
||||
|
||||
@@ -74,7 +74,7 @@ PageType {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
@@ -165,9 +165,11 @@ PageType {
|
||||
ScrollBar.vertical: ScrollBarType { policy: ScrollBar.AlwaysOn }
|
||||
|
||||
anchors.top: header.bottom
|
||||
anchors.bottom: addAppButton.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: addAppButton.implicitHeight + 48 + SettingsController.safeAreaBottomMargin + (searchField.textField.activeFocus ? 0 : SettingsController.imeHeight)
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
clip: true
|
||||
|
||||
model: SortFilterProxyModel {
|
||||
id: proxyAppSplitTunnelingModel
|
||||
@@ -215,50 +217,54 @@ PageType {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: addAppButton
|
||||
anchors.bottomMargin: -24
|
||||
color: AmneziaStyle.color.midnightBlack
|
||||
opacity: 0.8
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: addAppButton
|
||||
|
||||
enabled: root.pageEnabled
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 24
|
||||
anchors.rightMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
anchors.bottomMargin: 24
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
height: addAppButton.implicitHeight + 48 + SettingsController.safeAreaBottomMargin
|
||||
|
||||
color: AmneziaStyle.color.midnightBlack
|
||||
opacity: 0.8
|
||||
|
||||
RowLayout {
|
||||
id: addAppButton
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: searchField
|
||||
enabled: root.pageEnabled
|
||||
|
||||
Layout.fillWidth: true
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 24
|
||||
anchors.rightMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
anchors.bottomMargin: 24 + SettingsController.safeAreaBottomMargin
|
||||
|
||||
textField.placeholderText: qsTr("application name")
|
||||
buttonImageSource: "qrc:/images/controls/plus.svg"
|
||||
TextFieldWithHeaderType {
|
||||
id: searchField
|
||||
|
||||
rightButtonClickedOnEnter: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
clickedFunc: function() {
|
||||
searchField.focus = false
|
||||
PageController.showBusyIndicator(true)
|
||||
textField.placeholderText: qsTr("application name")
|
||||
buttonImageSource: "qrc:/images/controls/plus.svg"
|
||||
|
||||
if (Qt.platform.os === "windows") {
|
||||
var fileName = SystemController.getFileName(qsTr("Open executable file"),
|
||||
qsTr("Executable files (*.*)"))
|
||||
if (fileName !== "") {
|
||||
AppSplitTunnelingController.addApp(fileName)
|
||||
rightButtonClickedOnEnter: true
|
||||
|
||||
clickedFunc: function() {
|
||||
searchField.focus = false
|
||||
PageController.showBusyIndicator(true)
|
||||
|
||||
if (Qt.platform.os === "windows") {
|
||||
var fileName = SystemController.getFileName(qsTr("Open executable file"),
|
||||
qsTr("Executable files (*.*)"))
|
||||
if (fileName !== "") {
|
||||
AppSplitTunnelingController.addApp(fileName)
|
||||
}
|
||||
} else if (Qt.platform.os === "android"){
|
||||
installedAppDrawer.openTriggered()
|
||||
}
|
||||
} else if (Qt.platform.os === "android"){
|
||||
installedAppDrawer.openTriggered()
|
||||
}
|
||||
|
||||
PageController.showBusyIndicator(false)
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
|
||||
@@ -40,7 +40,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
|
||||
@@ -20,7 +20,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
|
||||
@@ -20,7 +20,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -17,7 +17,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
|
||||
@@ -30,7 +30,7 @@ PageType {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
@@ -22,7 +22,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -34,7 +34,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
|
||||
@@ -19,7 +19,7 @@ PageType {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
@@ -62,7 +62,7 @@ PageType {
|
||||
objectName: "mainLayout"
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
spacing: 4
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -25,7 +25,7 @@ PageType {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
@@ -88,7 +88,7 @@ PageType {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
@@ -168,11 +168,13 @@ PageType {
|
||||
|
||||
anchors.top: header.bottom
|
||||
anchors.topMargin: 16
|
||||
anchors.bottom: addSiteButton.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: addSiteButton.implicitHeight + 48 + (searchField.textField.activeFocus ? 0 : SettingsController.imeHeight)
|
||||
|
||||
width: parent.width
|
||||
|
||||
enabled: root.pageEnabled
|
||||
clip: true
|
||||
|
||||
model: SortFilterProxyModel {
|
||||
id: proxySitesModel
|
||||
@@ -231,56 +233,60 @@ PageType {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: addSiteButton
|
||||
anchors.bottomMargin: -24
|
||||
color: AmneziaStyle.color.midnightBlack
|
||||
opacity: 0.8
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: addSiteButton
|
||||
|
||||
enabled: root.pageEnabled
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 24
|
||||
anchors.rightMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
anchors.bottomMargin: 24
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
height: addSiteButton.implicitHeight + 48
|
||||
|
||||
color: AmneziaStyle.color.midnightBlack
|
||||
opacity: 0.8
|
||||
|
||||
RowLayout {
|
||||
id: addSiteButton
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: searchField
|
||||
enabled: root.pageEnabled
|
||||
|
||||
Layout.fillWidth: true
|
||||
rightButtonClickedOnEnter: true
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 24
|
||||
anchors.rightMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
anchors.bottomMargin: 24
|
||||
|
||||
textField.placeholderText: qsTr("website or IP")
|
||||
buttonImageSource: "qrc:/images/controls/plus.svg"
|
||||
TextFieldWithHeaderType {
|
||||
id: searchField
|
||||
|
||||
clickedFunc: function() {
|
||||
PageController.showBusyIndicator(true)
|
||||
SitesController.addSite(textField.text)
|
||||
textField.text = ""
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
rightButtonClickedOnEnter: true
|
||||
|
||||
ImageButtonType {
|
||||
id: addSiteButtonImage
|
||||
implicitWidth: 56
|
||||
implicitHeight: 56
|
||||
textField.placeholderText: qsTr("website or IP")
|
||||
buttonImageSource: "qrc:/images/controls/plus.svg"
|
||||
|
||||
image: "qrc:/images/controls/more-vertical.svg"
|
||||
imageColor: AmneziaStyle.color.paleGray
|
||||
|
||||
onClicked: function () {
|
||||
moreActionsDrawer.openTriggered()
|
||||
clickedFunc: function() {
|
||||
PageController.showBusyIndicator(true)
|
||||
SitesController.addSite(textField.text)
|
||||
textField.text = ""
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: addSiteButtonImage.clicked()
|
||||
Keys.onEnterPressed: addSiteButtonImage.clicked()
|
||||
ImageButtonType {
|
||||
id: addSiteButtonImage
|
||||
implicitWidth: 56
|
||||
implicitHeight: 56
|
||||
|
||||
image: "qrc:/images/controls/more-vertical.svg"
|
||||
imageColor: AmneziaStyle.color.paleGray
|
||||
|
||||
onClicked: function () {
|
||||
moreActionsDrawer.openTriggered()
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: addSiteButtonImage.clicked()
|
||||
Keys.onEnterPressed: addSiteButtonImage.clicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -22,7 +22,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
Layout.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
|
||||
@@ -41,7 +41,7 @@ PageType {
|
||||
property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible()
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.topMargin: 24 + SettingsController.safeAreaTopMargin
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
@@ -71,6 +71,7 @@ PageType {
|
||||
|
||||
clickedFunc: function () {
|
||||
clickedHandler()
|
||||
buttonImageSource = textField.text !== "" ? imageSource : ""
|
||||
}
|
||||
|
||||
textField.onFocusChanged: {
|
||||
@@ -78,8 +79,8 @@ PageType {
|
||||
}
|
||||
|
||||
textField.onTextChanged: {
|
||||
if (hideContent) {
|
||||
buttonImageSource = textField.text !== "" ? (hideContent ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : ""
|
||||
if (headerText == qsTr("Password or SSH private key")) {
|
||||
buttonImageSource = textField.text !== "" ? imageSource : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,8 +212,10 @@ PageType {
|
||||
property string title: qsTr("Password or SSH private key")
|
||||
readonly property string placeholderContent: ""
|
||||
property bool hideContent: true
|
||||
property string imageSource: "qrc:/images/controls/eye.svg"
|
||||
readonly property var clickedHandler: function() {
|
||||
hideContent = !hideContent
|
||||
imageSource = hideContent ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
|
||||
@@ -103,7 +103,7 @@ PageType {
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 20
|
||||
Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
@@ -147,6 +147,7 @@ PageType {
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 24 + SettingsController.safeAreaBottomMargin
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -41,7 +41,7 @@ PageType {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
|
||||
@@ -20,7 +20,7 @@ PageType {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
|
||||
@@ -25,7 +25,7 @@ PageType {
|
||||
source: "qrc:/images/amneziaBigLogo.png"
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
Layout.topMargin: 32
|
||||
Layout.topMargin: 32 + SettingsController.safeAreaTopMargin
|
||||
Layout.preferredWidth: 360
|
||||
Layout.preferredHeight: 287
|
||||
}
|
||||
@@ -33,7 +33,7 @@ PageType {
|
||||
BasicButtonType {
|
||||
id: startButton
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 48
|
||||
Layout.bottomMargin: 48 + SettingsController.safeAreaBottomMargin
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
@@ -19,7 +19,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
|
||||
@@ -22,7 +22,7 @@ PageType {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
|
||||
@@ -173,7 +173,7 @@ PageType {
|
||||
HeaderTypeWithButton {
|
||||
id: header
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.topMargin: 24 + SettingsController.safeAreaTopMargin
|
||||
|
||||
headerText: qsTr("Share VPN Access")
|
||||
|
||||
@@ -311,7 +311,7 @@ PageType {
|
||||
DropDownType {
|
||||
id: serverSelector
|
||||
|
||||
signal severSelectorIndexChanged
|
||||
signal serverSelectorIndexChanged
|
||||
property int currentIndex: -1
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -348,7 +348,7 @@ PageType {
|
||||
|
||||
if (serverSelector.currentIndex !== serverSelectorListView.selectedIndex) {
|
||||
serverSelector.currentIndex = serverSelectorListView.selectedIndex
|
||||
serverSelector.severSelectorIndexChanged()
|
||||
serverSelector.serverSelectorIndexChanged()
|
||||
}
|
||||
|
||||
serverSelector.closeTriggered()
|
||||
@@ -375,6 +375,8 @@ PageType {
|
||||
DropDownType {
|
||||
id: protocolSelector
|
||||
|
||||
signal protocolSelectorTextChanged
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
@@ -414,7 +416,7 @@ PageType {
|
||||
Connections {
|
||||
target: serverSelector
|
||||
|
||||
function onSeverSelectorIndexChanged() {
|
||||
function onServerSelectorIndexChanged() {
|
||||
var defaultContainer = proxyContainersModel.mapFromSource(ServersModel.getProcessedServerData("defaultContainer"))
|
||||
protocolSelectorListView.selectedIndex = defaultContainer
|
||||
protocolSelectorListView.positionViewAtIndex(selectedIndex, ListView.Beginning)
|
||||
@@ -436,17 +438,14 @@ PageType {
|
||||
|
||||
fillConnectionTypeModel()
|
||||
|
||||
if (exportTypeSelector.currentIndex >= root.connectionTypesModel.length) {
|
||||
exportTypeSelector.currentIndex = 0
|
||||
exportTypeSelector.text = root.connectionTypesModel[0].name
|
||||
}
|
||||
|
||||
if (accessTypeSelector.currentIndex === 1) {
|
||||
PageController.showBusyIndicator(true)
|
||||
ExportController.updateClientManagementModel(ContainersModel.getProcessedContainerIndex(),
|
||||
ServersModel.getProcessedServerCredentials())
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
|
||||
protocolSelector.protocolSelectorTextChanged()
|
||||
}
|
||||
|
||||
function fillConnectionTypeModel() {
|
||||
@@ -499,6 +498,14 @@ PageType {
|
||||
exportTypeSelector.text = exportTypeSelectorListView.selectedText
|
||||
}
|
||||
|
||||
onModelChanged: {
|
||||
if (exportTypeSelector.currentIndex >= model.length || exportTypeSelector.currentIndex < 0) {
|
||||
exportTypeSelector.currentIndex = 0
|
||||
}
|
||||
selectedIndex = exportTypeSelector.currentIndex
|
||||
exportTypeSelector.text = selectedText
|
||||
}
|
||||
|
||||
rootWidth: root.width
|
||||
|
||||
imageSource: "qrc:/images/controls/check.svg"
|
||||
@@ -506,16 +513,23 @@ PageType {
|
||||
model: root.connectionTypesModel
|
||||
currentIndex: 0
|
||||
|
||||
Connections {
|
||||
target: protocolSelector
|
||||
|
||||
function onProtocolSelectorTextChanged() {
|
||||
if (exportTypeSelector.currentIndex >= root.connectionTypesModel.length) {
|
||||
exportTypeSelectorListView.selectedIndex = 0
|
||||
exportTypeSelector.currentIndex = 0
|
||||
exportTypeSelector.text = root.connectionTypesModel[0].name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clickedFunction: function() {
|
||||
exportTypeSelector.text = exportTypeSelectorListView.selectedText
|
||||
exportTypeSelector.currentIndex = exportTypeSelectorListView.selectedIndex
|
||||
exportTypeSelector.closeTriggered()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
exportTypeSelector.text = exportTypeSelectorListView.selectedText
|
||||
exportTypeSelector.currentIndex = exportTypeSelectorListView.selectedIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,6 +580,7 @@ PageType {
|
||||
textField.placeholderText: qsTr("Search")
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
searchTextField.textField.text = ""
|
||||
root.isSearchBarVisible = false
|
||||
}
|
||||
|
||||
@@ -586,6 +601,7 @@ PageType {
|
||||
imageColor: AmneziaStyle.color.paleGray
|
||||
|
||||
function clickedFunc() {
|
||||
searchTextField.textField.text = ""
|
||||
root.isSearchBarVisible = false
|
||||
}
|
||||
|
||||
@@ -602,6 +618,8 @@ PageType {
|
||||
|
||||
visible: accessTypeSelector.currentIndex === 1
|
||||
|
||||
function escapeRe(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') }
|
||||
|
||||
property bool isFocusable: true
|
||||
property bool freezeFilter: false
|
||||
|
||||
@@ -611,7 +629,7 @@ PageType {
|
||||
filters: RegExpFilter {
|
||||
roleName: "clientName"
|
||||
enabled: !clientsListView.freezeFilter
|
||||
pattern: ".*" + searchTextField.textField.text + ".*"
|
||||
pattern: ".*" + clientsListView.escapeRe(searchTextField.textField.text) + ".*"
|
||||
caseSensitivity: Qt.CaseInsensitive
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user