Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into HEAD

This commit is contained in:
vladimir.kuznetsov 2025-01-08 14:41:02 +07:00
commit b717560047
134 changed files with 4331 additions and 3809 deletions

View file

@ -335,7 +335,8 @@ jobs:
arch: 'linux_gcc_64' arch: 'linux_gcc_64'
modules: ${{ env.QT_MODULES }} modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
extra: '--external 7z --base ${{ env.QT_MIRROR }}' py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_x86_64 Qt' - name: 'Install android_x86_64 Qt'
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
@ -346,7 +347,8 @@ jobs:
arch: 'android_x86_64' arch: 'android_x86_64'
modules: ${{ env.QT_MODULES }} modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
extra: '--external 7z --base ${{ env.QT_MIRROR }}' py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_x86 Qt' - name: 'Install android_x86 Qt'
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
@ -357,7 +359,8 @@ jobs:
arch: 'android_x86' arch: 'android_x86'
modules: ${{ env.QT_MODULES }} modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
extra: '--external 7z --base ${{ env.QT_MIRROR }}' py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_armv7 Qt' - name: 'Install android_armv7 Qt'
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
@ -368,7 +371,8 @@ jobs:
arch: 'android_armv7' arch: 'android_armv7'
modules: ${{ env.QT_MODULES }} modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
extra: '--external 7z --base ${{ env.QT_MIRROR }}' py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_arm64_v8a Qt' - name: 'Install android_arm64_v8a Qt'
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
@ -379,7 +383,8 @@ jobs:
arch: 'android_arm64_v8a' arch: 'android_arm64_v8a'
modules: ${{ env.QT_MODULES }} modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
extra: '--external 7z --base ${{ env.QT_MIRROR }}' py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Grant execute permission for qt-cmake' - name: 'Grant execute permission for qt-cmake'
shell: bash shell: bash

View file

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
project(${PROJECT} VERSION 4.8.2.4 project(${PROJECT} VERSION 4.8.3.0
DESCRIPTION "AmneziaVPN" DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/" HOMEPAGE_URL "https://amnezia.org/"
) )
@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}") set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2071) set(APP_ANDROID_VERSION_CODE 2072)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")

View file

@ -146,6 +146,7 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h ${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h ${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h ${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h
${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.h
) )
# Mozilla headres # Mozilla headres
@ -197,6 +198,7 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp ${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp
${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.cpp
) )
# Mozilla sources # Mozilla sources

View file

@ -404,6 +404,9 @@ void AmneziaApplication::initControllers()
m_pageController.reset(new PageController(m_serversModel, m_settings)); m_pageController.reset(new PageController(m_serversModel, m_settings));
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
m_focusController.reset(new FocusController(m_engine, this));
m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get());
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel,
m_apiServicesModel, m_settings)); m_apiServicesModel, m_settings));
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());

View file

@ -19,6 +19,7 @@
#include "ui/controllers/exportController.h" #include "ui/controllers/exportController.h"
#include "ui/controllers/importController.h" #include "ui/controllers/importController.h"
#include "ui/controllers/installController.h" #include "ui/controllers/installController.h"
#include "ui/controllers/focusController.h"
#include "ui/controllers/pageController.h" #include "ui/controllers/pageController.h"
#include "ui/controllers/settingsController.h" #include "ui/controllers/settingsController.h"
#include "ui/controllers/sitesController.h" #include "ui/controllers/sitesController.h"
@ -124,6 +125,7 @@ private:
#endif #endif
QScopedPointer<ConnectionController> m_connectionController; QScopedPointer<ConnectionController> m_connectionController;
QScopedPointer<FocusController> m_focusController;
QScopedPointer<PageController> m_pageController; QScopedPointer<PageController> m_pageController;
QScopedPointer<InstallController> m_installController; QScopedPointer<InstallController> m_installController;
QScopedPointer<ImportController> m_importController; QScopedPointer<ImportController> m_importController;

View file

@ -11,7 +11,7 @@
<uses-feature android:name="android.hardware.camera.any" android:required="false" /> <uses-feature android:name="android.hardware.camera.any" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" /> <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<!-- for TV --> <!-- for TV -->
<uses-feature android:name="android.software.leanback" android:required="false" /> <uses-feature android:name="android.software.leanback" android:required="true" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" /> <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<!-- The following comment will be replaced upon deployment with default features based on the dependencies <!-- The following comment will be replaced upon deployment with default features based on the dependencies
@ -91,6 +91,13 @@
android:exported="false" android:exported="false"
android:theme="@style/Translucent" /> android:theme="@style/Translucent" />
<activity android:name=".TvFilePicker"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity=""
android:exported="false"
android:theme="@style/Translucent" />
<activity <activity
android:name=".ImportConfigActivity" android:name=".ImportConfigActivity"
android:excludeFromRecents="true" android:excludeFromRecents="true"

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_banner_background"/>
<foreground android:drawable="@mipmap/ic_banner_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View file

@ -23,4 +23,6 @@
<string name="notificationSettingsDialogTitle">Настройки уведомлений</string> <string name="notificationSettingsDialogTitle">Настройки уведомлений</string>
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string> <string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
<string name="openNotificationSettings">Открыть настройки уведомлений</string> <string name="openNotificationSettings">Открыть настройки уведомлений</string>
<string name="tvNoFileBrowser">Пожалуйста, установите приложение для просмотра файлов</string>
</resources> </resources>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_banner_background">#1E1E1F</color>
</resources>

View file

@ -23,4 +23,6 @@
<string name="notificationSettingsDialogTitle">Notification settings</string> <string name="notificationSettingsDialogTitle">Notification settings</string>
<string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string> <string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string>
<string name="openNotificationSettings">Open notification settings</string> <string name="openNotificationSettings">Open notification settings</string>
<string name="tvNoFileBrowser">Please install a file management utility to browse files</string>
</resources> </resources>

View file

@ -4,6 +4,7 @@ import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.AlertDialog import android.app.AlertDialog
import android.app.NotificationManager import android.app.NotificationManager
import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.ComponentName import android.content.ComponentName
import android.content.Intent import android.content.Intent
@ -12,6 +13,7 @@ import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
import android.content.ServiceConnection import android.content.ServiceConnection
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri
import android.net.VpnService import android.net.VpnService
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -20,8 +22,13 @@ import android.os.IBinder
import android.os.Looper import android.os.Looper
import android.os.Message import android.os.Message
import android.os.Messenger import android.os.Messenger
import android.os.ParcelFileDescriptor
import android.os.SystemClock
import android.provider.OpenableColumns
import android.provider.Settings import android.provider.Settings
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager.LayoutParams import android.view.WindowManager.LayoutParams
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
@ -30,6 +37,7 @@ import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import java.io.IOException import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE import kotlin.LazyThreadSafetyMode.NONE
import kotlin.coroutines.CoroutineContext
import kotlin.text.RegexOption.IGNORE_CASE import kotlin.text.RegexOption.IGNORE_CASE
import AppListProvider import AppListProvider
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
@ -71,6 +79,7 @@ class AmneziaActivity : QtActivity() {
private var isInBoundState = false private var isInBoundState = false
private var notificationStateReceiver: BroadcastReceiver? = null private var notificationStateReceiver: BroadcastReceiver? = null
private lateinit var vpnServiceMessenger: IpcMessenger private lateinit var vpnServiceMessenger: IpcMessenger
private var pfd: ParcelFileDescriptor? = null
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>() private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>() private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
@ -514,6 +523,7 @@ class AmneziaActivity : QtActivity() {
type = "text/*" type = "text/*"
putExtra(Intent.EXTRA_TITLE, fileName) putExtra(Intent.EXTRA_TITLE, fileName)
}.also { }.also {
try {
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler( startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
onSuccess = { onSuccess = {
it?.data?.let { uri -> it?.data?.let { uri ->
@ -529,6 +539,9 @@ class AmneziaActivity : QtActivity() {
} }
} }
)) ))
} catch (_: ActivityNotFoundException) {
Toast.makeText(this@AmneziaActivity, "Unsupported", Toast.LENGTH_LONG).show()
}
} }
} }
} }
@ -537,6 +550,7 @@ class AmneziaActivity : QtActivity() {
fun openFile(filter: String?) { fun openFile(filter: String?) {
Log.v(TAG, "Open file with filter: $filter") Log.v(TAG, "Open file with filter: $filter")
mainScope.launch { mainScope.launch {
val intent = if (!isOnTv()) {
val mimeTypes = if (!filter.isNullOrEmpty()) { val mimeTypes = if (!filter.isNullOrEmpty()) {
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE) val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
val mime = MimeTypeMap.getSingleton() val mime = MimeTypeMap.getSingleton()
@ -562,10 +576,20 @@ class AmneziaActivity : QtActivity() {
else -> type = "*/*" else -> type = "*/*"
} }
} }
}.also { }
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler( } else {
Intent(this@AmneziaActivity, TvFilePicker::class.java)
}
try {
startActivityForResult(intent, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
onAny = { onAny = {
val uri = it?.data?.toString() ?: "" if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
showNoFileBrowserAlertDialog()
}
val uri = it?.data?.apply {
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}?.toString() ?: ""
Log.v(TAG, "Open file: $uri") Log.v(TAG, "Open file: $uri")
mainScope.launch { mainScope.launch {
qtInitialized.await() qtInitialized.await()
@ -573,9 +597,67 @@ class AmneziaActivity : QtActivity() {
} }
} }
)) ))
} catch (_: ActivityNotFoundException) {
showNoFileBrowserAlertDialog()
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened("")
} }
} }
} }
}
private fun showNoFileBrowserAlertDialog() {
AlertDialog.Builder(this)
.setMessage(R.string.tvNoFileBrowser)
.setCancelable(false)
.setPositiveButton(android.R.string.ok) { _, _ ->
try {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://webstoreredirect")))
} catch (_: Throwable) {}
}
.show()
}
@Suppress("unused")
fun getFd(fileName: String): Int {
Log.v(TAG, "Get fd for $fileName")
return blockingCall {
try {
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
pfd?.fd ?: -1
} catch (e: Exception) {
Log.e(TAG, "Failed to get fd: $e")
-1
}
}
}
@Suppress("unused")
fun closeFd() {
Log.v(TAG, "Close fd")
mainScope.launch {
pfd?.close()
pfd = null
}
}
@Suppress("unused")
fun getFileName(uri: String): String {
Log.v(TAG, "Get file name for uri: $uri")
return blockingCall {
try {
contentResolver.query(Uri.parse(uri), arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor ->
if (cursor.moveToFirst() && !cursor.isNull(0)) {
return@blockingCall cursor.getString(0) ?: ""
}
}
} catch (e: Exception) {
Log.e(TAG, "Failed to get file name: $e")
}
""
}
}
@Suppress("unused") @Suppress("unused")
@SuppressLint("UnsupportedChromeOsCameraSystemFeature") @SuppressLint("UnsupportedChromeOsCameraSystemFeature")
@ -721,6 +803,50 @@ class AmneziaActivity : QtActivity() {
} }
} }
// method to workaround Qt's problem with calling the keyboard on TVs
@Suppress("unused")
fun sendTouch(x: Float, y: Float) {
Log.v(TAG, "Send touch: $x, $y")
blockingCall {
findQtWindow(window.decorView)?.let {
Log.v(TAG, "Send touch to $it")
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
}
}
}
private fun findQtWindow(view: View): View? {
Log.v(TAG, "findQtWindow: process $view")
if (view::class.simpleName == "QtWindow") return view
else if (view is ViewGroup) {
for (i in 0 until view.childCount) {
val result = findQtWindow(view.getChildAt(i))
if (result != null) return result
}
return null
} else return null
}
private fun createEvent(x: Float, y: Float, eventTime: Long, action: Int): MotionEvent =
MotionEvent.obtain(
eventTime,
eventTime,
action,
1,
arrayOf(MotionEvent.PointerProperties().apply {
id = 0
toolType = MotionEvent.TOOL_TYPE_FINGER
}),
arrayOf(MotionEvent.PointerCoords().apply {
this.x = x
this.y = y
pressure = 1f
size = 1f
}),
0, 0, 1.0f, 1.0f, 0, 0, 0,0
)
// workaround for a bug in Qt that causes the mouse click event not to be handled // workaround for a bug in Qt that causes the mouse click event not to be handled
// also disable right-click, as it causes the application to crash // also disable right-click, as it causes the application to crash
private var lastButtonState = 0 private var lastButtonState = 0
@ -770,6 +896,7 @@ class AmneziaActivity : QtActivity() {
} }
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
Log.v(TAG, "dispatchTouch: $ev")
if (ev != null && ev.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) { if (ev != null && ev.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
return handleMouseEvent(ev) { super.dispatchTouchEvent(it) } return handleMouseEvent(ev) { super.dispatchTouchEvent(it) }
} }
@ -784,6 +911,13 @@ class AmneziaActivity : QtActivity() {
/** /**
* Utils methods * Utils methods
*/ */
private fun <T> blockingCall(
context: CoroutineContext = Dispatchers.Main.immediate,
block: suspend () -> T
) = runBlocking {
mainScope.async(context) { block() }.await()
}
companion object { companion object {
private fun actionCodeToString(actionCode: Int): String = private fun actionCodeToString(actionCode: Int): String =
when (actionCode) { when (actionCode) {

View file

@ -0,0 +1,45 @@
package org.amnezia.vpn
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import org.amnezia.vpn.util.Log
private const val TAG = "TvFilePicker"
class TvFilePicker : ComponentActivity() {
private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
setResult(RESULT_OK, Intent().apply { data = it })
finish()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.v(TAG, "onCreate")
getFile()
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.v(TAG, "onNewIntent")
getFile()
}
private fun getFile() {
try {
Log.v(TAG, "getFile")
fileChooseResultLauncher.launch("*/*")
} catch (_: ActivityNotFoundException) {
Log.w(TAG, "Activity not found")
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
finish()
} catch (e: Exception) {
Log.e(TAG, "Failed to get file: $e")
setResult(RESULT_CANCELED)
finish()
}
}
}

View file

@ -120,7 +120,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
} }
} }
QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); QString subnetIp = containerConfig.value(m_protocolName).toObject().value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
{ {
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts); QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
if (l.isEmpty()) { if (l.isEmpty()) {

View file

@ -346,7 +346,9 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
} }
if (container == DockerContainer::Awg) { if (container == DockerContainer::Awg) {
if ((oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort) if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|| (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)) != newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount) || (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)) != newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
@ -370,8 +372,10 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
} }
if (container == DockerContainer::WireGuard) { if (container == DockerContainer::WireGuard) {
if (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)) != newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|| (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)))
return true; return true;
} }
@ -607,6 +611,8 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } }); vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
// Amnezia wireguard vars // Amnezia wireguard vars
vars.append({ { "$AWG_SUBNET_IP",
amneziaWireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } });
vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } }); vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } }); vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });

View file

@ -104,7 +104,7 @@ QJsonObject Deserialize(const QString &vmessStr, QString *alias, QString *errMes
server.users.first().security = "auto"; server.users.first().security = "auto";
} }
const static auto getQueryValue = [&query](const QString &key, const QString &defaultValue) { const auto getQueryValue = [&query](const QString &key, const QString &defaultValue) {
if (query.hasQueryItem(key)) if (query.hasQueryItem(key))
return query.queryItemValue(key, QUrl::FullyDecoded); return query.queryItemValue(key, QUrl::FullyDecoded);
else else

View file

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 13V19C18 19.5304 17.7893 20.0391 17.4142 20.4142C17.0391 20.7893 16.5304 21 16 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V8C3 7.46957 3.21071 6.96086 3.58579 6.58579C3.96086 6.21071 4.46957 6 5 6H11" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 3H21V9" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 14L21 3" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 644 B

View file

@ -163,9 +163,7 @@ QString AndroidController::openFile(const QString &filter)
QString fileName; QString fileName;
connect(this, &AndroidController::fileOpened, this, connect(this, &AndroidController::fileOpened, this,
[&fileName, &wait](const QString &uri) { [&fileName, &wait](const QString &uri) {
qDebug() << "Android event: file opened; uri:" << uri; fileName = uri;
fileName = QQmlFile::urlToLocalFileOrQrc(uri);
qDebug() << "Android opened filename:" << fileName;
wait.quit(); wait.quit();
}, },
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection)); static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
@ -175,6 +173,25 @@ QString AndroidController::openFile(const QString &filter)
return fileName; return fileName;
} }
int AndroidController::getFd(const QString &fileName)
{
return callActivityMethod<jint>("getFd", "(Ljava/lang/String;)I",
QJniObject::fromString(fileName).object<jstring>());
}
void AndroidController::closeFd()
{
callActivityMethod("closeFd", "()V");
}
QString AndroidController::getFileName(const QString &uri)
{
auto fileName = callActivityMethod<jstring, jstring>("getFileName", "(Ljava/lang/String;)Ljava/lang/String;",
QJniObject::fromString(uri).object<jstring>());
QJniEnvironment env;
return AndroidUtils::convertJString(env.jniEnv(), fileName.object<jstring>());
}
bool AndroidController::isCameraPresent() bool AndroidController::isCameraPresent()
{ {
return callActivityMethod<jboolean>("isCameraPresent", "()Z"); return callActivityMethod<jboolean>("isCameraPresent", "()Z");
@ -287,6 +304,11 @@ bool AndroidController::requestAuthentication()
return result; return result;
} }
void AndroidController::sendTouch(float x, float y)
{
callActivityMethod("sendTouch", "(FF)V", x, y);
}
// Moving log processing to the Android side // Moving log processing to the Android side
jclass AndroidController::log; jclass AndroidController::log;
jmethodID AndroidController::logDebug; jmethodID AndroidController::logDebug;

View file

@ -34,6 +34,9 @@ public:
void resetLastServer(int serverIndex); void resetLastServer(int serverIndex);
void saveFile(const QString &fileName, const QString &data); void saveFile(const QString &fileName, const QString &data);
QString openFile(const QString &filter); QString openFile(const QString &filter);
int getFd(const QString &fileName);
void closeFd();
QString getFileName(const QString &uri);
bool isCameraPresent(); bool isCameraPresent();
bool isOnTv(); bool isOnTv();
void startQrReaderActivity(); void startQrReaderActivity();
@ -48,6 +51,7 @@ public:
bool isNotificationPermissionGranted(); bool isNotificationPermissionGranted();
void requestNotificationPermission(); void requestNotificationPermission();
bool requestAuthentication(); bool requestAuthentication();
void sendTouch(float x, float y);
static bool initLogging(); static bool initLogging();
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);

View file

@ -196,6 +196,8 @@ QStringList LinuxFirewall::getDNSRules(const QStringList& servers)
result << QStringLiteral("-o amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server); result << QStringLiteral("-o amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server); result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server); result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun2+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun2+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
} }
return result; return result;
} }
@ -277,6 +279,7 @@ void LinuxFirewall::install()
installAnchor(Both, QStringLiteral("200.allowVPN"), { installAnchor(Both, QStringLiteral("200.allowVPN"), {
QStringLiteral("-o amn0+ -j ACCEPT"), QStringLiteral("-o amn0+ -j ACCEPT"),
QStringLiteral("-o tun0+ -j ACCEPT"), QStringLiteral("-o tun0+ -j ACCEPT"),
QStringLiteral("-o tun2+ -j ACCEPT"),
}); });
installAnchor(IPv4, QStringLiteral("120.blockNets"), {}); installAnchor(IPv4, QStringLiteral("120.blockNets"), {});

View file

@ -1,7 +1,6 @@
#include "xrayprotocol.h" #include "xrayprotocol.h"
#include "utilities.h" #include "utilities.h"
#include "containers/containers_defs.h"
#include "core/networkUtilities.h" #include "core/networkUtilities.h"
#include <QCryptographicHash> #include <QCryptographicHash>
@ -22,9 +21,8 @@ XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent):
XrayProtocol::~XrayProtocol() XrayProtocol::~XrayProtocol()
{ {
qDebug() << "XrayProtocol::~XrayProtocol()";
XrayProtocol::stop(); XrayProtocol::stop();
QThread::msleep(200);
m_xrayProcess.close();
} }
ErrorCode XrayProtocol::start() ErrorCode XrayProtocol::start()
@ -36,10 +34,6 @@ ErrorCode XrayProtocol::start()
return lastError(); return lastError();
} }
if (Utils::processIsRunning(Utils::executable(xrayExecPath(), true))) {
Utils::killProcessByName(Utils::executable(xrayExecPath(), true));
}
#ifdef QT_DEBUG #ifdef QT_DEBUG
m_xrayCfgFile.setAutoRemove(false); m_xrayCfgFile.setAutoRemove(false);
#endif #endif
@ -54,9 +48,16 @@ ErrorCode XrayProtocol::start()
qDebug().noquote() << "XrayProtocol::start()" qDebug().noquote() << "XrayProtocol::start()"
<< xrayExecPath() << args.join(" "); << xrayExecPath() << args.join(" ");
m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels);
m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels);
m_xrayProcess.setProgram(xrayExecPath()); m_xrayProcess.setProgram(xrayExecPath());
if (Utils::processIsRunning(Utils::executable("xray", false))) {
qDebug().noquote() << "kill previos xray";
Utils::killProcessByName(Utils::executable("xray", false));
}
m_xrayProcess.setArguments(args); m_xrayProcess.setArguments(args);
connect(&m_xrayProcess, &QProcess::readyReadStandardOutput, this, [this]() { connect(&m_xrayProcess, &QProcess::readyReadStandardOutput, this, [this]() {
@ -68,13 +69,9 @@ ErrorCode XrayProtocol::start()
connect(&m_xrayProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) { connect(&m_xrayProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
qDebug().noquote() << "XrayProtocol finished, exitCode, exitStatus" << exitCode << exitStatus; qDebug().noquote() << "XrayProtocol finished, exitCode, exitStatus" << exitCode << exitStatus;
setConnectionState(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
if (exitStatus != QProcess::NormalExit) { if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) {
emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed); emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed);
stop(); emit setConnectionState(Vpn::ConnectionState::Error);
}
if (exitCode != 0) {
emit protocolError(amnezia::ErrorCode::InternalError);
stop();
} }
}); });
@ -177,14 +174,14 @@ void XrayProtocol::stop()
IpcClient::Interface()->StartRoutingIpv6(); IpcClient::Interface()->StartRoutingIpv6();
#endif #endif
qDebug() << "XrayProtocol::stop()"; qDebug() << "XrayProtocol::stop()";
m_xrayProcess.terminate(); m_xrayProcess.disconnect();
m_xrayProcess.kill();
m_xrayProcess.waitForFinished(3000);
if (m_t2sProcess) { if (m_t2sProcess) {
m_t2sProcess->stop(); m_t2sProcess->stop();
} }
#ifdef Q_OS_WIN setConnectionState(Vpn::ConnectionState::Disconnected);
Utils::signalCtrl(m_xrayProcess.processId(), CTRL_C_EVENT);
#endif
} }
QString XrayProtocol::xrayExecPath() QString XrayProtocol::xrayExecPath()

View file

@ -1,226 +1,230 @@
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
<file>fonts/pt-root-ui_vf.ttf</file>
<file>images/amneziaBigLogo.png</file>
<file>images/AmneziaVPN.png</file>
<file>images/controls/alert-circle.svg</file>
<file>images/controls/amnezia.svg</file>
<file>images/controls/app.svg</file>
<file>images/controls/archive-restore.svg</file>
<file>images/controls/arrow-left.svg</file>
<file>images/controls/arrow-right.svg</file>
<file>images/controls/bug.svg</file>
<file>images/controls/check.svg</file>
<file>images/controls/chevron-down.svg</file>
<file>images/controls/chevron-right.svg</file>
<file>images/controls/chevron-up.svg</file>
<file>images/controls/close.svg</file>
<file>images/controls/copy.svg</file>
<file>images/controls/delete.svg</file>
<file>images/controls/download.svg</file>
<file>images/controls/edit-3.svg</file>
<file>images/controls/eye-off.svg</file>
<file>images/controls/eye.svg</file>
<file>images/controls/external-link.svg</file>
<file>images/controls/file-check-2.svg</file>
<file>images/controls/file-cog-2.svg</file>
<file>images/controls/folder-open.svg</file>
<file>images/controls/folder-search-2.svg</file>
<file>images/controls/gauge.svg</file>
<file>images/controls/github.svg</file>
<file>images/controls/help-circle.svg</file>
<file>images/controls/history.svg</file>
<file>images/controls/home.svg</file>
<file>images/controls/info.svg</file>
<file>images/controls/mail.svg</file>
<file>images/controls/map-pin.svg</file>
<file>images/controls/more-vertical.svg</file>
<file>images/controls/plus.svg</file>
<file>images/controls/qr-code.svg</file>
<file>images/controls/radio-button-inner-circle-pressed.png</file>
<file>images/controls/radio-button-inner-circle.png</file>
<file>images/controls/radio-button-pressed.svg</file>
<file>images/controls/radio-button.svg</file>
<file>images/controls/radio.svg</file>
<file>images/controls/refresh-cw.svg</file>
<file>images/controls/save.svg</file>
<file>images/controls/scan-line.svg</file>
<file>images/controls/search.svg</file>
<file>images/controls/server.svg</file>
<file>images/controls/settings-2.svg</file>
<file>images/controls/settings.svg</file>
<file>images/controls/share-2.svg</file>
<file>images/controls/split-tunneling.svg</file>
<file>images/controls/tag.svg</file>
<file>images/controls/telegram.svg</file>
<file>images/controls/text-cursor.svg</file>
<file>images/controls/trash.svg</file>
<file>images/controls/x-circle.svg</file>
<file>images/tray/active.png</file> <file>images/tray/active.png</file>
<file>images/tray/default.png</file> <file>images/tray/default.png</file>
<file>images/tray/error.png</file> <file>images/tray/error.png</file>
<file>images/AmneziaVPN.png</file> <file>server_scripts/awg/configure_container.sh</file>
<file>server_scripts/remove_container.sh</file> <file>server_scripts/awg/Dockerfile</file>
<file>server_scripts/setup_host_firewall.sh</file> <file>server_scripts/awg/run_container.sh</file>
<file>server_scripts/openvpn_cloak/Dockerfile</file> <file>server_scripts/awg/start.sh</file>
<file>server_scripts/awg/template.conf</file>
<file>server_scripts/build_container.sh</file>
<file>server_scripts/check_connection.sh</file>
<file>server_scripts/check_server_is_busy.sh</file>
<file>server_scripts/check_user_in_sudo.sh</file>
<file>server_scripts/dns/configure_container.sh</file>
<file>server_scripts/dns/Dockerfile</file>
<file>server_scripts/dns/run_container.sh</file>
<file>server_scripts/install_docker.sh</file>
<file>server_scripts/ipsec/configure_container.sh</file>
<file>server_scripts/ipsec/Dockerfile</file>
<file>server_scripts/ipsec/mobileconfig.plist</file>
<file>server_scripts/ipsec/run_container.sh</file>
<file>server_scripts/ipsec/start.sh</file>
<file>server_scripts/ipsec/template.conf</file>
<file>server_scripts/ipsec/strongswan.profile</file>
<file>server_scripts/openvpn_cloak/configure_container.sh</file> <file>server_scripts/openvpn_cloak/configure_container.sh</file>
<file>server_scripts/openvpn_cloak/Dockerfile</file>
<file>server_scripts/openvpn_cloak/run_container.sh</file>
<file>server_scripts/openvpn_cloak/start.sh</file> <file>server_scripts/openvpn_cloak/start.sh</file>
<file>server_scripts/openvpn_cloak/template.ovpn</file> <file>server_scripts/openvpn_cloak/template.ovpn</file>
<file>server_scripts/install_docker.sh</file>
<file>server_scripts/build_container.sh</file>
<file>server_scripts/prepare_host.sh</file>
<file>server_scripts/check_connection.sh</file>
<file>server_scripts/remove_all_containers.sh</file>
<file>server_scripts/openvpn_cloak/run_container.sh</file>
<file>server_scripts/openvpn/configure_container.sh</file>
<file>server_scripts/openvpn/run_container.sh</file>
<file>server_scripts/openvpn/template.ovpn</file>
<file>server_scripts/openvpn/Dockerfile</file>
<file>server_scripts/openvpn/start.sh</file>
<file>server_scripts/openvpn_shadowsocks/configure_container.sh</file> <file>server_scripts/openvpn_shadowsocks/configure_container.sh</file>
<file>server_scripts/openvpn_shadowsocks/Dockerfile</file> <file>server_scripts/openvpn_shadowsocks/Dockerfile</file>
<file>server_scripts/openvpn_shadowsocks/run_container.sh</file> <file>server_scripts/openvpn_shadowsocks/run_container.sh</file>
<file>server_scripts/openvpn_shadowsocks/start.sh</file> <file>server_scripts/openvpn_shadowsocks/start.sh</file>
<file>server_scripts/openvpn_shadowsocks/template.ovpn</file> <file>server_scripts/openvpn_shadowsocks/template.ovpn</file>
<file>server_scripts/openvpn/configure_container.sh</file>
<file>server_scripts/openvpn/Dockerfile</file>
<file>server_scripts/openvpn/run_container.sh</file>
<file>server_scripts/openvpn/start.sh</file>
<file>server_scripts/openvpn/template.ovpn</file>
<file>server_scripts/prepare_host.sh</file>
<file>server_scripts/remove_all_containers.sh</file>
<file>server_scripts/remove_container.sh</file>
<file>server_scripts/setup_host_firewall.sh</file>
<file>server_scripts/sftp/configure_container.sh</file>
<file>server_scripts/sftp/Dockerfile</file>
<file>server_scripts/sftp/run_container.sh</file>
<file>server_scripts/socks5_proxy/configure_container.sh</file>
<file>server_scripts/socks5_proxy/Dockerfile</file>
<file>server_scripts/socks5_proxy/run_container.sh</file>
<file>server_scripts/socks5_proxy/start.sh</file>
<file>server_scripts/website_tor/configure_container.sh</file>
<file>server_scripts/website_tor/Dockerfile</file>
<file>server_scripts/website_tor/run_container.sh</file>
<file>server_scripts/wireguard/configure_container.sh</file> <file>server_scripts/wireguard/configure_container.sh</file>
<file>server_scripts/wireguard/Dockerfile</file> <file>server_scripts/wireguard/Dockerfile</file>
<file>server_scripts/wireguard/run_container.sh</file> <file>server_scripts/wireguard/run_container.sh</file>
<file>server_scripts/wireguard/start.sh</file> <file>server_scripts/wireguard/start.sh</file>
<file>server_scripts/wireguard/template.conf</file> <file>server_scripts/wireguard/template.conf</file>
<file>server_scripts/website_tor/configure_container.sh</file>
<file>server_scripts/website_tor/run_container.sh</file>
<file>ui/qml/Config/GlobalConfig.qml</file>
<file>ui/qml/Config/qmldir</file>
<file>server_scripts/check_server_is_busy.sh</file>
<file>server_scripts/dns/configure_container.sh</file>
<file>server_scripts/dns/Dockerfile</file>
<file>server_scripts/dns/run_container.sh</file>
<file>server_scripts/sftp/configure_container.sh</file>
<file>server_scripts/sftp/Dockerfile</file>
<file>server_scripts/sftp/run_container.sh</file>
<file>server_scripts/ipsec/configure_container.sh</file>
<file>server_scripts/ipsec/Dockerfile</file>
<file>server_scripts/ipsec/run_container.sh</file>
<file>server_scripts/ipsec/start.sh</file>
<file>server_scripts/ipsec/mobileconfig.plist</file>
<file>server_scripts/ipsec/strongswan.profile</file>
<file>server_scripts/website_tor/Dockerfile</file>
<file>server_scripts/check_user_in_sudo.sh</file>
<file>ui/qml/Controls2/BasicButtonType.qml</file>
<file>ui/qml/Controls2/TextFieldWithHeaderType.qml</file>
<file>ui/qml/Controls2/LabelWithButtonType.qml</file>
<file>images/controls/arrow-right.svg</file>
<file>images/controls/chevron-right.svg</file>
<file>ui/qml/Controls2/ImageButtonType.qml</file>
<file>ui/qml/Controls2/CardType.qml</file>
<file>ui/qml/Controls2/CheckBoxType.qml</file>
<file>images/controls/check.svg</file>
<file>ui/qml/Controls2/DropDownType.qml</file>
<file>ui/qml/Pages2/PageSetupWizardStart.qml</file>
<file>ui/qml/main2.qml</file>
<file>images/amneziaBigLogo.png</file>
<file>ui/qml/Controls2/FlickableType.qml</file>
<file>ui/qml/Pages2/PageSetupWizardCredentials.qml</file>
<file>ui/qml/Controls2/HeaderType.qml</file>
<file>images/controls/arrow-left.svg</file>
<file>ui/qml/Pages2/PageSetupWizardProtocols.qml</file>
<file>ui/qml/Pages2/PageSetupWizardEasy.qml</file>
<file>images/controls/chevron-down.svg</file>
<file>images/controls/chevron-up.svg</file>
<file>ui/qml/Controls2/TextTypes/ParagraphTextType.qml</file>
<file>ui/qml/Controls2/TextTypes/Header2TextType.qml</file>
<file>ui/qml/Controls2/HorizontalRadioButton.qml</file>
<file>ui/qml/Controls2/VerticalRadioButton.qml</file>
<file>ui/qml/Controls2/SwitcherType.qml</file>
<file>ui/qml/Controls2/TabButtonType.qml</file>
<file>ui/qml/Pages2/PageSetupWizardProtocolSettings.qml</file>
<file>ui/qml/Pages2/PageSetupWizardInstalling.qml</file>
<file>ui/qml/Pages2/PageSetupWizardConfigSource.qml</file>
<file>images/controls/folder-open.svg</file>
<file>images/controls/qr-code.svg</file>
<file>images/controls/text-cursor.svg</file>
<file>ui/qml/Pages2/PageSetupWizardTextKey.qml</file>
<file>ui/qml/Pages2/PageStart.qml</file>
<file>ui/qml/Controls2/TabImageButtonType.qml</file>
<file>images/controls/home.svg</file>
<file>images/controls/settings-2.svg</file>
<file>images/controls/share-2.svg</file>
<file>ui/qml/Pages2/PageHome.qml</file>
<file>ui/qml/Pages2/PageSettingsServersList.qml</file>
<file>ui/qml/Pages2/PageShare.qml</file>
<file>ui/qml/Controls2/TextTypes/Header1TextType.qml</file>
<file>ui/qml/Controls2/TextTypes/LabelTextType.qml</file>
<file>ui/qml/Controls2/TextTypes/ButtonTextType.qml</file>
<file>ui/qml/Controls2/Header2Type.qml</file>
<file>images/controls/plus.svg</file>
<file>ui/qml/Components/ConnectButton.qml</file>
<file>images/controls/download.svg</file>
<file>ui/qml/Controls2/ProgressBarType.qml</file>
<file>ui/qml/Components/ConnectionTypeSelectionDrawer.qml</file>
<file>ui/qml/Components/HomeContainersListView.qml</file>
<file>ui/qml/Controls2/TextTypes/CaptionTextType.qml</file>
<file>images/controls/settings.svg</file>
<file>ui/qml/Pages2/PageSettingsServerInfo.qml</file>
<file>ui/qml/Controls2/PageType.qml</file>
<file>ui/qml/Controls2/PopupType.qml</file>
<file>images/controls/edit-3.svg</file>
<file>ui/qml/Pages2/PageSettingsServerData.qml</file>
<file>ui/qml/Components/SettingsContainersListView.qml</file>
<file>ui/qml/Controls2/TextTypes/ListItemTitleType.qml</file>
<file>ui/qml/Controls2/DividerType.qml</file>
<file>ui/qml/Controls2/StackViewType.qml</file>
<file>ui/qml/Pages2/PageSettings.qml</file>
<file>images/controls/amnezia.svg</file>
<file>images/controls/app.svg</file>
<file>images/controls/radio.svg</file>
<file>images/controls/save.svg</file>
<file>images/controls/server.svg</file>
<file>ui/qml/Pages2/PageSettingsServerProtocols.qml</file>
<file>ui/qml/Pages2/PageSettingsServerServices.qml</file>
<file>ui/qml/Pages2/PageSetupWizardViewConfig.qml</file>
<file>images/controls/file-cog-2.svg</file>
<file>ui/qml/Components/QuestionDrawer.qml</file>
<file>ui/qml/Pages2/PageDeinstalling.qml</file>
<file>ui/qml/Controls2/BackButtonType.qml</file>
<file>ui/qml/Pages2/PageSettingsServerProtocol.qml</file>
<file>ui/qml/Components/TransportProtoSelector.qml</file>
<file>ui/qml/Controls2/ListViewWithRadioButtonType.qml</file>
<file>images/controls/radio-button.svg</file>
<file>images/controls/radio-button-inner-circle.png</file>
<file>images/controls/radio-button-pressed.svg</file>
<file>images/controls/radio-button-inner-circle-pressed.png</file>
<file>ui/qml/Components/ShareConnectionDrawer.qml</file>
<file>ui/qml/Pages2/PageSettingsConnection.qml</file>
<file>ui/qml/Pages2/PageSettingsDns.qml</file>
<file>ui/qml/Pages2/PageSettingsApplication.qml</file>
<file>ui/qml/Pages2/PageSettingsBackup.qml</file>
<file>images/controls/delete.svg</file>
<file>ui/qml/Pages2/PageSettingsAbout.qml</file>
<file>images/controls/github.svg</file>
<file>images/controls/mail.svg</file>
<file>images/controls/telegram.svg</file>
<file>ui/qml/Controls2/TextTypes/SmallTextType.qml</file>
<file>ui/qml/Filters/ContainersModelFilters.qml</file>
<file>ui/qml/Components/SelectLanguageDrawer.qml</file>
<file>ui/qml/Controls2/BusyIndicatorType.qml</file>
<file>ui/qml/Pages2/PageProtocolOpenVpnSettings.qml</file>
<file>ui/qml/Pages2/PageProtocolShadowSocksSettings.qml</file>
<file>ui/qml/Pages2/PageProtocolCloakSettings.qml</file>
<file>ui/qml/Pages2/PageProtocolXraySettings.qml</file>
<file>ui/qml/Pages2/PageProtocolRaw.qml</file>
<file>ui/qml/Pages2/PageSettingsLogging.qml</file>
<file>ui/qml/Pages2/PageServiceSftpSettings.qml</file>
<file>images/controls/copy.svg</file>
<file>ui/qml/Pages2/PageServiceTorWebsiteSettings.qml</file>
<file>ui/qml/Pages2/PageSetupWizardQrReader.qml</file>
<file>images/controls/eye.svg</file>
<file>images/controls/eye-off.svg</file>
<file>ui/qml/Pages2/PageSettingsSplitTunneling.qml</file>
<file>ui/qml/Controls2/ContextMenuType.qml</file>
<file>ui/qml/Controls2/TextAreaType.qml</file>
<file>images/controls/trash.svg</file>
<file>images/controls/more-vertical.svg</file>
<file>ui/qml/Controls2/ListViewWithLabelsType.qml</file>
<file>ui/qml/Pages2/PageServiceDnsSettings.qml</file>
<file>ui/qml/Controls2/TopCloseButtonType.qml</file>
<file>images/controls/x-circle.svg</file>
<file>ui/qml/Pages2/PageProtocolAwgSettings.qml</file>
<file>server_scripts/awg/template.conf</file>
<file>server_scripts/awg/start.sh</file>
<file>server_scripts/awg/configure_container.sh</file>
<file>server_scripts/awg/run_container.sh</file>
<file>server_scripts/awg/Dockerfile</file>
<file>ui/qml/Pages2/PageShareFullAccess.qml</file>
<file>images/controls/close.svg</file>
<file>images/controls/search.svg</file>
<file>server_scripts/xray/configure_container.sh</file> <file>server_scripts/xray/configure_container.sh</file>
<file>server_scripts/xray/Dockerfile</file> <file>server_scripts/xray/Dockerfile</file>
<file>server_scripts/xray/run_container.sh</file> <file>server_scripts/xray/run_container.sh</file>
<file>server_scripts/xray/start.sh</file> <file>server_scripts/xray/start.sh</file>
<file>server_scripts/xray/template.json</file> <file>server_scripts/xray/template.json</file>
<file>ui/qml/Pages2/PageProtocolWireGuardSettings.qml</file> <file>ui/qml/Components/AdLabel.qml</file>
<file>ui/qml/Components/ConnectButton.qml</file>
<file>ui/qml/Components/ConnectionTypeSelectionDrawer.qml</file>
<file>ui/qml/Components/HomeContainersListView.qml</file>
<file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file> <file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file>
<file>images/controls/split-tunneling.svg</file>
<file>ui/qml/Controls2/DrawerType2.qml</file>
<file>ui/qml/Pages2/PageSettingsAppSplitTunneling.qml</file>
<file>ui/qml/Components/InstalledAppsDrawer.qml</file> <file>ui/qml/Components/InstalledAppsDrawer.qml</file>
<file>images/controls/alert-circle.svg</file> <file>ui/qml/Components/QuestionDrawer.qml</file>
<file>images/controls/file-check-2.svg</file> <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/ShareConnectionDrawer.qml</file>
<file>ui/qml/Components/TransportProtoSelector.qml</file>
<file>ui/qml/Config/GlobalConfig.qml</file>
<file>ui/qml/Config/qmldir</file>
<file>ui/qml/Controls2/BackButtonType.qml</file>
<file>ui/qml/Controls2/BasicButtonType.qml</file>
<file>ui/qml/Controls2/BusyIndicatorType.qml</file>
<file>ui/qml/Controls2/CardType.qml</file>
<file>ui/qml/Controls2/CardWithIconsType.qml</file>
<file>ui/qml/Controls2/CheckBoxType.qml</file>
<file>ui/qml/Controls2/ContextMenuType.qml</file>
<file>ui/qml/Controls2/DividerType.qml</file>
<file>ui/qml/Controls2/DrawerType2.qml</file>
<file>ui/qml/Controls2/DropDownType.qml</file>
<file>ui/qml/Controls2/FlickableType.qml</file>
<file>ui/qml/Controls2/Header2Type.qml</file>
<file>ui/qml/Controls2/HeaderType.qml</file>
<file>ui/qml/Controls2/HorizontalRadioButton.qml</file>
<file>ui/qml/Controls2/ImageButtonType.qml</file>
<file>ui/qml/Controls2/LabelWithButtonType.qml</file>
<file>ui/qml/Controls2/LabelWithImageType.qml</file>
<file>ui/qml/Controls2/ListViewWithLabelsType.qml</file>
<file>ui/qml/Controls2/ListViewWithRadioButtonType.qml</file>
<file>ui/qml/Controls2/PageType.qml</file>
<file>ui/qml/Controls2/PopupType.qml</file>
<file>ui/qml/Controls2/ProgressBarType.qml</file>
<file>ui/qml/Controls2/ScrollBarType.qml</file>
<file>ui/qml/Controls2/StackViewType.qml</file>
<file>ui/qml/Controls2/SwitcherType.qml</file>
<file>ui/qml/Controls2/TabButtonType.qml</file>
<file>ui/qml/Controls2/TabImageButtonType.qml</file>
<file>ui/qml/Controls2/TextAreaType.qml</file>
<file>ui/qml/Controls2/TextAreaWithFooterType.qml</file>
<file>ui/qml/Controls2/TextFieldWithHeaderType.qml</file>
<file>ui/qml/Controls2/TextTypes/ButtonTextType.qml</file>
<file>ui/qml/Controls2/TextTypes/CaptionTextType.qml</file>
<file>ui/qml/Controls2/TextTypes/Header1TextType.qml</file>
<file>ui/qml/Controls2/TextTypes/Header2TextType.qml</file>
<file>ui/qml/Controls2/TextTypes/LabelTextType.qml</file>
<file>ui/qml/Controls2/TextTypes/ListItemTitleType.qml</file>
<file>ui/qml/Controls2/TextTypes/ParagraphTextType.qml</file>
<file>ui/qml/Controls2/TextTypes/SmallTextType.qml</file>
<file>ui/qml/Controls2/TopCloseButtonType.qml</file>
<file>ui/qml/Controls2/VerticalRadioButton.qml</file>
<file>ui/qml/Controls2/WarningType.qml</file> <file>ui/qml/Controls2/WarningType.qml</file>
<file>fonts/pt-root-ui_vf.ttf</file> <file>ui/qml/Filters/ContainersModelFilters.qml</file>
<file>ui/qml/Modules/Style/qmldir</file> <file>ui/qml/main2.qml</file>
<file>ui/qml/Modules/Style/AmneziaStyle.qml</file> <file>ui/qml/Modules/Style/AmneziaStyle.qml</file>
<file>ui/qml/Modules/Style/qmldir</file>
<file>ui/qml/Pages2/PageDeinstalling.qml</file>
<file>ui/qml/Pages2/PageDevMenu.qml</file>
<file>ui/qml/Pages2/PageHome.qml</file>
<file>ui/qml/Pages2/PageProtocolAwgSettings.qml</file>
<file>ui/qml/Pages2/PageProtocolCloakSettings.qml</file>
<file>ui/qml/Pages2/PageProtocolOpenVpnSettings.qml</file>
<file>ui/qml/Pages2/PageProtocolRaw.qml</file>
<file>ui/qml/Pages2/PageProtocolShadowSocksSettings.qml</file>
<file>ui/qml/Pages2/PageProtocolWireGuardSettings.qml</file>
<file>ui/qml/Pages2/PageProtocolXraySettings.qml</file>
<file>ui/qml/Pages2/PageServiceDnsSettings.qml</file>
<file>ui/qml/Pages2/PageServiceSftpSettings.qml</file>
<file>ui/qml/Pages2/PageServiceSocksProxySettings.qml</file> <file>ui/qml/Pages2/PageServiceSocksProxySettings.qml</file>
<file>server_scripts/socks5_proxy/run_container.sh</file> <file>ui/qml/Pages2/PageServiceTorWebsiteSettings.qml</file>
<file>server_scripts/socks5_proxy/Dockerfile</file> <file>ui/qml/Pages2/PageSettings.qml</file>
<file>server_scripts/socks5_proxy/configure_container.sh</file> <file>ui/qml/Pages2/PageSettingsAbout.qml</file>
<file>server_scripts/socks5_proxy/start.sh</file> <file>ui/qml/Pages2/PageSettingsApiLanguageList.qml</file>
<file>server_scripts/ipsec/template.conf</file> <file>ui/qml/Pages2/PageSettingsApiServerInfo.qml</file>
<file>ui/qml/Pages2/PageSettingsApplication.qml</file>
<file>ui/qml/Pages2/PageSettingsAppSplitTunneling.qml</file>
<file>ui/qml/Pages2/PageSettingsBackup.qml</file>
<file>ui/qml/Pages2/PageSettingsConnection.qml</file>
<file>ui/qml/Pages2/PageSettingsDns.qml</file>
<file>ui/qml/Pages2/PageSettingsLogging.qml</file>
<file>ui/qml/Pages2/PageSettingsServerData.qml</file>
<file>ui/qml/Pages2/PageSettingsServerInfo.qml</file>
<file>ui/qml/Pages2/PageSettingsServerProtocol.qml</file>
<file>ui/qml/Pages2/PageSettingsServerProtocols.qml</file>
<file>ui/qml/Pages2/PageSettingsServerServices.qml</file>
<file>ui/qml/Pages2/PageSettingsServersList.qml</file>
<file>ui/qml/Pages2/PageSettingsSplitTunneling.qml</file>
<file>ui/qml/Pages2/PageProtocolAwgClientSettings.qml</file> <file>ui/qml/Pages2/PageProtocolAwgClientSettings.qml</file>
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file> <file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
<file>ui/qml/Pages2/PageSetupWizardApiServicesList.qml</file>
<file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file> <file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file>
<file>ui/qml/Controls2/CardWithIconsType.qml</file> <file>ui/qml/Pages2/PageSetupWizardApiServicesList.qml</file>
<file>images/controls/tag.svg</file> <file>ui/qml/Pages2/PageSetupWizardConfigSource.qml</file>
<file>images/controls/history.svg</file> <file>ui/qml/Pages2/PageSetupWizardCredentials.qml</file>
<file>images/controls/gauge.svg</file> <file>ui/qml/Pages2/PageSetupWizardEasy.qml</file>
<file>images/controls/map-pin.svg</file> <file>ui/qml/Pages2/PageSetupWizardInstalling.qml</file>
<file>ui/qml/Controls2/LabelWithImageType.qml</file> <file>ui/qml/Pages2/PageSetupWizardProtocols.qml</file>
<file>images/controls/info.svg</file> <file>ui/qml/Pages2/PageSetupWizardProtocolSettings.qml</file>
<file>ui/qml/Controls2/TextAreaWithFooterType.qml</file> <file>ui/qml/Pages2/PageSetupWizardQrReader.qml</file>
<file>images/controls/scan-line.svg</file> <file>ui/qml/Pages2/PageSetupWizardStart.qml</file>
<file>images/controls/folder-search-2.svg</file> <file>ui/qml/Pages2/PageSetupWizardTextKey.qml</file>
<file>ui/qml/Pages2/PageSettingsApiServerInfo.qml</file> <file>ui/qml/Pages2/PageSetupWizardViewConfig.qml</file>
<file>images/controls/bug.svg</file> <file>ui/qml/Pages2/PageShare.qml</file>
<file>ui/qml/Pages2/PageDevMenu.qml</file> <file>ui/qml/Pages2/PageShareFullAccess.qml</file>
<file>images/controls/refresh-cw.svg</file> <file>ui/qml/Pages2/PageStart.qml</file>
<file>ui/qml/Pages2/PageSettingsApiLanguageList.qml</file>
<file>images/controls/archive-restore.svg</file>
<file>images/controls/help-circle.svg</file>
</qresource> </qresource>
<qresource prefix="/countriesFlags"> <qresource prefix="/countriesFlags">
<file>images/flagKit/ZW.svg</file> <file>images/flagKit/ZW.svg</file>

View file

@ -12,7 +12,7 @@ echo $WIREGUARD_PSK > /opt/amnezia/awg/wireguard_psk.key
cat > /opt/amnezia/awg/wg0.conf <<EOF cat > /opt/amnezia/awg/wg0.conf <<EOF
[Interface] [Interface]
PrivateKey = $WIREGUARD_SERVER_PRIVATE_KEY PrivateKey = $WIREGUARD_SERVER_PRIVATE_KEY
Address = $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR Address = $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR
ListenPort = $AWG_SERVER_PORT ListenPort = $AWG_SERVER_PORT
Jc = $JUNK_PACKET_COUNT Jc = $JUNK_PACKET_COUNT
Jmin = $JUNK_PACKET_MIN_SIZE Jmin = $JUNK_PACKET_MIN_SIZE

View file

@ -17,12 +17,12 @@ iptables -A FORWARD -i wg0 -j ACCEPT
iptables -A OUTPUT -o wg0 -j ACCEPT iptables -A OUTPUT -o wg0 -j ACCEPT
# Allow forwarding traffic only from the VPN. # Allow forwarding traffic only from the VPN.
iptables -A FORWARD -i wg0 -o eth0 -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT iptables -A FORWARD -i wg0 -o eth0 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
iptables -A FORWARD -i wg0 -o eth1 -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT iptables -A FORWARD -i wg0 -o eth1 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth0 -j MASQUERADE iptables -t nat -A POSTROUTING -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth1 -j MASQUERADE iptables -t nat -A POSTROUTING -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth1 -j MASQUERADE
tail -f /dev/null tail -f /dev/null

View file

@ -538,3 +538,13 @@ void Settings::toggleDevGatewayEnv(bool enabled)
{ {
m_isDevGatewayEnv = enabled; m_isDevGatewayEnv = enabled;
} }
bool Settings::isHomeAdLabelVisible()
{
return value("Conf/homeAdLabelVisible", true).toBool();
}
void Settings::disableHomeAdLabel()
{
setValue("Conf/homeAdLabelVisible", false);
}

View file

@ -222,6 +222,9 @@ public:
bool isDevGatewayEnv(); bool isDevGatewayEnv();
void toggleDevGatewayEnv(bool enabled); void toggleDevGatewayEnv(bool enabled);
bool isHomeAdLabelVisible();
void disableHomeAdLabel();
signals: signals:
void saveLogsChanged(bool enabled); void saveLogsChanged(bool enabled);
void screenshotsEnabledChanged(bool enabled); void screenshotsEnabledChanged(bool enabled);

View file

@ -2679,7 +2679,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="154"/> <location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="154"/>
<source>Where to get connection data, step-by-step instructions for buying a VPS</source> <source>Where to get connection data, step-by-step instructions for buying a VPS</source>
<translation>Где взять данные для подключения, пошаговые инстуркции по покупке VPS</translation> <translation>Где взять данные для подключения, пошаговые инструкции по покупке VPS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="170"/> <location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="170"/>

View file

@ -0,0 +1,209 @@
#include "focusController.h"
#include "utils/qmlUtils.h"
#include <QQmlApplicationEngine>
#include <QQuickWindow>
FocusController::FocusController(QQmlApplicationEngine *engine, QObject *parent)
: QObject { parent },
m_engine { engine },
m_focusChain {},
m_focusedItem { nullptr },
m_rootObjects {},
m_defaultFocusItem { nullptr },
m_lvfc { nullptr }
{
QObject::connect(m_engine, &QQmlApplicationEngine::objectCreated, this, [this](QObject *object, const QUrl &url) {
QQuickItem *newDefaultFocusItem = object->findChild<QQuickItem *>("defaultFocusItem");
if (newDefaultFocusItem && m_defaultFocusItem != newDefaultFocusItem) {
m_defaultFocusItem = newDefaultFocusItem;
}
});
QObject::connect(this, &FocusController::focusedItemChanged, this,
[this]() { m_focusedItem->forceActiveFocus(Qt::TabFocusReason); });
}
void FocusController::nextKeyTabItem()
{
nextItem(Direction::Forward);
}
void FocusController::previousKeyTabItem()
{
nextItem(Direction::Backward);
}
void FocusController::nextKeyUpItem()
{
nextItem(Direction::Backward);
}
void FocusController::nextKeyDownItem()
{
nextItem(Direction::Forward);
}
void FocusController::nextKeyLeftItem()
{
nextItem(Direction::Backward);
}
void FocusController::nextKeyRightItem()
{
nextItem(Direction::Forward);
}
void FocusController::setFocusItem(QQuickItem *item)
{
if (m_focusedItem != item) {
m_focusedItem = item;
}
emit focusedItemChanged();
}
void FocusController::setFocusOnDefaultItem()
{
setFocusItem(m_defaultFocusItem);
}
void FocusController::pushRootObject(QObject *object)
{
m_rootObjects.push(object);
dropListView();
// setFocusOnDefaultItem();
}
void FocusController::dropRootObject(QObject *object)
{
if (m_rootObjects.empty()) {
return;
}
if (m_rootObjects.top() == object) {
m_rootObjects.pop();
dropListView();
setFocusOnDefaultItem();
} else {
qWarning() << "===>> TRY TO DROP WRONG ROOT OBJECT: " << m_rootObjects.top() << " SHOULD BE: " << object;
}
}
void FocusController::resetRootObject()
{
m_rootObjects.clear();
}
void FocusController::reload(Direction direction)
{
m_focusChain.clear();
QObject *rootObject = (m_rootObjects.empty() ? m_engine->rootObjects().value(0) : m_rootObjects.top());
if (!rootObject) {
qCritical() << "No ROOT OBJECT found!";
resetRootObject();
dropListView();
return;
}
m_focusChain.append(FocusControl::getSubChain(rootObject));
std::sort(m_focusChain.begin(), m_focusChain.end(),
direction == Direction::Forward ? FocusControl::isLess : FocusControl::isMore);
if (m_focusChain.empty()) {
qWarning() << "Focus chain is empty!";
resetRootObject();
dropListView();
return;
}
}
void FocusController::nextItem(Direction direction)
{
reload(direction);
if (m_lvfc && FocusControl::isListView(m_focusedItem)) {
direction == Direction::Forward ? focusNextListViewItem() : focusPreviousListViewItem();
return;
}
if (m_focusChain.empty()) {
qWarning() << "There are no items to navigate";
setFocusOnDefaultItem();
return;
}
auto focusedItemIndex = m_focusChain.indexOf(m_focusedItem);
if (focusedItemIndex == -1) {
focusedItemIndex = 0;
} else if (focusedItemIndex == (m_focusChain.size() - 1)) {
focusedItemIndex = 0;
} else {
focusedItemIndex++;
}
const auto focusedItem = qobject_cast<QQuickItem *>(m_focusChain.at(focusedItemIndex));
if (focusedItem == nullptr) {
qWarning() << "Failed to get item to focus on. Setting focus on default";
setFocusOnDefaultItem();
return;
}
if (FocusControl::isListView(focusedItem)) {
m_lvfc = new ListViewFocusController(focusedItem, this);
m_focusedItem = focusedItem;
if (direction == Direction::Forward) {
m_lvfc->nextDelegate();
focusNextListViewItem();
} else {
m_lvfc->previousDelegate();
focusPreviousListViewItem();
}
return;
}
setFocusItem(focusedItem);
}
void FocusController::focusNextListViewItem()
{
m_lvfc->reloadFocusChain();
if (m_lvfc->isLastFocusItemInListView() || m_lvfc->isReturnNeeded()) {
dropListView();
nextItem(Direction::Forward);
return;
} else if (m_lvfc->isLastFocusItemInDelegate()) {
m_lvfc->resetFocusChain();
m_lvfc->nextDelegate();
}
m_lvfc->focusNextItem();
}
void FocusController::focusPreviousListViewItem()
{
m_lvfc->reloadFocusChain();
if (m_lvfc->isFirstFocusItemInListView() || m_lvfc->isReturnNeeded()) {
dropListView();
nextItem(Direction::Backward);
return;
} else if (m_lvfc->isFirstFocusItemInDelegate()) {
m_lvfc->resetFocusChain();
m_lvfc->previousDelegate();
}
m_lvfc->focusPreviousItem();
}
void FocusController::dropListView()
{
if (m_lvfc) {
delete m_lvfc;
m_lvfc = nullptr;
}
}

View file

@ -0,0 +1,57 @@
#ifndef FOCUSCONTROLLER_H
#define FOCUSCONTROLLER_H
#include "ui/controllers/listViewFocusController.h"
#include <QQmlApplicationEngine>
/*!
* \brief The FocusController class makes focus control more straightforward
* \details Focus is handled only for visible and enabled items which have
* `isFocused` property from top left to bottom right.
* \note There are items handled differently (e.g. ListView)
*/
class FocusController : public QObject
{
Q_OBJECT
public:
explicit FocusController(QQmlApplicationEngine *engine, QObject *parent = nullptr);
~FocusController() override = default;
Q_INVOKABLE void nextKeyTabItem();
Q_INVOKABLE void previousKeyTabItem();
Q_INVOKABLE void nextKeyUpItem();
Q_INVOKABLE void nextKeyDownItem();
Q_INVOKABLE void nextKeyLeftItem();
Q_INVOKABLE void nextKeyRightItem();
Q_INVOKABLE void setFocusItem(QQuickItem *item);
Q_INVOKABLE void setFocusOnDefaultItem();
Q_INVOKABLE void pushRootObject(QObject *object);
Q_INVOKABLE void dropRootObject(QObject *object);
Q_INVOKABLE void resetRootObject();
private:
enum class Direction {
Forward,
Backward,
};
void reload(Direction direction);
void nextItem(Direction direction);
void focusNextListViewItem();
void focusPreviousListViewItem();
void dropListView();
QQmlApplicationEngine *m_engine; // Pointer to engine to get root object
QList<QObject *> m_focusChain; // List of current objects to be focused
QQuickItem *m_focusedItem; // Pointer to the active focus item
QStack<QObject *> m_rootObjects; // Pointer to stack of roots for focus chain
QQuickItem *m_defaultFocusItem;
ListViewFocusController *m_lvfc; // ListView focus manager
signals:
void focusedItemChanged();
};
#endif // FOCUSCONTROLLER_H

View file

@ -9,6 +9,7 @@
#include "core/errorstrings.h" #include "core/errorstrings.h"
#include "core/serialization/serialization.h" #include "core/serialization/serialization.h"
#include "systemController.h"
#include "utilities.h" #include "utilities.h"
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
@ -76,17 +77,18 @@ ImportController::ImportController(const QSharedPointer<ServersModel> &serversMo
bool ImportController::extractConfigFromFile(const QString &fileName) bool ImportController::extractConfigFromFile(const QString &fileName)
{ {
QFile file(fileName); QString data;
if (!SystemController::readFile(fileName, data)) {
if (file.open(QIODevice::ReadOnly)) {
QString data = file.readAll();
m_configFileName = QFileInfo(file.fileName()).fileName();
return extractConfigFromData(data);
}
emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false); emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false);
return false; return false;
}
m_configFileName = QFileInfo(QFile(fileName).fileName()).fileName();
#ifdef Q_OS_ANDROID
if (m_configFileName.isEmpty()) {
m_configFileName = AndroidController::instance()->getFileName(fileName);
}
#endif
return extractConfigFromData(data);
} }
bool ImportController::extractConfigFromData(QString data) bool ImportController::extractConfigFromData(QString data)

View file

@ -0,0 +1,309 @@
#include "listViewFocusController.h"
#include "utils/qmlUtils.h"
#include <QQuickWindow>
ListViewFocusController::ListViewFocusController(QQuickItem *listView, QObject *parent)
: QObject { parent },
m_listView { listView },
m_focusChain {},
m_currentSection { Section::Default },
m_header { nullptr },
m_footer { nullptr },
m_focusedItem { nullptr },
m_focusedItemIndex { -1 },
m_delegateIndex { 0 },
m_isReturnNeeded { false },
m_currentSectionString { "Default", "Header", "Delegate", "Footer" }
{
QVariant headerItemProperty = m_listView->property("headerItem");
m_header = headerItemProperty.canConvert<QQuickItem *>() ? headerItemProperty.value<QQuickItem *>() : nullptr;
QVariant footerItemProperty = m_listView->property("footerItem");
m_footer = footerItemProperty.canConvert<QQuickItem *>() ? footerItemProperty.value<QQuickItem *>() : nullptr;
}
ListViewFocusController::~ListViewFocusController()
{
}
void ListViewFocusController::viewAtCurrentIndex() const
{
switch (m_currentSection) {
case Section::Default: [[fallthrough]];
case Section::Header: {
QMetaObject::invokeMethod(m_listView, "positionViewAtBeginning");
break;
}
case Section::Delegate: {
QMetaObject::invokeMethod(m_listView, "positionViewAtIndex", Q_ARG(int, m_delegateIndex), // Index
Q_ARG(int, 2)); // PositionMode (0 = Visible)
break;
}
case Section::Footer: {
QMetaObject::invokeMethod(m_listView, "positionViewAtEnd");
break;
}
}
}
int ListViewFocusController::size() const
{
return m_listView->property("count").toInt();
}
int ListViewFocusController::currentIndex() const
{
return m_delegateIndex;
}
void ListViewFocusController::setDelegateIndex(int index)
{
m_delegateIndex = index;
m_listView->setProperty("currentIndex", index);
}
void ListViewFocusController::nextDelegate()
{
switch (m_currentSection) {
case Section::Default: {
if (hasHeader()) {
m_currentSection = Section::Header;
viewAtCurrentIndex();
break;
}
[[fallthrough]];
}
case Section::Header: {
if (size() > 0) {
m_currentSection = Section::Delegate;
viewAtCurrentIndex();
break;
}
[[fallthrough]];
}
case Section::Delegate:
if (m_delegateIndex < (size() - 1)) {
setDelegateIndex(m_delegateIndex + 1);
viewAtCurrentIndex();
break;
} else if (hasFooter()) {
m_currentSection = Section::Footer;
viewAtCurrentIndex();
break;
}
[[fallthrough]];
case Section::Footer: {
m_isReturnNeeded = true;
m_currentSection = Section::Default;
viewAtCurrentIndex();
break;
}
default: {
qCritical() << "Current section is invalid!";
break;
}
}
}
void ListViewFocusController::previousDelegate()
{
switch (m_currentSection) {
case Section::Default: {
if (hasFooter()) {
m_currentSection = Section::Footer;
break;
}
[[fallthrough]];
}
case Section::Footer: {
if (size() > 0) {
m_currentSection = Section::Delegate;
setDelegateIndex(size() - 1);
break;
}
[[fallthrough]];
}
case Section::Delegate: {
if (m_delegateIndex > 0) {
setDelegateIndex(m_delegateIndex - 1);
break;
} else if (hasHeader()) {
m_currentSection = Section::Header;
break;
}
[[fallthrough]];
}
case Section::Header: {
m_isReturnNeeded = true;
m_currentSection = Section::Default;
break;
}
default: {
qCritical() << "Current section is invalid!";
break;
}
}
}
void ListViewFocusController::decrementIndex()
{
m_delegateIndex--;
}
QQuickItem *ListViewFocusController::itemAtIndex(const int index) const
{
QQuickItem *item { nullptr };
QMetaObject::invokeMethod(m_listView, "itemAtIndex", Q_RETURN_ARG(QQuickItem *, item), Q_ARG(int, index));
return item;
}
QQuickItem *ListViewFocusController::currentDelegate() const
{
QQuickItem *result { nullptr };
switch (m_currentSection) {
case Section::Default: {
qWarning() << "No elements...";
break;
}
case Section::Header: {
result = m_header;
break;
}
case Section::Delegate: {
result = itemAtIndex(m_delegateIndex);
break;
}
case Section::Footer: {
result = m_footer;
break;
}
}
return result;
}
QQuickItem *ListViewFocusController::focusedItem() const
{
return m_focusedItem;
}
void ListViewFocusController::focusNextItem()
{
if (m_isReturnNeeded) {
return;
}
reloadFocusChain();
if (m_focusChain.empty()) {
qWarning() << "No elements found in the delegate. Going to next delegate...";
nextDelegate();
focusNextItem();
return;
}
m_focusedItemIndex++;
m_focusedItem = qobject_cast<QQuickItem *>(m_focusChain.at(m_focusedItemIndex));
m_focusedItem->forceActiveFocus(Qt::TabFocusReason);
}
void ListViewFocusController::focusPreviousItem()
{
if (m_isReturnNeeded) {
return;
}
if (m_focusChain.empty()) {
qInfo() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements...";
reloadFocusChain();
}
if (m_focusChain.empty()) {
qWarning() << "No elements found in the delegate. Going to next delegate...";
previousDelegate();
focusPreviousItem();
return;
}
if (m_focusedItemIndex == -1) {
m_focusedItemIndex = m_focusChain.size();
}
m_focusedItemIndex--;
m_focusedItem = qobject_cast<QQuickItem *>(m_focusChain.at(m_focusedItemIndex));
m_focusedItem->forceActiveFocus(Qt::TabFocusReason);
}
void ListViewFocusController::resetFocusChain()
{
m_focusChain.clear();
m_focusedItem = nullptr;
m_focusedItemIndex = -1;
}
void ListViewFocusController::reloadFocusChain()
{
m_focusChain = FocusControl::getItemsChain(currentDelegate());
}
bool ListViewFocusController::isFirstFocusItemInDelegate() const
{
return m_focusedItem && (m_focusedItem == m_focusChain.first());
}
bool ListViewFocusController::isLastFocusItemInDelegate() const
{
return m_focusedItem && (m_focusedItem == m_focusChain.last());
}
bool ListViewFocusController::hasHeader() const
{
return m_header && !FocusControl::getItemsChain(m_header).isEmpty();
}
bool ListViewFocusController::hasFooter() const
{
return m_footer && !FocusControl::getItemsChain(m_footer).isEmpty();
}
bool ListViewFocusController::isFirstFocusItemInListView() const
{
switch (m_currentSection) {
case Section::Footer: {
return isFirstFocusItemInDelegate() && !hasHeader() && (size() == 0);
}
case Section::Delegate: {
return isFirstFocusItemInDelegate() && (m_delegateIndex == 0) && !hasHeader();
}
case Section::Header: {
isFirstFocusItemInDelegate();
}
case Section::Default: {
return true;
}
default: qWarning() << "Wrong section"; return true;
}
}
bool ListViewFocusController::isLastFocusItemInListView() const
{
switch (m_currentSection) {
case Section::Default: {
return !hasHeader() && (size() == 0) && !hasFooter();
}
case Section::Header: {
return isLastFocusItemInDelegate() && (size() == 0) && !hasFooter();
}
case Section::Delegate: {
return isLastFocusItemInDelegate() && (m_delegateIndex == size() - 1) && !hasFooter();
}
case Section::Footer: {
return isLastFocusItemInDelegate();
}
default: qWarning() << "Wrong section"; return true;
}
}
bool ListViewFocusController::isReturnNeeded() const
{
return m_isReturnNeeded;
}

View file

@ -0,0 +1,70 @@
#ifndef LISTVIEWFOCUSCONTROLLER_H
#define LISTVIEWFOCUSCONTROLLER_H
#include <QList>
#include <QObject>
#include <QQuickItem>
#include <QSharedPointer>
#include <QStack>
/*!
* \brief The ListViewFocusController class manages the focus of elements in ListView
* \details This class object moving focus to ListView's controls since ListView stores
* it's data implicitly and it could be got one by one.
*
* This class was made to store as less as possible data getting it from QML
* when it's needed.
*/
class ListViewFocusController : public QObject
{
Q_OBJECT
public:
explicit ListViewFocusController(QQuickItem *listView, QObject *parent = nullptr);
~ListViewFocusController();
void nextDelegate();
void previousDelegate();
void decrementIndex();
void focusNextItem();
void focusPreviousItem();
void resetFocusChain();
void reloadFocusChain();
bool isFirstFocusItemInListView() const;
bool isFirstFocusItemInDelegate() const;
bool isLastFocusItemInListView() const;
bool isLastFocusItemInDelegate() const;
bool isReturnNeeded() const;
private:
enum class Section {
Default,
Header,
Delegate,
Footer,
};
int size() const;
int currentIndex() const;
void setDelegateIndex(int index);
void viewAtCurrentIndex() const;
QQuickItem *itemAtIndex(const int index) const;
QQuickItem *currentDelegate() const;
QQuickItem *focusedItem() const;
bool hasHeader() const;
bool hasFooter() const;
QQuickItem *m_listView;
QList<QObject *> m_focusChain;
Section m_currentSection;
QQuickItem *m_header;
QQuickItem *m_footer;
QQuickItem *m_focusedItem; // Pointer to focused item on Delegate
qsizetype m_focusedItemIndex;
qsizetype m_delegateIndex;
bool m_isReturnNeeded;
QList<QString> m_currentSectionString;
};
#endif // LISTVIEWFOCUSCONTROLLER_H

View file

@ -81,7 +81,7 @@ void PageController::keyPressEvent(Qt::Key key)
case Qt::Key_Escape: { case Qt::Key_Escape: {
if (m_drawerDepth) { if (m_drawerDepth) {
emit closeTopDrawer(); emit closeTopDrawer();
setDrawerDepth(getDrawerDepth() - 1); decrementDrawerDepth();
} else { } else {
emit escapePressed(); emit escapePressed();
} }
@ -142,11 +142,25 @@ void PageController::setDrawerDepth(const int depth)
} }
} }
int PageController::getDrawerDepth() int PageController::getDrawerDepth() const
{ {
return m_drawerDepth; return m_drawerDepth;
} }
int PageController::incrementDrawerDepth()
{
return ++m_drawerDepth;
}
int PageController::decrementDrawerDepth()
{
if (m_drawerDepth == 0) {
return m_drawerDepth;
} else {
return --m_drawerDepth;
}
}
void PageController::onShowErrorMessage(ErrorCode errorCode) void PageController::onShowErrorMessage(ErrorCode errorCode)
{ {
const auto fullErrorMessage = errorString(errorCode); const auto fullErrorMessage = errorString(errorCode);

View file

@ -100,7 +100,9 @@ public slots:
void closeApplication(); void closeApplication();
void setDrawerDepth(const int depth); void setDrawerDepth(const int depth);
int getDrawerDepth(); int getDrawerDepth() const;
int incrementDrawerDepth();
int decrementDrawerDepth();
private slots: private slots:
void onShowErrorMessage(amnezia::ErrorCode errorCode); void onShowErrorMessage(amnezia::ErrorCode errorCode);
@ -135,9 +137,6 @@ signals:
void escapePressed(); void escapePressed();
void closeTopDrawer(); void closeTopDrawer();
void forceTabBarActiveFocus();
void forceStackActiveFocus();
private: private:
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;

View file

@ -131,12 +131,8 @@ void SettingsController::backupAppConfig(const QString &fileName)
void SettingsController::restoreAppConfig(const QString &fileName) void SettingsController::restoreAppConfig(const QString &fileName)
{ {
QFile file(fileName); QByteArray data;
SystemController::readFile(fileName, data);
file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
restoreAppConfigFromData(data); restoreAppConfigFromData(data);
} }
@ -325,3 +321,14 @@ bool SettingsController::isOnTv()
return false; return false;
#endif #endif
} }
bool SettingsController::isHomeAdLabelVisible()
{
return m_settings->isHomeAdLabelVisible();
}
void SettingsController::disableHomeAdLabel()
{
m_settings->disableHomeAdLabel();
emit isHomeAdLabelVisibleChanged(false);
}

View file

@ -29,6 +29,8 @@ public:
Q_PROPERTY(QString gatewayEndpoint READ getGatewayEndpoint WRITE setGatewayEndpoint NOTIFY gatewayEndpointChanged) Q_PROPERTY(QString gatewayEndpoint READ getGatewayEndpoint WRITE setGatewayEndpoint NOTIFY gatewayEndpointChanged)
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged) Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
public slots: public slots:
void toggleAmneziaDns(bool enable); void toggleAmneziaDns(bool enable);
bool isAmneziaDnsEnabled(); bool isAmneziaDnsEnabled();
@ -89,6 +91,9 @@ public slots:
bool isOnTv(); bool isOnTv();
bool isHomeAdLabelVisible();
void disableHomeAdLabel();
signals: signals:
void primaryDnsChanged(); void primaryDnsChanged();
void secondaryDnsChanged(); void secondaryDnsChanged();
@ -112,6 +117,8 @@ signals:
void gatewayEndpointChanged(const QString &endpoint); void gatewayEndpointChanged(const QString &endpoint);
void devGatewayEnvChanged(bool enabled); void devGatewayEnvChanged(bool enabled);
void isHomeAdLabelVisibleChanged(bool visible);
private: private:
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel; QSharedPointer<ContainersModel> m_containersModel;

View file

@ -82,14 +82,12 @@ void SitesController::removeSite(int index)
void SitesController::importSites(const QString &fileName, bool replaceExisting) void SitesController::importSites(const QString &fileName, bool replaceExisting)
{ {
QFile file(fileName); QByteArray jsonData;
if (!SystemController::readFile(fileName, jsonData)) {
if (!file.open(QIODevice::ReadOnly)) {
emit errorOccurred(tr("Can't open file: %1").arg(fileName)); emit errorOccurred(tr("Can't open file: %1").arg(fileName));
return; return;
} }
QByteArray jsonData = file.readAll();
QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData); QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData);
if (jsonDocument.isNull()) { if (jsonDocument.isNull()) {
emit errorOccurred(tr("Failed to parse JSON data from file: %1").arg(fileName)); emit errorOccurred(tr("Failed to parse JSON data from file: %1").arg(fileName));

View file

@ -24,7 +24,7 @@ SystemController::SystemController(const std::shared_ptr<Settings> &settings, QO
{ {
} }
void SystemController::saveFile(QString fileName, const QString &data) void SystemController::saveFile(const QString &fileName, const QString &data)
{ {
#if defined Q_OS_ANDROID #if defined Q_OS_ANDROID
AndroidController::instance()->saveFile(fileName, data); AndroidController::instance()->saveFile(fileName, data);
@ -62,6 +62,31 @@ void SystemController::saveFile(QString fileName, const QString &data)
#endif #endif
} }
bool SystemController::readFile(const QString &fileName, QByteArray &data)
{
#ifdef Q_OS_ANDROID
int fd = AndroidController::instance()->getFd(fileName);
if (fd == -1) return false;
QFile file;
if(!file.open(fd, QIODevice::ReadOnly)) return false;
data = file.readAll();
AndroidController::instance()->closeFd();
#else
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) return false;
data = file.readAll();
#endif
return true;
}
bool SystemController::readFile(const QString &fileName, QString &data)
{
QByteArray byteArray;
if(!readFile(fileName, byteArray)) return false;
data = byteArray;
return true;
}
QString SystemController::getFileName(const QString &acceptLabel, const QString &nameFilter, QString SystemController::getFileName(const QString &acceptLabel, const QString &nameFilter,
const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix) const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix)
{ {
@ -134,3 +159,10 @@ bool SystemController::isAuthenticated()
return true; return true;
#endif #endif
} }
void SystemController::sendTouch(float x, float y)
{
#ifdef Q_OS_ANDROID
AndroidController::instance()->sendTouch(x, y);
#endif
}

View file

@ -11,7 +11,9 @@ class SystemController : public QObject
public: public:
explicit SystemController(const std::shared_ptr<Settings> &setting, QObject *parent = nullptr); explicit SystemController(const std::shared_ptr<Settings> &setting, QObject *parent = nullptr);
static void saveFile(QString fileName, const QString &data); static void saveFile(const QString &fileName, const QString &data);
static bool readFile(const QString &fileName, QByteArray &data);
static bool readFile(const QString &fileName, QString &data);
public slots: public slots:
QString getFileName(const QString &acceptLabel, const QString &nameFilter, const QString &selectedFile = "", QString getFileName(const QString &acceptLabel, const QString &nameFilter, const QString &selectedFile = "",
@ -20,6 +22,8 @@ public slots:
void setQmlRoot(QObject *qmlRoot); void setQmlRoot(QObject *qmlRoot);
bool isAuthenticated(); bool isAuthenticated();
void sendTouch(float x, float y);
signals: signals:
void fileDialogClosed(const bool isAccepted); void fileDialogClosed(const bool isAccepted);

View file

@ -70,7 +70,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
.arg(speed); .arg(speed);
} else if (serviceType == serviceType::amneziaFree){ } else if (serviceType == serviceType::amneziaFree){
QString description = tr("VPN to access blocked sites in regions with high levels of Internet censorship. "); QString description = tr("VPN to access blocked sites in regions with high levels of Internet censorship. ");
if (isServiceAvailable) { if (!isServiceAvailable) {
description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a>"); description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a>");
} }
return description; return description;
@ -86,7 +86,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
} }
case IsServiceAvailableRole: { case IsServiceAvailableRole: {
if (serviceType == serviceType::amneziaFree) { if (serviceType == serviceType::amneziaFree) {
if (isServiceAvailable) { if (!isServiceAvailable) {
return false; return false;
} }
} }

View file

@ -21,6 +21,7 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in
} }
switch (role) { switch (role) {
case Roles::SubnetAddressRole: m_serverProtocolConfig.insert(config_key::subnet_address, value.toString()); break;
case Roles::PortRole: m_serverProtocolConfig.insert(config_key::port, value.toString()); break; case Roles::PortRole: m_serverProtocolConfig.insert(config_key::port, value.toString()); break;
case Roles::ClientMtuRole: m_clientProtocolConfig.insert(config_key::mtu, value.toString()); break; case Roles::ClientMtuRole: m_clientProtocolConfig.insert(config_key::mtu, value.toString()); break;
@ -58,6 +59,7 @@ QVariant AwgConfigModel::data(const QModelIndex &index, int role) const
} }
switch (role) { switch (role) {
case Roles::SubnetAddressRole: return m_serverProtocolConfig.value(config_key::subnet_address).toString();
case Roles::PortRole: return m_serverProtocolConfig.value(config_key::port).toString(); case Roles::PortRole: return m_serverProtocolConfig.value(config_key::port).toString();
case Roles::ClientMtuRole: return m_clientProtocolConfig.value(config_key::mtu); case Roles::ClientMtuRole: return m_clientProtocolConfig.value(config_key::mtu);
@ -92,6 +94,7 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
m_serverProtocolConfig.insert(config_key::transport_proto, m_serverProtocolConfig.insert(config_key::transport_proto,
serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto)); serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto));
m_serverProtocolConfig[config_key::last_config] = serverProtocolConfig.value(config_key::last_config); m_serverProtocolConfig[config_key::last_config] = serverProtocolConfig.value(config_key::last_config);
m_serverProtocolConfig[config_key::subnet_address] = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
m_serverProtocolConfig[config_key::port] = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort); m_serverProtocolConfig[config_key::port] = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort);
m_serverProtocolConfig[config_key::junkPacketCount] = m_serverProtocolConfig[config_key::junkPacketCount] =
serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount); serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
@ -168,6 +171,7 @@ QHash<int, QByteArray> AwgConfigModel::roleNames() const
{ {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[SubnetAddressRole] = "subnetAddress";
roles[PortRole] = "port"; roles[PortRole] = "port";
roles[ClientMtuRole] = "clientMtu"; roles[ClientMtuRole] = "clientMtu";
@ -197,6 +201,7 @@ AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig)
clientJunkPacketMinSize = clientProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize); clientJunkPacketMinSize = clientProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
clientJunkPacketMaxSize = clientProtocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize); clientJunkPacketMaxSize = clientProtocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize);
subnetAddress = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
port = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort); port = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort);
serverJunkPacketCount = serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount); serverJunkPacketCount = serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
serverJunkPacketMinSize = serverProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize); serverJunkPacketMinSize = serverProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
@ -216,7 +221,7 @@ AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig)
bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const
{ {
if (port != other.port || serverJunkPacketCount != other.serverJunkPacketCount if (subnetAddress != other.subnetAddress || port != other.port || serverJunkPacketCount != other.serverJunkPacketCount
|| serverJunkPacketMinSize != other.serverJunkPacketMinSize || serverJunkPacketMaxSize != other.serverJunkPacketMaxSize || serverJunkPacketMinSize != other.serverJunkPacketMinSize || serverJunkPacketMaxSize != other.serverJunkPacketMaxSize
|| serverInitPacketJunkSize != other.serverInitPacketJunkSize || serverResponsePacketJunkSize != other.serverResponsePacketJunkSize || serverInitPacketJunkSize != other.serverInitPacketJunkSize || serverResponsePacketJunkSize != other.serverResponsePacketJunkSize
|| serverInitPacketMagicHeader != other.serverInitPacketMagicHeader || serverInitPacketMagicHeader != other.serverInitPacketMagicHeader

View file

@ -15,6 +15,7 @@ struct AwgConfig
{ {
AwgConfig(const QJsonObject &jsonConfig); AwgConfig(const QJsonObject &jsonConfig);
QString subnetAddress;
QString port; QString port;
QString clientMtu; QString clientMtu;
@ -43,7 +44,8 @@ class AwgConfigModel : public QAbstractListModel
public: public:
enum Roles { enum Roles {
PortRole = Qt::UserRole + 1, SubnetAddressRole = Qt::UserRole + 1,
PortRole,
ClientMtuRole, ClientMtuRole,
ClientJunkPacketCountRole, ClientJunkPacketCountRole,

View file

@ -21,6 +21,7 @@ bool WireGuardConfigModel::setData(const QModelIndex &index, const QVariant &val
} }
switch (role) { switch (role) {
case Roles::SubnetAddressRole: m_serverProtocolConfig.insert(config_key::subnet_address, value.toString()); break;
case Roles::PortRole: m_serverProtocolConfig.insert(config_key::port, value.toString()); break; case Roles::PortRole: m_serverProtocolConfig.insert(config_key::port, value.toString()); break;
case Roles::ClientMtuRole: m_clientProtocolConfig.insert(config_key::mtu, value.toString()); break; case Roles::ClientMtuRole: m_clientProtocolConfig.insert(config_key::mtu, value.toString()); break;
} }
@ -36,6 +37,7 @@ QVariant WireGuardConfigModel::data(const QModelIndex &index, int role) const
} }
switch (role) { switch (role) {
case Roles::SubnetAddressRole: return m_serverProtocolConfig.value(config_key::subnet_address).toString();
case Roles::PortRole: return m_serverProtocolConfig.value(config_key::port).toString(); case Roles::PortRole: return m_serverProtocolConfig.value(config_key::port).toString();
case Roles::ClientMtuRole: return m_clientProtocolConfig.value(config_key::mtu); case Roles::ClientMtuRole: return m_clientProtocolConfig.value(config_key::mtu);
} }
@ -56,6 +58,7 @@ void WireGuardConfigModel::updateModel(const QJsonObject &config)
m_serverProtocolConfig.insert(config_key::transport_proto, m_serverProtocolConfig.insert(config_key::transport_proto,
serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto)); serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto));
m_serverProtocolConfig[config_key::last_config] = serverProtocolConfig.value(config_key::last_config); m_serverProtocolConfig[config_key::last_config] = serverProtocolConfig.value(config_key::last_config);
m_serverProtocolConfig[config_key::subnet_address] = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
m_serverProtocolConfig[config_key::port] = serverProtocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort); m_serverProtocolConfig[config_key::port] = serverProtocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString(); auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString();
@ -96,6 +99,7 @@ QHash<int, QByteArray> WireGuardConfigModel::roleNames() const
{ {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[SubnetAddressRole] = "subnetAddress";
roles[PortRole] = "port"; roles[PortRole] = "port";
roles[ClientMtuRole] = "clientMtu"; roles[ClientMtuRole] = "clientMtu";
@ -108,12 +112,13 @@ WgConfig::WgConfig(const QJsonObject &serverProtocolConfig)
QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
clientMtu = clientProtocolConfig[config_key::mtu].toString(protocols::wireguard::defaultMtu); clientMtu = clientProtocolConfig[config_key::mtu].toString(protocols::wireguard::defaultMtu);
subnetAddress = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
port = serverProtocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort); port = serverProtocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
} }
bool WgConfig::hasEqualServerSettings(const WgConfig &other) const bool WgConfig::hasEqualServerSettings(const WgConfig &other) const
{ {
if (port != other.port) { if (subnetAddress != other.subnetAddress || port != other.port) {
return false; return false;
} }
return true; return true;

View file

@ -10,6 +10,7 @@ struct WgConfig
{ {
WgConfig(const QJsonObject &jsonConfig); WgConfig(const QJsonObject &jsonConfig);
QString subnetAddress;
QString port; QString port;
QString clientMtu; QString clientMtu;
@ -24,7 +25,8 @@ class WireGuardConfigModel : public QAbstractListModel
public: public:
enum Roles { enum Roles {
PortRole = Qt::UserRole + 1, SubnetAddressRole = Qt::UserRole + 1,
PortRole,
ClientMtuRole ClientMtuRole
}; };

View file

@ -0,0 +1,72 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Shapes
import Qt5Compat.GraphicalEffects
import Style 1.0
import "../Config"
import "../Controls2"
import "../Controls2/TextTypes"
Rectangle {
id: root
property real contentHeight: ad.implicitHeight + ad.anchors.topMargin + ad.anchors.bottomMargin
border.width: 1
border.color: AmneziaStyle.color.goldenApricot
color: AmneziaStyle.color.transparent
radius: 13
visible: GC.isDesktop() && ServersModel.isDefaultServerFromApi
&& ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && SettingsController.isHomeAdLabelVisible
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: function() {
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/premium")
}
}
RowLayout {
id: ad
anchors.fill: parent
anchors.margins: 16
Image {
source: "qrc:/images/controls/amnezia.svg"
sourceSize: Qt.size(36, 36)
layer {
effect: ColorOverlay {
color: AmneziaStyle.color.paleGray
}
}
}
CaptionTextType {
Layout.fillWidth: true
Layout.rightMargin: 10
Layout.leftMargin: 10
text: qsTr("Amnezia Premium - for access to any website")
color: AmneziaStyle.color.pearlGray
lineHeight: 18
font.pixelSize: 15
}
ImageButtonType {
image: "qrc:/images/controls/close.svg"
imageColor: AmneziaStyle.color.paleGray
onClicked: function() {
SettingsController.disableHomeAdLabel()
}
}
}
}

View file

@ -16,6 +16,32 @@ Button {
property string connectedButtonColor: AmneziaStyle.color.goldenApricot property string connectedButtonColor: AmneziaStyle.color.goldenApricot
property bool buttonActiveFocus: activeFocus && (Qt.platform.os !== "android" || SettingsController.isOnTv()) property bool buttonActiveFocus: activeFocus && (Qt.platform.os !== "android" || SettingsController.isOnTv())
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitWidth: 190 implicitWidth: 190
implicitHeight: 190 implicitHeight: 190

View file

@ -14,7 +14,7 @@ DrawerType2 {
width: parent.width width: parent.width
height: parent.height height: parent.height
expandedContent: ColumnLayout { expandedStateContent: ColumnLayout {
id: content id: content
anchors.top: parent.top anchors.top: parent.top
@ -26,14 +26,6 @@ DrawerType2 {
root.expandedHeight = content.implicitHeight + 32 root.expandedHeight = content.implicitHeight + 32
} }
Connections {
target: root
enabled: !GC.isMobile()
function onOpened() {
focusItem.forceActiveFocus()
}
}
Header2Type { Header2Type {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24
@ -44,11 +36,6 @@ DrawerType2 {
headerText: qsTr("Add new connection") headerText: qsTr("Add new connection")
} }
Item {
id: focusItem
KeyNavigation.tab: ip.rightButton
}
LabelWithButtonType { LabelWithButtonType {
id: ip id: ip
Layout.fillWidth: true Layout.fillWidth: true
@ -59,10 +46,8 @@ DrawerType2 {
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageSetupWizardCredentials) PageController.goToPage(PageEnum.PageSetupWizardCredentials)
root.close() root.closeTriggered()
} }
KeyNavigation.tab: qrCode.rightButton
} }
DividerType {} DividerType {}
@ -76,10 +61,8 @@ DrawerType2 {
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageSetupWizardConfigSource) PageController.goToPage(PageEnum.PageSetupWizardConfigSource)
root.close() root.closeTriggered()
} }
KeyNavigation.tab: focusItem
} }
DividerType {} DividerType {}

View file

@ -17,55 +17,15 @@ ListView {
property var rootWidth property var rootWidth
property var selectedText property var selectedText
property bool a: true
width: rootWidth width: rootWidth
height: menuContent.contentItem.height height: contentItem.height
clip: true clip: true
interactive: false snapMode: ListView.SnapToItem
property FlickableType parentFlickable ScrollBar.vertical: ScrollBarType {}
property var lastItemTabClicked
property int currentFocusIndex: 0 property bool isFocusable: true
activeFocusOnTab: true
onActiveFocusChanged: {
if (activeFocus) {
this.currentFocusIndex = 0
this.itemAtIndex(currentFocusIndex).forceActiveFocus()
}
}
Keys.onTabPressed: {
if (currentFocusIndex < this.count - 1) {
currentFocusIndex += 1
this.itemAtIndex(currentFocusIndex).forceActiveFocus()
} else {
currentFocusIndex = 0
if (lastItemTabClicked && typeof lastItemTabClicked === "function") {
lastItemTabClicked()
}
}
}
onVisibleChanged: {
if (visible) {
currentFocusIndex = 0
focusItem.forceActiveFocus()
}
}
Item {
id: focusItem
}
onCurrentFocusIndexChanged: {
if (parentFlickable) {
parentFlickable.ensureVisible(this.itemAtIndex(currentFocusIndex))
}
}
ButtonGroup { ButtonGroup {
id: containersRadioButtonGroup id: containersRadioButtonGroup
@ -75,12 +35,6 @@ ListView {
implicitWidth: rootWidth implicitWidth: rootWidth
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
onActiveFocusChanged: {
if (activeFocus) {
containerRadioButton.forceActiveFocus()
}
}
ColumnLayout { ColumnLayout {
id: content id: content
@ -111,13 +65,13 @@ ListView {
} }
if (checked) { if (checked) {
containersDropDown.close() containersDropDown.closeTriggered()
ServersModel.setDefaultContainer(ServersModel.defaultIndex, proxyDefaultServerContainersModel.mapToSource(index)) ServersModel.setDefaultContainer(ServersModel.defaultIndex, proxyDefaultServerContainersModel.mapToSource(index))
} else { } else {
ContainersModel.setProcessedContainerIndex(proxyDefaultServerContainersModel.mapToSource(index)) ContainersModel.setProcessedContainerIndex(proxyDefaultServerContainersModel.mapToSource(index))
InstallController.setShouldCreateServer(false) InstallController.setShouldCreateServer(false)
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
containersDropDown.close() containersDropDown.closeTriggered()
} }
} }

View file

@ -16,7 +16,7 @@ DrawerType2 {
anchors.fill: parent anchors.fill: parent
expandedHeight: parent.height * 0.9 expandedHeight: parent.height * 0.9
expandedContent: ColumnLayout { expandedStateContent: ColumnLayout {
id: content id: content
anchors.top: parent.top anchors.top: parent.top
@ -24,14 +24,6 @@ DrawerType2 {
anchors.right: parent.right anchors.right: parent.right
spacing: 0 spacing: 0
Connections {
target: root
enabled: !GC.isMobile()
function onOpened() {
focusItem.forceActiveFocus()
}
}
Header2Type { Header2Type {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24
@ -43,11 +35,6 @@ DrawerType2 {
descriptionText: qsTr("Allows you to connect to some sites or applications through a VPN connection and bypass others") descriptionText: qsTr("Allows you to connect to some sites or applications through a VPN connection and bypass others")
} }
Item {
id: focusItem
KeyNavigation.tab: splitTunnelingSwitch.visible ? splitTunnelingSwitch : siteBasedSplitTunnelingSwitch.rightButton
}
LabelWithButtonType { LabelWithButtonType {
id: splitTunnelingSwitch id: splitTunnelingSwitch
Layout.fillWidth: true Layout.fillWidth: true
@ -59,11 +46,9 @@ DrawerType2 {
descriptionText: qsTr("Enabled \nCan't be disabled for current server") descriptionText: qsTr("Enabled \nCan't be disabled for current server")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
KeyNavigation.tab: siteBasedSplitTunnelingSwitch.visible ? siteBasedSplitTunnelingSwitch.rightButton : focusItem
clickedFunction: function() { clickedFunction: function() {
// PageController.goToPage(PageEnum.PageSettingsSplitTunneling) PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
// root.close() root.closeTriggered()
} }
} }
@ -80,13 +65,9 @@ DrawerType2 {
descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
KeyNavigation.tab: appSplitTunnelingSwitch.visible ?
appSplitTunnelingSwitch.rightButton :
focusItem
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsSplitTunneling) PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
root.close() root.closeTriggered()
} }
} }
@ -103,11 +84,9 @@ DrawerType2 {
descriptionText: AppSplitTunnelingModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled") descriptionText: AppSplitTunnelingModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
KeyNavigation.tab: focusItem
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling) PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling)
root.close() root.closeTriggered()
} }
} }

View file

@ -26,7 +26,7 @@ DrawerType2 {
id: installedAppsModel id: installedAppsModel
} }
expandedContent: Item { expandedStateContent: Item {
id: container id: container
implicitHeight: expandedHeight implicitHeight: expandedHeight
@ -43,7 +43,7 @@ DrawerType2 {
BackButtonType { BackButtonType {
backButtonImage: "qrc:/images/controls/arrow-left.svg" backButtonImage: "qrc:/images/controls/arrow-left.svg"
backButtonFunction: function() { backButtonFunction: function() {
root.close() root.closeTriggered()
} }
} }
@ -69,6 +69,8 @@ DrawerType2 {
clip: true clip: true
interactive: true interactive: true
property bool isFocusable: true
model: SortFilterProxyModel { model: SortFilterProxyModel {
id: proxyInstalledAppsModel id: proxyInstalledAppsModel
sourceModel: installedAppsModel sourceModel: installedAppsModel
@ -79,10 +81,7 @@ DrawerType2 {
} }
} }
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBarType {}
id: scrollBar
policy: ScrollBar.AlwaysOn
}
ButtonGroup { ButtonGroup {
id: buttonGroup id: buttonGroup
@ -155,7 +154,7 @@ DrawerType2 {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
AppSplitTunnelingController.addApps(installedAppsModel.getSelectedAppsInfo()) AppSplitTunnelingController.addApps(installedAppsModel.getSelectedAppsInfo())
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
root.close() root.closeTriggered()
} }
} }
} }

View file

@ -20,7 +20,7 @@ DrawerType2 {
property var yesButtonFunction property var yesButtonFunction
property var noButtonFunction property var noButtonFunction
expandedContent: ColumnLayout { expandedStateContent: ColumnLayout {
id: content id: content
anchors.top: parent.top anchors.top: parent.top
@ -33,14 +33,6 @@ DrawerType2 {
root.expandedHeight = content.implicitHeight + 32 root.expandedHeight = content.implicitHeight + 32
} }
Connections {
target: root
enabled: !GC.isMobile()
function onOpened() {
focusItem.forceActiveFocus()
}
}
Header2TextType { Header2TextType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
@ -59,11 +51,6 @@ DrawerType2 {
text: descriptionText text: descriptionText
} }
Item {
id: focusItem
KeyNavigation.tab: yesButton
}
BasicButtonType { BasicButtonType {
id: yesButton id: yesButton
Layout.fillWidth: true Layout.fillWidth: true
@ -78,8 +65,6 @@ DrawerType2 {
yesButtonFunction() yesButtonFunction()
} }
} }
KeyNavigation.tab: noButton
} }
BasicButtonType { BasicButtonType {
@ -102,8 +87,6 @@ DrawerType2 {
noButtonFunction() noButtonFunction()
} }
} }
KeyNavigation.tab: focusItem
} }
} }
} }

View file

@ -11,7 +11,7 @@ import "../Config"
DrawerType2 { DrawerType2 {
id: root id: root
expandedContent: Item { expandedStateContent: Item {
id: container id: container
implicitHeight: root.height * 0.9 implicitHeight: root.height * 0.9
@ -20,19 +20,6 @@ DrawerType2 {
root.expandedHeight = container.implicitHeight root.expandedHeight = container.implicitHeight
} }
Connections {
target: root
enabled: !GC.isMobile()
function onOpened() {
focusItem.forceActiveFocus()
}
}
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: backButtonLayout id: backButtonLayout
@ -43,26 +30,16 @@ DrawerType2 {
BackButtonType { BackButtonType {
id: backButton id: backButton
Layout.fillWidth: true
backButtonImage: "qrc:/images/controls/arrow-left.svg" backButtonImage: "qrc:/images/controls/arrow-left.svg"
backButtonFunction: function() { root.close() } backButtonFunction: function() { root.closeTriggered() }
KeyNavigation.tab: listView
} }
}
FlickableType {
anchors.top: backButtonLayout.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
contentHeight: content.implicitHeight
ColumnLayout {
id: content
anchors.fill: parent
Header2Type { Header2Type {
id: header id: header
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
@ -70,67 +47,34 @@ DrawerType2 {
headerText: qsTr("Choose language") headerText: qsTr("Choose language")
} }
}
ListView { ListView {
id: listView id: listView
Layout.fillWidth: true anchors.top: backButtonLayout.bottom
height: listView.contentItem.height anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
property bool isFocusable: true
property int selectedIndex: LanguageModel.currentLanguageIndex
clip: true clip: true
interactive: false reuseItems: true
ScrollBar.vertical: ScrollBarType {}
model: LanguageModel model: LanguageModel
currentIndex: LanguageModel.currentLanguageIndex
ButtonGroup { ButtonGroup {
id: buttonGroup id: buttonGroup
} }
property int currentFocusIndex: 0
activeFocusOnTab: true
onActiveFocusChanged: {
if (activeFocus) {
this.currentFocusIndex = 0
this.itemAtIndex(currentFocusIndex).forceActiveFocus()
}
}
Keys.onTabPressed: {
if (currentFocusIndex < this.count - 1) {
currentFocusIndex += 1
this.itemAtIndex(currentFocusIndex).forceActiveFocus()
} else {
listViewFocusItem.forceActiveFocus()
focusItem.forceActiveFocus()
}
}
Item {
id: listViewFocusItem
Keys.onTabPressed: {
root.forceActiveFocus()
}
}
onVisibleChanged: {
if (visible) {
listViewFocusItem.forceActiveFocus()
focusItem.forceActiveFocus()
}
}
delegate: Item { delegate: Item {
implicitWidth: root.width implicitWidth: root.width
implicitHeight: delegateContent.implicitHeight implicitHeight: delegateContent.implicitHeight
onActiveFocusChanged: {
if (activeFocus) {
radioButton.forceActiveFocus()
}
}
ColumnLayout { ColumnLayout {
id: delegateContent id: delegateContent
@ -144,6 +88,32 @@ DrawerType2 {
hoverEnabled: true hoverEnabled: true
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
indicator: Rectangle { indicator: Rectangle {
width: parent.width - 1 width: parent.width - 1
height: parent.height height: parent.height
@ -190,12 +160,12 @@ DrawerType2 {
} }
ButtonGroup.group: buttonGroup ButtonGroup.group: buttonGroup
checked: listView.currentIndex === index checked: listView.selectedIndex === index
onClicked: { onClicked: {
listView.currentIndex = index listView.selectedIndex = index
LanguageModel.changeLanguage(languageIndex) LanguageModel.changeLanguage(languageIndex)
root.close() root.closeTriggered()
} }
} }
} }
@ -205,6 +175,4 @@ DrawerType2 {
} }
} }
} }
}
}
} }

View file

@ -0,0 +1,126 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import SortFilterProxyModel 0.2
import PageEnum 1.0
import ProtocolEnum 1.0
import ContainerProps 1.0
import ContainersModelFilters 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
ListView {
id: root
property int selectedIndex: ServersModel.defaultIndex
anchors.top: serversMenuHeader.bottom
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.topMargin: 16
model: ServersModel
ScrollBar.vertical: ScrollBarType {}
property bool isFocusable: true
Connections {
target: ServersModel
function onDefaultServerIndexChanged(serverIndex) {
root.selectedIndex = serverIndex
}
}
clip: true
reuseItems: true
delegate: Item {
id: menuContentDelegate
objectName: "menuContentDelegate"
property variant delegateData: model
property VerticalRadioButton serverRadioButtonProperty: serverRadioButton
implicitWidth: root.width
implicitHeight: serverRadioButtonContent.implicitHeight
ColumnLayout {
id: serverRadioButtonContent
objectName: "serverRadioButtonContent"
anchors.fill: parent
anchors.rightMargin: 16
anchors.leftMargin: 16
spacing: 0
RowLayout {
objectName: "serverRadioButtonRowLayout"
Layout.fillWidth: true
VerticalRadioButton {
id: serverRadioButton
objectName: "serverRadioButton"
Layout.fillWidth: true
text: name
descriptionText: serverDescription
checked: index === root.selectedIndex
checkable: !ConnectionController.isConnected
ButtonGroup.group: serversRadioButtonGroup
onClicked: {
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection"))
return
}
root.selectedIndex = index
ServersModel.defaultIndex = index
}
Keys.onEnterPressed: serverRadioButton.clicked()
Keys.onReturnPressed: serverRadioButton.clicked()
}
ImageButtonType {
id: serverInfoButton
objectName: "serverInfoButton"
image: "qrc:/images/controls/settings.svg"
imageColor: AmneziaStyle.color.paleGray
implicitWidth: 56
implicitHeight: 56
z: 1
onClicked: function() {
ServersModel.processedIndex = index
PageController.goToPage(PageEnum.PageSettingsServerInfo)
drawer.closeTriggered()
}
}
}
DividerType {
Layout.fillWidth: true
Layout.leftMargin: 0
Layout.rightMargin: 0
}
}
}
}

View file

@ -20,47 +20,14 @@ ListView {
height: root.contentItem.height height: root.contentItem.height
clip: true clip: true
interactive: false reuseItems: true
activeFocusOnTab: true property bool isFocusable: false
Keys.onTabPressed: {
if (currentIndex < this.count - 1) {
this.incrementCurrentIndex()
} else {
currentIndex = 0
lastItemTabClickedSignal()
}
}
onCurrentIndexChanged: {
if (visible) {
if (fl.contentHeight > fl.height) {
var item = this.currentItem
if (item.y < fl.height) {
fl.contentY = item.y
} else if (item.y + item.height > fl.contentY + fl.height) {
fl.contentY = item.y + item.height - fl.height
}
}
}
}
onVisibleChanged: {
if (visible) {
this.currentIndex = 0
}
}
delegate: Item { delegate: Item {
implicitWidth: root.width implicitWidth: root.width
implicitHeight: delegateContent.implicitHeight implicitHeight: delegateContent.implicitHeight
onActiveFocusChanged: {
if (activeFocus) {
containerRadioButton.rightButton.forceActiveFocus()
}
}
ColumnLayout { ColumnLayout {
id: delegateContent id: delegateContent

View file

@ -36,17 +36,9 @@ DrawerType2 {
configFileName = "amnezia_config" configFileName = "amnezia_config"
} }
expandedContent: Item { expandedStateContent: Item {
implicitHeight: root.expandedHeight implicitHeight: root.expandedHeight
Connections {
target: root
enabled: !GC.isMobile()
function onOpened() {
header.forceActiveFocus()
}
}
Header2Type { Header2Type {
id: header id: header
anchors.top: parent.top anchors.top: parent.top
@ -57,24 +49,27 @@ DrawerType2 {
anchors.rightMargin: 16 anchors.rightMargin: 16
headerText: root.headerText headerText: root.headerText
KeyNavigation.tab: shareButton
} }
FlickableType { ListView {
id: listView
anchors.top: header.bottom anchors.top: header.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
contentHeight: content.height + 32
ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.leftMargin: 16 property bool isFocusable: true
anchors.rightMargin: 16
ScrollBar.vertical: ScrollBarType {}
model: 1
clip: true
reuseItems: true
header: ColumnLayout {
width: listView.width
visible: root.contentVisible visible: root.contentVisible
@ -82,12 +77,12 @@ DrawerType2 {
id: shareButton id: shareButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Share") text: qsTr("Share")
leftImageSource: "qrc:/images/controls/share-2.svg" leftImageSource: "qrc:/images/controls/share-2.svg"
KeyNavigation.tab: copyConfigTextButton
clickedFunc: function() { clickedFunc: function() {
var fileName = "" var fileName = ""
if (GC.isMobile()) { if (GC.isMobile()) {
@ -111,6 +106,8 @@ DrawerType2 {
id: copyConfigTextButton id: copyConfigTextButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 8 Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
defaultColor: AmneziaStyle.color.transparent defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite hoveredColor: AmneziaStyle.color.translucentWhite
@ -124,14 +121,14 @@ DrawerType2 {
Keys.onReturnPressed: { copyConfigTextButton.clicked() } Keys.onReturnPressed: { copyConfigTextButton.clicked() }
Keys.onEnterPressed: { copyConfigTextButton.clicked() } Keys.onEnterPressed: { copyConfigTextButton.clicked() }
KeyNavigation.tab: copyNativeConfigStringButton.visible ? copyNativeConfigStringButton : showSettingsButton
} }
BasicButtonType { BasicButtonType {
id: copyNativeConfigStringButton id: copyNativeConfigStringButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 8 Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: false visible: false
@ -153,6 +150,8 @@ DrawerType2 {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24
Layout.leftMargin: 16
Layout.rightMargin: 16
defaultColor: AmneziaStyle.color.transparent defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite hoveredColor: AmneziaStyle.color.translucentWhite
@ -164,10 +163,8 @@ DrawerType2 {
text: qsTr("Show connection settings") text: qsTr("Show connection settings")
clickedFunc: function() { clickedFunc: function() {
configContentDrawer.open() configContentDrawer.openTriggered()
} }
KeyNavigation.tab: header
} }
DrawerType2 { DrawerType2 {
@ -178,30 +175,11 @@ DrawerType2 {
anchors.fill: parent anchors.fill: parent
expandedHeight: parent.height * 0.9 expandedHeight: parent.height * 0.9
onClosed: { expandedStateContent: Item {
if (!GC.isMobile()) {
header.forceActiveFocus()
}
}
expandedContent: Item {
id: configContentContainer id: configContentContainer
implicitHeight: configContentDrawer.expandedHeight implicitHeight: configContentDrawer.expandedHeight
Connections {
target: configContentDrawer
enabled: !GC.isMobile()
function onOpened() {
focusItem.forceActiveFocus()
}
}
Item {
id: focusItem
KeyNavigation.tab: backButton
}
Connections { Connections {
target: copyNativeConfigStringButton target: copyNativeConfigStringButton
function onClicked() { function onClicked() {
@ -231,9 +209,7 @@ DrawerType2 {
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 16 anchors.topMargin: 16
backButtonFunction: function() { configContentDrawer.close() } backButtonFunction: function() { configContentDrawer.closeTriggered() }
KeyNavigation.tab: focusItem
} }
FlickableType { FlickableType {
@ -302,6 +278,10 @@ DrawerType2 {
} }
} }
} }
}
delegate: ColumnLayout {
width: listView.width
Rectangle { Rectangle {
id: qrCodeContainer id: qrCodeContainer
@ -309,6 +289,8 @@ DrawerType2 {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: width Layout.preferredHeight: width
Layout.topMargin: 20 Layout.topMargin: 20
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: ExportController.qrCodesCount > 0 visible: ExportController.qrCodesCount > 0
@ -320,6 +302,32 @@ DrawerType2 {
source: ExportController.qrCodesCount ? ExportController.qrCodes[0] : "" source: ExportController.qrCodesCount ? ExportController.qrCodes[0] : ""
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
Timer { Timer {
property int index: 0 property int index: 0
interval: 1000 interval: 1000
@ -346,6 +354,8 @@ DrawerType2 {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24
Layout.bottomMargin: 32 Layout.bottomMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: ExportController.qrCodesCount > 0 visible: ExportController.qrCodesCount > 0

View file

@ -39,8 +39,6 @@ Rectangle {
implicitWidth: (rootWidth - 32) / 2 implicitWidth: (rootWidth - 32) / 2
text: "UDP" text: "UDP"
KeyNavigation.tab: tcpButton
onClicked: { onClicked: {
root.currentIndex = 0 root.currentIndex = 0
} }

View file

@ -4,7 +4,7 @@ import Qt5Compat.GraphicalEffects
import Style 1.0 import Style 1.0
Item { FocusScope {
id: root id: root
property string backButtonImage: "qrc:/images/controls/arrow-left.svg" property string backButtonImage: "qrc:/images/controls/arrow-left.svg"
@ -15,12 +15,6 @@ Item {
visible: backButtonImage !== "" visible: backButtonImage !== ""
onActiveFocusChanged: {
if (activeFocus) {
backButton.forceActiveFocus()
}
}
RowLayout { RowLayout {
id: content id: content

View file

@ -24,7 +24,7 @@ Button {
property string leftImageSource property string leftImageSource
property string rightImageSource property string rightImageSource
property string leftImageColor property string leftImageColor: textColor
property bool changeLeftImageSize: true property bool changeLeftImageSize: true
property bool squareLeftSide: false property bool squareLeftSide: false
@ -35,10 +35,35 @@ Button {
property alias buttonTextLabel: buttonText property alias buttonTextLabel: buttonText
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitHeight: 56 implicitHeight: 56
hoverEnabled: true hoverEnabled: true
focusPolicy: Qt.TabFocus
onFocusChanged: { onFocusChanged: {
if (root.activeFocus) { if (root.activeFocus) {
@ -150,7 +175,7 @@ Button {
ButtonTextType { ButtonTextType {
id: buttonText id: buttonText
color: textColor color: root.textColor
text: root.text text: root.text
visible: root.text === "" ? false : true visible: root.text === "" ? false : true

View file

@ -22,6 +22,7 @@ RadioButton {
property string pressedBorderColor: AmneziaStyle.color.softGoldenApricot property string pressedBorderColor: AmneziaStyle.color.softGoldenApricot
property string selectedBorderColor: AmneziaStyle.color.goldenApricot property string selectedBorderColor: AmneziaStyle.color.goldenApricot
property string defaultBodredColor: AmneziaStyle.color.transparent property string defaultBodredColor: AmneziaStyle.color.transparent
property string focusBorderColor: AmneziaStyle.color.paleGray
property int borderWidth: 0 property int borderWidth: 0
implicitWidth: content.implicitWidth implicitWidth: content.implicitWidth
@ -29,6 +30,32 @@ RadioButton {
hoverEnabled: true hoverEnabled: true
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
indicator: Rectangle { indicator: Rectangle {
anchors.fill: parent anchors.fill: parent
radius: 16 radius: 16
@ -52,6 +79,8 @@ RadioButton {
return pressedBorderColor return pressedBorderColor
} else if (root.checked) { } else if (root.checked) {
return selectedBorderColor return selectedBorderColor
} else if (root.activeFocus) {
return focusBorderColor
} }
} }
return defaultBodredColor return defaultBodredColor
@ -59,7 +88,7 @@ RadioButton {
border.width: { border.width: {
if (root.enabled) { if (root.enabled) {
if(root.checked) { if(root.checked || root.activeFocus) {
return 1 return 1
} }
return root.pressed ? 1 : 0 return root.pressed ? 1 : 0

View file

@ -25,10 +25,15 @@ Button {
property real textOpacity: 1.0 property real textOpacity: 1.0
property alias focusItem: rightImage
property FlickableType parentFlickable
hoverEnabled: true hoverEnabled: true
background: Rectangle { background: Rectangle {
id: backgroundRect id: backgroundRect
anchors.fill: parent anchors.fill: parent
radius: 16 radius: 16
@ -39,13 +44,31 @@ Button {
} }
} }
function ensureVisible(item) {
if (item.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
onFocusChanged: {
ensureVisible(root)
}
focusItem.onFocusChanged: {
root.ensureVisible(focusItem)
}
contentItem: Item { contentItem: Item {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
RowLayout { RowLayout {
id: content id: content
anchors.fill: parent anchors.fill: parent
Image { Image {
@ -61,6 +84,7 @@ Button {
} }
ColumnLayout { ColumnLayout {
ListItemTitleType { ListItemTitleType {
text: root.headerText text: root.headerText
visible: text !== "" visible: text !== ""
@ -123,6 +147,7 @@ Button {
Rectangle { Rectangle {
id: rightImageBackground id: rightImageBackground
anchors.fill: parent anchors.fill: parent
radius: 12 radius: 12
color: "transparent" color: "transparent"
@ -131,10 +156,9 @@ Button {
PropertyAnimation { duration: 200 } PropertyAnimation { duration: 200 }
} }
} }
onClicked: { onClicked: {
if (clickedFunction && typeof clickedFunction === "function") { root.clicked()
clickedFunction()
}
} }
} }
} }

View file

@ -9,17 +9,14 @@ import "TextTypes"
Item { Item {
id: root id: root
readonly property string drawerExpanded: "expanded" readonly property string drawerExpandedStateName: "expanded"
readonly property string drawerCollapsed: "collapsed" readonly property string drawerCollapsedStateName: "collapsed"
readonly property bool isOpened: drawerContent.state === root.drawerExpanded || (drawerContent.state === root.drawerCollapsed && dragArea.drag.active === true) readonly property bool isOpened: isExpandedStateActive() || (isCollapsedStateActive() && (dragArea.drag.active === true))
readonly property bool isClosed: drawerContent.state === root.drawerCollapsed && dragArea.drag.active === false readonly property bool isClosed: isCollapsedStateActive() && (dragArea.drag.active === false)
readonly property bool isExpanded: drawerContent.state === root.drawerExpanded property Component collapsedStateContent
readonly property bool isCollapsed: drawerContent.state === root.drawerCollapsed property Component expandedStateContent
property Component collapsedContent
property Component expandedContent
property string defaultColor: AmneziaStyle.color.onyxBlack property string defaultColor: AmneziaStyle.color.onyxBlack
property string borderColor: AmneziaStyle.color.slateGray property string borderColor: AmneziaStyle.color.slateGray
@ -29,29 +26,41 @@ Item {
property int depthIndex: 0 property int depthIndex: 0
signal entered signal cursorEntered
signal exited signal cursorExited
signal pressed(bool pressed, bool entered) signal pressed(bool pressed, bool entered)
signal aboutToHide signal aboutToHide
signal aboutToShow signal aboutToShow
signal close signal closeTriggered
signal open signal openTriggered
signal closed signal closed
signal opened signal opened
function isExpandedStateActive() {
return isStateActive(drawerExpandedStateName)
}
function isCollapsedStateActive() {
return isStateActive(drawerCollapsedStateName)
}
function isStateActive(stateName) {
return drawerContent.state === stateName
}
Connections { Connections {
target: PageController target: PageController
function onCloseTopDrawer() { function onCloseTopDrawer() {
if (depthIndex === PageController.getDrawerDepth()) { if (depthIndex === PageController.getDrawerDepth()) {
if (isCollapsed) { if (isCollapsedStateActive()) {
return return
} }
aboutToHide() aboutToHide()
drawerContent.state = root.drawerCollapsed drawerContent.state = root.drawerCollapsedStateName
depthIndex = 0 depthIndex = 0
closed() closed()
} }
@ -61,30 +70,52 @@ Item {
Connections { Connections {
target: root target: root
function onClose() { function onCloseTriggered() {
if (isCollapsed) { if (isCollapsedStateActive()) {
return return
} }
aboutToHide() aboutToHide()
drawerContent.state = root.drawerCollapsed
depthIndex = 0
PageController.setDrawerDepth(PageController.getDrawerDepth() - 1)
closed() closed()
} }
function onOpen() { function onClosed() {
if (isExpanded) { drawerContent.state = root.drawerCollapsedStateName
if (root.isCollapsedStateActive()) {
var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor()
if (initialPageNavigationBarColor !== 0xFF1C1D21) {
PageController.updateNavigationBarColor(initialPageNavigationBarColor)
}
}
depthIndex = 0
PageController.decrementDrawerDepth()
FocusController.dropRootObject(root)
}
function onOpenTriggered() {
if (root.isExpandedStateActive()) {
return return
} }
aboutToShow() root.aboutToShow()
drawerContent.state = root.drawerExpanded root.opened()
depthIndex = PageController.getDrawerDepth() + 1 }
PageController.setDrawerDepth(depthIndex)
opened() function onOpened() {
drawerContent.state = root.drawerExpandedStateName
if (isExpandedStateActive()) {
if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) {
PageController.updateNavigationBarColor(0xFF1C1D21)
}
}
depthIndex = PageController.incrementDrawerDepth()
FocusController.pushRootObject(root)
} }
} }
@ -92,7 +123,7 @@ Item {
id: background id: background
anchors.fill: parent anchors.fill: parent
color: root.isCollapsed ? AmneziaStyle.color.transparent : AmneziaStyle.color.translucentMidnightBlack color: root.isCollapsedStateActive() ? AmneziaStyle.color.transparent : AmneziaStyle.color.translucentMidnightBlack
Behavior on color { Behavior on color {
PropertyAnimation { duration: 200 } PropertyAnimation { duration: 200 }
@ -102,18 +133,17 @@ Item {
MouseArea { MouseArea {
id: emptyArea id: emptyArea
anchors.fill: parent anchors.fill: parent
enabled: root.isExpanded
visible: enabled
onClicked: { onClicked: {
root.close() root.closeTriggered()
} }
} }
MouseArea { MouseArea {
id: dragArea id: dragArea
objectName: "dragArea"
anchors.fill: drawerContentBackground anchors.fill: drawerContentBackground
cursorShape: root.isCollapsed ? Qt.PointingHandCursor : Qt.ArrowCursor
hoverEnabled: true hoverEnabled: true
enabled: drawerContent.implicitHeight > 0 enabled: drawerContent.implicitHeight > 0
@ -125,35 +155,36 @@ Item {
/** If drag area is released at any point other than min or max y, transition to the other state */ /** If drag area is released at any point other than min or max y, transition to the other state */
onReleased: { onReleased: {
if (root.isCollapsed && drawerContent.y < dragArea.drag.maximumY) { if (isCollapsedStateActive() && drawerContent.y < dragArea.drag.maximumY) {
root.open() root.openTriggered()
return return
} }
if (root.isExpanded && drawerContent.y > dragArea.drag.minimumY) { if (isExpandedStateActive() && drawerContent.y > dragArea.drag.minimumY) {
root.close() root.closeTriggered()
return return
} }
} }
onEntered: { onEntered: {
root.entered() root.cursorEntered()
} }
onExited: { onExited: {
root.exited() root.cursorExited()
} }
onPressedChanged: { onPressedChanged: {
root.pressed(pressed, entered) root.pressed(pressed, entered)
} }
onClicked: { onClicked: {
if (root.isCollapsed) { if (isCollapsedStateActive()) {
root.open() root.openTriggered()
} }
} }
} }
Rectangle { Rectangle {
id: drawerContentBackground id: drawerContentBackground
objectName: "drawerContentBackground"
anchors { left: drawerContent.left; right: drawerContent.right; top: drawerContent.top } anchors { left: drawerContent.left; right: drawerContent.right; top: drawerContent.top }
height: root.height height: root.height
@ -174,53 +205,80 @@ Item {
Item { Item {
id: drawerContent id: drawerContent
objectName: "drawerContent"
Drag.active: dragArea.drag.active Drag.active: dragArea.drag.active
anchors.right: root.right anchors.right: root.right
anchors.left: root.left anchors.left: root.left
y: root.height - drawerContent.height
state: root.drawerCollapsed
implicitHeight: root.isCollapsed ? collapsedHeight : expandedHeight state: root.drawerCollapsedStateName
onStateChanged: {
if (root.isCollapsed) {
var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor()
if (initialPageNavigationBarColor !== 0xFF1C1D21) {
PageController.updateNavigationBarColor(initialPageNavigationBarColor)
}
return
}
if (root.isExpanded) {
if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) {
PageController.updateNavigationBarColor(0xFF1C1D21)
}
return
}
}
states: [ states: [
State { State {
name: root.drawerCollapsed name: root.drawerCollapsedStateName
PropertyChanges { PropertyChanges {
target: drawerContent target: drawerContent
implicitHeight: collapsedHeight
y: root.height - root.collapsedHeight y: root.height - root.collapsedHeight
} }
PropertyChanges {
target: background
color: AmneziaStyle.color.transparent
}
PropertyChanges {
target: dragArea
cursorShape: Qt.PointingHandCursor
}
PropertyChanges {
target: emptyArea
enabled: false
visible: false
}
PropertyChanges {
target: collapsedLoader
// visible: true
}
PropertyChanges {
target: expandedLoader
visible: false
}
}, },
State { State {
name: root.drawerExpanded name: root.drawerExpandedStateName
PropertyChanges { PropertyChanges {
target: drawerContent target: drawerContent
implicitHeight: expandedHeight
y: dragArea.drag.minimumY y: dragArea.drag.minimumY
}
PropertyChanges {
target: background
color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
}
PropertyChanges {
target: dragArea
cursorShape: Qt.ArrowCursor
}
PropertyChanges {
target: emptyArea
enabled: true
visible: true
}
PropertyChanges {
target: collapsedLoader
// visible: false
}
PropertyChanges {
target: expandedLoader
visible: true
} }
} }
] ]
transitions: [ transitions: [
Transition { Transition {
from: root.drawerCollapsed from: root.drawerCollapsedStateName
to: root.drawerExpanded to: root.drawerExpandedStateName
PropertyAnimation { PropertyAnimation {
target: drawerContent target: drawerContent
properties: "y" properties: "y"
@ -228,8 +286,8 @@ Item {
} }
}, },
Transition { Transition {
from: root.drawerExpanded from: root.drawerExpandedStateName
to: root.drawerCollapsed to: root.drawerCollapsedStateName
PropertyAnimation { PropertyAnimation {
target: drawerContent target: drawerContent
properties: "y" properties: "y"
@ -241,7 +299,7 @@ Item {
Loader { Loader {
id: collapsedLoader id: collapsedLoader
sourceComponent: root.collapsedContent sourceComponent: root.collapsedStateContent
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
@ -250,8 +308,7 @@ Item {
Loader { Loader {
id: expandedLoader id: expandedLoader
visible: root.isExpanded sourceComponent: root.expandedStateContent
sourceComponent: root.expandedContent
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left

View file

@ -45,33 +45,56 @@ Item {
property Item drawerParent property Item drawerParent
property Component listView property Component listView
signal open signal openTriggered
signal close signal closeTriggered
function popupClosedFunc() { readonly property bool isFocusable: true
if (!GC.isMobile()) {
this.forceActiveFocus() Keys.onTabPressed: {
} FocusController.nextKeyTabItem()
} }
property var parentFlickable Keys.onBacktabPressed: {
onFocusChanged: { FocusController.previousKeyTabItem()
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
} }
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
} }
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
} }
implicitWidth: rootButtonContent.implicitWidth implicitWidth: rootButtonContent.implicitWidth
implicitHeight: rootButtonContent.implicitHeight implicitHeight: rootButtonContent.implicitHeight
onOpen: { onOpenTriggered: {
menu.open() menu.openTriggered()
} }
onClose: { onCloseTriggered: {
menu.close() menu.closeTriggered()
}
Keys.onEnterPressed: {
if (menu.isClosed) {
menu.openTriggered()
}
}
Keys.onReturnPressed: {
if (menu.isClosed) {
menu.openTriggered()
}
} }
Rectangle { Rectangle {
@ -173,7 +196,7 @@ Item {
if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") { if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") {
rootButtonClickedFunction() rootButtonClickedFunction()
} else { } else {
menu.open() menu.openTriggered()
} }
} }
} }
@ -186,93 +209,38 @@ Item {
anchors.fill: parent anchors.fill: parent
expandedHeight: drawerParent.height * drawerHeight expandedHeight: drawerParent.height * drawerHeight
onClosed: { expandedStateContent: Item {
root.popupClosedFunc()
}
expandedContent: Item {
id: container id: container
implicitHeight: menu.expandedHeight implicitHeight: menu.expandedHeight
Connections {
target: menu
enabled: !GC.isMobile()
function onOpened() {
focusItem.forceActiveFocus()
}
}
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: header id: header
anchors.top: parent.top anchors.fill: parent
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16 anchors.topMargin: 16
BackButtonType { BackButtonType {
id: backButton id: backButton
backButtonImage: root.headerBackButtonImage backButtonImage: root.headerBackButtonImage
backButtonFunction: function() { menu.close() } backButtonFunction: function() { menu.closeTriggered() }
KeyNavigation.tab: listViewLoader.item
} }
}
FlickableType {
id: flickable
anchors.top: header.bottom
anchors.topMargin: 16
contentHeight: col.implicitHeight
Column {
id: col
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 16
Header2Type { Header2Type {
anchors.left: parent.left Layout.leftMargin: 16
anchors.right: parent.right Layout.rightMargin: 16
anchors.leftMargin: 16 Layout.bottomMargin: 16
anchors.rightMargin: 16 Layout.fillWidth: true
headerText: root.headerText headerText: root.headerText
width: parent.width
} }
Loader { Loader {
id: listViewLoader id: listViewLoader
sourceComponent: root.listView sourceComponent: root.listView
onLoaded: { Layout.fillHeight: true
listViewLoader.item.parentFlickable = flickable
listViewLoader.item.lastItemTabClicked = function() {
focusItem.forceActiveFocus()
} }
} }
} }
} }
}
}
}
Keys.onEnterPressed: {
if (menu.isClosed) {
menu.open()
}
}
Keys.onReturnPressed: {
if (menu.isClosed) {
menu.open()
}
}
} }

View file

@ -7,10 +7,11 @@ Flickable {
function ensureVisible(item) { function ensureVisible(item) {
if (item.y < fl.contentY) { if (item.y < fl.contentY) {
fl.contentY = item.y fl.contentY = item.y - 40 // 40 is a top margin
} else if (item.y + item.height > fl.contentY + fl.height) { } else if (item.y + item.height > fl.contentY + fl.height) {
fl.contentY = item.y + item.height - fl.height + 40 // 40 is a bottom margin fl.contentY = item.y + item.height - fl.height + 40 // 40 is a bottom margin
} }
fl.returnToBounds()
} }
clip: true clip: true
@ -24,7 +25,7 @@ Flickable {
Keys.onUpPressed: scrollBar.decrease() Keys.onUpPressed: scrollBar.decrease()
Keys.onDownPressed: scrollBar.increase() Keys.onDownPressed: scrollBar.increase()
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBarType {
id: scrollBar id: scrollBar
policy: fl.height >= fl.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn policy: fl.height >= fl.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn
} }

View file

@ -19,8 +19,6 @@ Item {
property string descriptionText property string descriptionText
focus: true
implicitWidth: content.implicitWidth implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight

View file

@ -27,6 +27,32 @@ RadioButton {
implicitWidth: content.implicitWidth implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
indicator: Rectangle { indicator: Rectangle {
anchors.fill: parent anchors.fill: parent
radius: 16 radius: 16

View file

@ -24,22 +24,39 @@ Button {
property int borderFocusedWidth: 1 property int borderFocusedWidth: 1
hoverEnabled: true hoverEnabled: true
focus: true
focusPolicy: Qt.TabFocus
icon.source: image icon.source: image
icon.color: root.enabled ? imageColor : disableImageColor icon.color: root.enabled ? imageColor : disableImageColor
property Flickable parentFlickable property bool isFocusable: true
onFocusChanged: { Keys.onTabPressed: {
if (root.activeFocus) { FocusController.nextKeyTabItem()
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(this)
} }
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
} }
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
} }
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
Keys.onEnterPressed: root.clicked()
Keys.onReturnPressed: root.clicked()
Behavior on icon.color { Behavior on icon.color {
PropertyAnimation { duration: 200 } PropertyAnimation { duration: 200 }
} }

View file

@ -41,6 +41,32 @@ Item {
property bool descriptionOnTop: false property bool descriptionOnTop: false
property bool hideDescription: true property bool hideDescription: true
property bool isFocusable: !(eyeImage.visible || rightImage.visible) // TODO: this component already has focusable items
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin
implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin

View file

@ -17,7 +17,7 @@ ListView {
property bool dividerVisible: false property bool dividerVisible: false
currentIndex: 0 property int selectedIndex: 0
width: rootWidth width: rootWidth
height: menuContent.contentItem.height height: menuContent.contentItem.height
@ -45,7 +45,7 @@ ListView {
rightImageSource: imageSource rightImageSource: imageSource
clickedFunction: function() { clickedFunction: function() {
menuContent.currentIndex = index menuContent.selectedIndex = index
menuContent.selectedText = name menuContent.selectedText = name
if (menuContent.clickedFunction && typeof menuContent.clickedFunction === "function") { if (menuContent.clickedFunction && typeof menuContent.clickedFunction === "function") {
menuContent.clickedFunction() menuContent.clickedFunction()
@ -62,7 +62,7 @@ ListView {
} }
Component.onCompleted: { Component.onCompleted: {
if (menuContent.currentIndex === index) { if (menuContent.selectedIndex === index) {
menuContent.selectedText = name menuContent.selectedText = name
} }
} }

View file

@ -20,79 +20,33 @@ ListView {
property var clickedFunction property var clickedFunction
currentIndex: 0 property int selectedIndex: 0
width: rootWidth width: rootWidth
height: root.contentItem.height height: root.contentItem.height
clip: true clip: true
interactive: false reuseItems: true
property FlickableType parentFlickable property bool isFocusable: true
property var lastItemTabClicked
property int currentFocusIndex: 0 ScrollBar.vertical: ScrollBarType {}
activeFocusOnTab: true
onActiveFocusChanged: {
if (activeFocus) {
this.currentFocusIndex = 0
this.itemAtIndex(currentFocusIndex).forceActiveFocus()
}
}
Keys.onTabPressed: {
if (currentFocusIndex < this.count - 1) {
currentFocusIndex += 1
} else {
currentFocusIndex = 0
}
this.itemAtIndex(currentFocusIndex).forceActiveFocus()
}
Item {
id: focusItem
Keys.onTabPressed: {
root.forceActiveFocus()
}
}
onVisibleChanged: {
if (visible) {
focusItem.forceActiveFocus()
}
}
onCurrentFocusIndexChanged: {
if (parentFlickable) {
parentFlickable.ensureVisible(this.itemAtIndex(currentFocusIndex))
}
}
ButtonGroup { ButtonGroup {
id: buttonGroup id: buttonGroup
} }
function triggerCurrentItem() { function triggerCurrentItem() {
var item = root.itemAtIndex(currentIndex) var item = root.itemAtIndex(selectedIndex)
var radioButton = item.children[0].children[0] item.selectable.clicked()
radioButton.clicked()
} }
delegate: Item { delegate: ColumnLayout {
implicitWidth: rootWidth
implicitHeight: content.implicitHeight
onActiveFocusChanged: {
if (activeFocus) {
radioButton.forceActiveFocus()
}
}
ColumnLayout {
id: content id: content
anchors.fill: parent property alias selectable: radioButton
implicitWidth: rootWidth
RadioButton { RadioButton {
id: radioButton id: radioButton
@ -102,6 +56,32 @@ ListView {
hoverEnabled: true hoverEnabled: true
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
indicator: Rectangle { indicator: Rectangle {
width: parent.width - 1 width: parent.width - 1
height: parent.height height: parent.height
@ -155,20 +135,19 @@ ListView {
} }
ButtonGroup.group: buttonGroup ButtonGroup.group: buttonGroup
checked: root.currentIndex === index checked: root.selectedIndex === index
onClicked: { onClicked: {
root.currentIndex = index root.selectedIndex = index
root.selectedText = name root.selectedText = name
if (clickedFunction && typeof clickedFunction === "function") { if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction() clickedFunction()
} }
} }
} }
}
Component.onCompleted: { Component.onCompleted: {
if (root.currentIndex === index) { if (root.selectedIndex === index) {
root.selectedText = name root.selectedText = name
} }
} }

View file

@ -9,53 +9,21 @@ Item {
property StackView stackView: StackView.view property StackView stackView: StackView.view
property var defaultActiveFocusItem: null
onVisibleChanged: { onVisibleChanged: {
if (visible && !GC.isMobile()) { if (visible) {
timer.start() timer.start()
} }
} }
function lastItemTabClicked(focusItem) {
if (GC.isMobile()) {
return
}
if (focusItem) {
focusItem.forceActiveFocus()
PageController.forceTabBarActiveFocus()
} else {
if (defaultActiveFocusItem) {
defaultActiveFocusItem.forceActiveFocus()
}
PageController.forceTabBarActiveFocus()
}
}
// MouseArea {
// id: globalMouseArea
// z: 99
// anchors.fill: parent
// enabled: true
// onPressed: function(mouse) {
// forceActiveFocus()
// mouse.accepted = false
// }
// }
// Set a timer to set focus after a short delay // Set a timer to set focus after a short delay
Timer { Timer {
id: timer id: timer
interval: 100 // Milliseconds interval: 200 // Milliseconds
onTriggered: { onTriggered: {
if (defaultActiveFocusItem) { FocusController.resetRootObject()
defaultActiveFocusItem.forceActiveFocus() FocusController.setFocusOnDefaultItem()
}
} }
repeat: false // Stop the timer after one trigger repeat: false // Stop the timer after one trigger
running: !GC.isMobile() // Start the timer running: true // Start the timer
} }
} }

View file

@ -5,6 +5,7 @@ import QtQuick.Layouts
import Style 1.0 import Style 1.0
import "TextTypes" import "TextTypes"
import "../Config"
Popup { Popup {
id: root id: root
@ -28,11 +29,11 @@ Popup {
} }
onOpened: { onOpened: {
focusItem.forceActiveFocus() timer.start()
} }
onClosed: { onClosed: {
PageController.forceStackActiveFocus() FocusController.dropRootObject(root)
} }
background: Rectangle { background: Rectangle {
@ -42,6 +43,17 @@ Popup {
radius: 4 radius: 4
} }
Timer {
id: timer
interval: 200 // Milliseconds
onTriggered: {
FocusController.pushRootObject(root)
FocusController.setFocusItem(closeButton)
}
repeat: false // Stop the timer after one trigger
running: true // Start the timer
}
contentItem: Item { contentItem: Item {
implicitWidth: content.implicitWidth implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
@ -72,11 +84,6 @@ Popup {
} }
} }
Item {
id: focusItem
KeyNavigation.tab: closeButton
}
BasicButtonType { BasicButtonType {
id: closeButton id: closeButton
visible: closeButtonVisible visible: closeButtonVisible
@ -92,7 +99,6 @@ Popup {
borderWidth: 0 borderWidth: 0
text: qsTr("Close") text: qsTr("Close")
KeyNavigation.tab: focusItem
clickedFunc: function() { clickedFunc: function() {
root.close() root.close()

View file

@ -0,0 +1,11 @@
import QtQuick
import QtQuick.Controls
import "./"
import "../Controls2"
ScrollBar {
id: root
policy: parent.height >= parent.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn
}

View file

@ -35,10 +35,37 @@ Switch {
property string hoveredIndicatorBackgroundColor: AmneziaStyle.color.translucentWhite property string hoveredIndicatorBackgroundColor: AmneziaStyle.color.translucentWhite
property string defaultIndicatorBackgroundColor: AmneziaStyle.color.transparent property string defaultIndicatorBackgroundColor: AmneziaStyle.color.transparent
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
hoverEnabled: enabled ? true : false hoverEnabled: enabled ? true : false
focusPolicy: Qt.TabFocus focusPolicy: Qt.TabFocus
property FlickableType parentFlickable: null property FlickableType parentFlickable: null
onFocusChanged: { onFocusChanged: {
if (root.activeFocus) { if (root.activeFocus) {
if (root.parentFlickable) { if (root.parentFlickable) {
@ -131,13 +158,15 @@ Switch {
enabled: false enabled: false
} }
Keys.onEnterPressed: { Keys.onEnterPressed: event => handleSwitch(event)
Keys.onReturnPressed: event => handleSwitch(event)
Keys.onSpacePressed: event => handleSwitch(event)
function handleSwitch(event) {
if (!event.isAutoRepeat) {
root.checked = !root.checked root.checked = !root.checked
root.checkedChanged() root.checkedChanged()
} }
event.accepted = true
Keys.onReturnPressed: {
root.checked = !root.checked
root.checkedChanged()
} }
} }

View file

@ -17,10 +17,35 @@ TabButton {
property bool isSelected: false property bool isSelected: false
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitHeight: 48 implicitHeight: 48
hoverEnabled: true hoverEnabled: true
focusPolicy: Qt.TabFocus
background: Rectangle { background: Rectangle {
id: background id: background

View file

@ -14,13 +14,38 @@ TabButton {
property bool isSelected: false property bool isSelected: false
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
property string borderFocusedColor: AmneziaStyle.color.paleGray property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderFocusedWidth: 1 property int borderFocusedWidth: 1
property var clickedFunc property var clickedFunc
hoverEnabled: true hoverEnabled: true
focusPolicy: Qt.TabFocus
icon.source: image icon.source: image
icon.color: isSelected ? selectedColor : defaultColor icon.color: isSelected ? selectedColor : defaultColor

View file

@ -78,9 +78,6 @@ Rectangle {
placeholderText: root.placeholderText placeholderText: root.placeholderText
text: root.text text: root.text
KeyNavigation.tab: firstButton
onCursorVisibleChanged: { onCursorVisibleChanged: {
if (textArea.cursorVisible) { if (textArea.cursorVisible) {
fl.interactive = true fl.interactive = true

View file

@ -40,6 +40,7 @@ Item {
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
property FlickableType parentFlickable property FlickableType parentFlickable
Connections { Connections {
target: textField target: textField
function onFocusChanged() { function onFocusChanged() {
@ -84,7 +85,16 @@ Item {
TextField { TextField {
id: textField id: textField
activeFocusOnTab: false
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
enabled: root.textFieldEditable enabled: root.textFieldEditable
color: root.enabled ? root.textFieldTextColor : root.textFieldTextDisabledColor color: root.enabled ? root.textFieldTextColor : root.textFieldTextDisabledColor
@ -209,9 +219,9 @@ Item {
clickedFunc() clickedFunc()
} }
if (KeyNavigation.tab) { // if (KeyNavigation.tab) {
KeyNavigation.tab.forceActiveFocus(); // KeyNavigation.tab.forceActiveFocus();
} // }
} }
Keys.onReturnPressed: { Keys.onReturnPressed: {
@ -219,8 +229,8 @@ Item {
clickedFunc() clickedFunc()
} }
if (KeyNavigation.tab) { // if (KeyNavigation.tab) {
KeyNavigation.tab.forceActiveFocus(); // KeyNavigation.tab.forceActiveFocus();
} // }
} }
} }

View file

@ -28,8 +28,33 @@ RadioButton {
property string imageSource property string imageSource
property bool showImage property bool showImage
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
hoverEnabled: true hoverEnabled: true
focusPolicy: Qt.TabFocus
indicator: Rectangle { indicator: Rectangle {
id: background id: background

View file

@ -26,5 +26,6 @@ QtObject {
readonly property color softGoldenApricot: Qt.rgba(251/255, 178/255, 106/255, 0.3) readonly property color softGoldenApricot: Qt.rgba(251/255, 178/255, 106/255, 0.3)
readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8) readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8)
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65) readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
readonly property color pearlGray: '#EAEAEC'
} }
} }

View file

@ -16,40 +16,28 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout {
id: backButtonLayout
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20
BackButtonType { BackButtonType {
id: backButton id: backButton
// KeyNavigation.tab: removeButton
}
}
FlickableType {
id: fl
anchors.top: backButtonLayout.bottom
anchors.bottom: parent.bottom
contentHeight: content.implicitHeight
ColumnLayout {
id: content
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20
}
ListView {
id: listView
anchors.top: backButton.bottom
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
property bool isFocusable: true
ScrollBar.vertical: ScrollBarType {}
header: ColumnLayout {
width: listView.width
HeaderType { HeaderType {
id: header id: header
@ -60,7 +48,14 @@ PageType {
headerText: "Dev menu" headerText: "Dev menu"
} }
}
model: 1
clip: true
spacing: 16
delegate: ColumnLayout {
width: listView.width
TextFieldWithHeaderType { TextFieldWithHeaderType {
id: passwordTextField id: passwordTextField
@ -69,7 +64,6 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
parentFlickable: fl
headerText: qsTr("Gateway endpoint") headerText: qsTr("Gateway endpoint")
textFieldText: SettingsController.gatewayEndpoint textFieldText: SettingsController.gatewayEndpoint
@ -86,17 +80,19 @@ PageType {
SettingsController.gatewayEndpoint = textFieldText SettingsController.gatewayEndpoint = textFieldText
} }
} }
// KeyNavigation.tab: saveButton
} }
}
footer: ColumnLayout {
width: listView.width
SwitcherType { SwitcherType {
id: switcher id: switcher
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.topMargin: 16
text: qsTr("Dev gateway environment") text: qsTr("Dev gateway environment")
checked: SettingsController.isDevGatewayEnv checked: SettingsController.isDevGatewayEnv

View file

@ -1,6 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
@ -19,13 +20,13 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
Connections { Connections {
objectName: "pageControllerConnections"
target: PageController target: PageController
function onRestorePageHomeState(isContainerInstalled) { function onRestorePageHomeState(isContainerInstalled) {
drawer.open() drawer.openTriggered()
if (isContainerInstalled) { if (isContainerInstalled) {
containersDropDown.rootButtonClickedFunction() containersDropDown.rootButtonClickedFunction()
} }
@ -33,23 +34,32 @@ PageType {
} }
Item { Item {
objectName: "homeColumnItem"
anchors.fill: parent anchors.fill: parent
anchors.bottomMargin: drawer.collapsedHeight anchors.bottomMargin: drawer.collapsedHeight
ColumnLayout { ColumnLayout {
anchors.fill: parent objectName: "homeColumnLayout"
anchors.topMargin: 34
anchors.bottomMargin: 34
Item { anchors.fill: parent
id: focusItem anchors.topMargin: 12
KeyNavigation.tab: loggingButton.visible ? anchors.bottomMargin: 16
loggingButton :
connectButton AdLabel {
id: adLabel
Layout.fillWidth: true
Layout.preferredHeight: adLabel.contentHeight
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 22
} }
BasicButtonType { BasicButtonType {
id: loggingButton id: loggingButton
objectName: "loggingButton"
property bool isLoggingEnabled: SettingsController.isLoggingEnabled property bool isLoggingEnabled: SettingsController.isLoggingEnabled
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -69,8 +79,6 @@ PageType {
Keys.onEnterPressed: loggingButton.clicked() Keys.onEnterPressed: loggingButton.clicked()
Keys.onReturnPressed: loggingButton.clicked() Keys.onReturnPressed: loggingButton.clicked()
KeyNavigation.tab: connectButton
onClicked: { onClicked: {
PageController.goToPage(PageEnum.PageSettingsLogging) PageController.goToPage(PageEnum.PageSettingsLogging)
} }
@ -78,16 +86,17 @@ PageType {
ConnectButton { ConnectButton {
id: connectButton id: connectButton
objectName: "connectButton"
Layout.fillHeight: true Layout.fillHeight: true
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
KeyNavigation.tab: splitTunnelingButton
} }
BasicButtonType { BasicButtonType {
id: splitTunnelingButton id: splitTunnelingButton
objectName: "splitTunnelingButton"
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
Layout.bottomMargin: 34
leftPadding: 16 leftPadding: 16
rightPadding: 16 rightPadding: 16
@ -110,58 +119,43 @@ PageType {
text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled") text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled")
leftImageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : "" leftImageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : ""
leftImageColor: ""
rightImageSource: "qrc:/images/controls/chevron-down.svg" rightImageSource: "qrc:/images/controls/chevron-down.svg"
Keys.onEnterPressed: splitTunnelingButton.clicked() Keys.onEnterPressed: splitTunnelingButton.clicked()
Keys.onReturnPressed: splitTunnelingButton.clicked() Keys.onReturnPressed: splitTunnelingButton.clicked()
KeyNavigation.tab: drawer
onClicked: { onClicked: {
homeSplitTunnelingDrawer.open() homeSplitTunnelingDrawer.openTriggered()
} }
HomeSplitTunnelingDrawer { HomeSplitTunnelingDrawer {
id: homeSplitTunnelingDrawer id: homeSplitTunnelingDrawer
objectName: "homeSplitTunnelingDrawer"
parent: root parent: root
onClosed: {
if (!GC.isMobile()) {
focusItem.forceActiveFocus()
} }
} }
} }
} }
}
}
DrawerType2 { DrawerType2 {
id: drawer id: drawer
objectName: "drawerProtocol"
anchors.fill: parent anchors.fill: parent
onClosed: { collapsedStateContent: Item {
if (!GC.isMobile()) { objectName: "ProtocolDrawerCollapsedContent"
focusItem.forceActiveFocus()
}
}
collapsedContent: Item {
implicitHeight: Qt.platform.os !== "ios" ? root.height * 0.9 : screen.height * 0.77 implicitHeight: Qt.platform.os !== "ios" ? root.height * 0.9 : screen.height * 0.77
Component.onCompleted: { Component.onCompleted: {
drawer.expandedHeight = implicitHeight drawer.expandedHeight = implicitHeight
} }
Connections {
target: drawer
enabled: !GC.isMobile()
function onActiveFocusChanged() {
if (drawer.activeFocus && !drawer.isOpened) {
collapsedButtonChevron.forceActiveFocus()
}
}
}
ColumnLayout { ColumnLayout {
id: collapsed id: collapsed
objectName: "collapsedColumnLayout"
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
@ -180,6 +174,8 @@ PageType {
} }
RowLayout { RowLayout {
objectName: "rowLayout"
Layout.topMargin: 14 Layout.topMargin: 14
Layout.leftMargin: 24 Layout.leftMargin: 24
Layout.rightMargin: 24 Layout.rightMargin: 24
@ -188,9 +184,11 @@ PageType {
spacing: 0 spacing: 0
Connections { Connections {
objectName: "drawerConnections"
target: drawer target: drawer
function onEntered() { function onCursorEntered() {
if (drawer.isCollapsed) { if (drawer.isCollapsedStateActive) {
collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor
collapsedButtonHeader.opacity = 0.8 collapsedButtonHeader.opacity = 0.8
} else { } else {
@ -198,8 +196,8 @@ PageType {
} }
} }
function onExited() { function onCursorExited() {
if (drawer.isCollapsed) { if (drawer.isCollapsedStateActive) {
collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor
collapsedButtonHeader.opacity = 1 collapsedButtonHeader.opacity = 1
} else { } else {
@ -208,7 +206,7 @@ PageType {
} }
function onPressed(pressed, entered) { function onPressed(pressed, entered) {
if (drawer.isCollapsed) { if (drawer.isCollapsedStateActive) {
collapsedButtonChevron.backgroundColor = pressed ? collapsedButtonChevron.pressedColor : entered ? collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor collapsedButtonChevron.backgroundColor = pressed ? collapsedButtonChevron.pressedColor : entered ? collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor
collapsedButtonHeader.opacity = 0.7 collapsedButtonHeader.opacity = 0.7
} else { } else {
@ -219,6 +217,8 @@ PageType {
Header1TextType { Header1TextType {
id: collapsedButtonHeader id: collapsedButtonHeader
objectName: "collapsedButtonHeader"
Layout.maximumWidth: drawer.width - 48 - 18 - 12 Layout.maximumWidth: drawer.width - 48 - 18 - 12
maximumLineCount: 2 maximumLineCount: 2
@ -227,8 +227,6 @@ PageType {
text: ServersModel.defaultServerName text: ServersModel.defaultServerName
horizontalAlignment: Qt.AlignHCenter horizontalAlignment: Qt.AlignHCenter
KeyNavigation.tab: tabBar
Behavior on opacity { Behavior on opacity {
PropertyAnimation { duration: 200 } PropertyAnimation { duration: 200 }
} }
@ -236,10 +234,11 @@ PageType {
ImageButtonType { ImageButtonType {
id: collapsedButtonChevron id: collapsedButtonChevron
objectName: "collapsedButtonChevron"
Layout.leftMargin: 8 Layout.leftMargin: 8
visible: drawer.isCollapsed visible: drawer.isCollapsedStateActive()
hoverEnabled: false hoverEnabled: false
image: "qrc:/images/controls/chevron-down.svg" image: "qrc:/images/controls/chevron-down.svg"
@ -254,25 +253,24 @@ PageType {
Keys.onEnterPressed: collapsedButtonChevron.clicked() Keys.onEnterPressed: collapsedButtonChevron.clicked()
Keys.onReturnPressed: collapsedButtonChevron.clicked() Keys.onReturnPressed: collapsedButtonChevron.clicked()
Keys.onTabPressed: lastItemTabClicked()
onClicked: { onClicked: {
if (drawer.isCollapsed) { if (drawer.isCollapsedStateActive()) {
drawer.open() drawer.openTriggered()
} }
} }
} }
} }
RowLayout { RowLayout {
objectName: "rowLayoutLabel"
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.topMargin: 8 Layout.topMargin: 8
Layout.bottomMargin: drawer.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16 Layout.bottomMargin: drawer.isCollapsedStateActive ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16
spacing: 0 spacing: 0
BasicButtonType { BasicButtonType {
enabled: (ServersModel.defaultServerImagePathCollapsed !== "") && drawer.isCollapsed enabled: (ServersModel.defaultServerImagePathCollapsed !== "") && drawer.isCollapsedStateActive
hoverEnabled: enabled hoverEnabled: enabled
implicitHeight: 36 implicitHeight: 36
@ -290,8 +288,9 @@ PageType {
buttonTextLabel.font.pixelSize: 13 buttonTextLabel.font.pixelSize: 13
buttonTextLabel.font.weight: 400 buttonTextLabel.font.weight: 400
text: drawer.isCollapsed ? ServersModel.defaultServerDescriptionCollapsed : ServersModel.defaultServerDescriptionExpanded text: drawer.isCollapsedStateActive ? ServersModel.defaultServerDescriptionCollapsed : ServersModel.defaultServerDescriptionExpanded
leftImageSource: ServersModel.defaultServerImagePathCollapsed leftImageSource: ServersModel.defaultServerImagePathCollapsed
leftImageColor: ""
changeLeftImageSize: false changeLeftImageSize: false
rightImageSource: hoverEnabled ? "qrc:/images/controls/chevron-down.svg" : "" rightImageSource: hoverEnabled ? "qrc:/images/controls/chevron-down.svg" : ""
@ -304,18 +303,9 @@ PageType {
} }
} }
Connections {
target: drawer
enabled: !GC.isMobile()
function onIsCollapsedChanged() {
if (!drawer.isCollapsed) {
focusItem1.forceActiveFocus()
}
}
}
ColumnLayout { ColumnLayout {
id: serversMenuHeader id: serversMenuHeader
objectName: "serversMenuHeader"
anchors.top: collapsed.bottom anchors.top: collapsed.bottom
anchors.right: parent.right anchors.right: parent.right
@ -327,13 +317,9 @@ PageType {
visible: !ServersModel.isDefaultServerFromApi visible: !ServersModel.isDefaultServerFromApi
Item {
id: focusItem1
KeyNavigation.tab: containersDropDown
}
DropDownType { DropDownType {
id: containersDropDown id: containersDropDown
objectName: "containersDropDown"
rootButtonImageColor: AmneziaStyle.color.midnightBlack rootButtonImageColor: AmneziaStyle.color.midnightBlack
rootButtonBackgroundColor: AmneziaStyle.color.paleGray rootButtonBackgroundColor: AmneziaStyle.color.paleGray
@ -344,28 +330,28 @@ PageType {
rootButtonTextTopMargin: 8 rootButtonTextTopMargin: 8
rootButtonTextBottomMargin: 8 rootButtonTextBottomMargin: 8
enabled: drawer.isOpened
text: ServersModel.defaultServerDefaultContainerName text: ServersModel.defaultServerDefaultContainerName
textColor: AmneziaStyle.color.midnightBlack textColor: AmneziaStyle.color.midnightBlack
headerText: qsTr("VPN protocol") headerText: qsTr("VPN protocol")
headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" headerBackButtonImage: "qrc:/images/controls/arrow-left.svg"
rootButtonClickedFunction: function() { rootButtonClickedFunction: function() {
containersDropDown.open() containersDropDown.openTriggered()
} }
drawerParent: root drawerParent: root
KeyNavigation.tab: serversMenuContent
listView: HomeContainersListView { listView: HomeContainersListView {
id: containersListView id: containersListView
objectName: "containersListView"
rootWidth: root.width rootWidth: root.width
onVisibleChanged: {
if (containersDropDown.visible && !GC.isMobile()) {
focusItem1.forceActiveFocus()
}
}
Connections { Connections {
objectName: "rowLayoutConnections"
target: ServersModel target: ServersModel
function onDefaultServerIndexChanged() { function onDefaultServerIndexChanged() {
@ -407,167 +393,21 @@ PageType {
ButtonGroup { ButtonGroup {
id: serversRadioButtonGroup id: serversRadioButtonGroup
objectName: "serversRadioButtonGroup"
} }
ListView { ServersListView {
id: serversMenuContent id: serversMenuContent
objectName: "serversMenuContent"
anchors.top: serversMenuHeader.bottom isFocusable: false
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.topMargin: 16
model: ServersModel
currentIndex: ServersModel.defaultIndex
ScrollBar.vertical: ScrollBar {
id: scrollBar
policy: serversMenuContent.height >= serversMenuContent.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn
}
activeFocusOnTab: true
focus: true
property int focusItemIndex: 0
onActiveFocusChanged: {
if (activeFocus) {
serversMenuContent.focusItemIndex = 0
serversMenuContent.itemAtIndex(focusItemIndex).forceActiveFocus()
}
}
onFocusItemIndexChanged: {
const focusedElement = serversMenuContent.itemAtIndex(focusItemIndex)
if (focusedElement) {
if (focusedElement.y + focusedElement.height > serversMenuContent.height) {
serversMenuContent.contentY = focusedElement.y + focusedElement.height - serversMenuContent.height
} else {
serversMenuContent.contentY = 0
}
}
}
Keys.onUpPressed: scrollBar.decrease()
Keys.onDownPressed: scrollBar.increase()
Connections { Connections {
target: drawer target: drawer
enabled: !GC.isMobile()
function onIsCollapsedChanged() {
if (drawer.isCollapsed) {
const item = serversMenuContent.itemAtIndex(serversMenuContent.focusItemIndex)
if (item) { item.serverRadioButtonProperty.focus = false }
}
}
}
Connections { // this item shouldn't be focused when drawer is closed
target: ServersModel function onIsOpenedChanged() {
function onDefaultServerIndexChanged(serverIndex) { serversMenuContent.isFocusable = drawer.isOpened
serversMenuContent.currentIndex = serverIndex
}
}
clip: true
delegate: Item {
id: menuContentDelegate
property variant delegateData: model
property VerticalRadioButton serverRadioButtonProperty: serverRadioButton
implicitWidth: serversMenuContent.width
implicitHeight: serverRadioButtonContent.implicitHeight
onActiveFocusChanged: {
if (activeFocus) {
serverRadioButton.forceActiveFocus()
}
}
ColumnLayout {
id: serverRadioButtonContent
anchors.fill: parent
anchors.rightMargin: 16
anchors.leftMargin: 16
spacing: 0
RowLayout {
Layout.fillWidth: true
VerticalRadioButton {
id: serverRadioButton
Layout.fillWidth: true
text: name
descriptionText: serverDescription
checked: index === serversMenuContent.currentIndex
checkable: !ConnectionController.isConnected
ButtonGroup.group: serversRadioButtonGroup
onClicked: {
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection"))
return
}
serversMenuContent.currentIndex = index
ServersModel.defaultIndex = index
}
MouseArea {
anchors.fill: serverRadioButton
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onTabPressed: serverInfoButton.forceActiveFocus()
Keys.onEnterPressed: serverRadioButton.clicked()
Keys.onReturnPressed: serverRadioButton.clicked()
}
ImageButtonType {
id: serverInfoButton
image: "qrc:/images/controls/settings.svg"
imageColor: AmneziaStyle.color.paleGray
implicitWidth: 56
implicitHeight: 56
z: 1
Keys.onTabPressed: {
if (serversMenuContent.focusItemIndex < serversMenuContent.count - 1) {
serversMenuContent.focusItemIndex++
serversMenuContent.itemAtIndex(serversMenuContent.focusItemIndex).forceActiveFocus()
} else {
focusItem1.forceActiveFocus()
serversMenuContent.contentY = 0
}
}
Keys.onEnterPressed: serverInfoButton.clicked()
Keys.onReturnPressed: serverInfoButton.clicked()
onClicked: function() {
ServersModel.processedIndex = index
PageController.goToPage(PageEnum.PageSettingsServerInfo)
drawer.close()
}
}
}
DividerType {
Layout.fillWidth: true
Layout.leftMargin: 0
Layout.rightMargin: 0
}
} }
} }
} }

View file

@ -16,18 +16,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: listview.currentItem.mtuTextField.textField
Item {
id: focusItem
onFocusChanged: {
if (activeFocus) {
fl.ensureVisible(focusItem)
}
}
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: backButtonLayout id: backButtonLayout
@ -39,31 +27,44 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: listview.currentItem.mtuTextField.textField
} }
} }
FlickableType {
id: fl
anchors.top: backButtonLayout.bottom
anchors.bottom: parent.bottom
contentHeight: content.implicitHeight + saveButton.implicitHeight + saveButton.anchors.bottomMargin + saveButton.anchors.topMargin
Column {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
ListView { ListView {
id: listview id: listview
anchors.top: backButtonLayout.bottom
anchors.bottom: saveButton.top
width: parent.width width: parent.width
height: listview.contentItem.height
clip: true clip: true
interactive: false
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
model: AwgConfigModel model: AwgConfigModel
@ -122,7 +123,6 @@ PageType {
headerText: "Jc - Junk packet count" headerText: "Jc - Junk packet count"
textFieldText: clientJunkPacketCount textFieldText: clientJunkPacketCount
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== clientJunkPacketCount) { if (textFieldText !== clientJunkPacketCount) {
@ -143,7 +143,6 @@ PageType {
headerText: "Jmin - Junk packet minimum size" headerText: "Jmin - Junk packet minimum size"
textFieldText: clientJunkPacketMinSize textFieldText: clientJunkPacketMinSize
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== clientJunkPacketMinSize) { if (textFieldText !== clientJunkPacketMinSize) {
@ -164,7 +163,6 @@ PageType {
headerText: "Jmax - Junk packet maximum size" headerText: "Jmax - Junk packet maximum size"
textFieldText: clientJunkPacketMaxSize textFieldText: clientJunkPacketMaxSize
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== clientJunkPacketMaxSize) { if (textFieldText !== clientJunkPacketMaxSize) {
@ -174,7 +172,6 @@ PageType {
checkEmptyText: true checkEmptyText: true
Keys.onTabPressed: saveButton.forceActiveFocus()
} }
Header2TextType { Header2TextType {
@ -243,7 +240,6 @@ PageType {
id: underloadPacketMagicHeaderTextField id: underloadPacketMagicHeaderTextField
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
parentFlickable: fl
enabled: false enabled: false
@ -264,8 +260,6 @@ PageType {
} }
} }
} }
}
}
BasicButtonType { BasicButtonType {
id: saveButton id: saveButton
@ -283,7 +277,11 @@ PageType {
text: qsTr("Save") text: qsTr("Save")
Keys.onTabPressed: lastItemTabClicked(focusItem) onActiveFocusChanged: {
if(activeFocus) {
listview.positionViewAtEnd()
}
}
clickedFunc: function() { clickedFunc: function() {
forceActiveFocus() forceActiveFocus()

View file

@ -2,6 +2,8 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtCore
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
import PageEnum 1.0 import PageEnum 1.0
@ -17,18 +19,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: listview.currentItem.portTextField.textField
Item {
id: focusItem
onFocusChanged: {
if (activeFocus) {
fl.ensureVisible(focusItem)
}
}
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: backButtonLayout id: backButtonLayout
@ -40,31 +30,44 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: listview.currentItem.portTextField.textField
} }
} }
FlickableType {
id: fl
anchors.top: backButtonLayout.bottom
anchors.bottom: parent.bottom
contentHeight: content.implicitHeight
Column {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
ListView { ListView {
id: listview id: listview
property bool isFocusable: true
anchors.top: backButtonLayout.bottom
anchors.bottom: parent.bottom
width: parent.width width: parent.width
height: listview.contentItem.height
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
clip: true clip: true
interactive: false
model: AwgConfigModel model: AwgConfigModel
@ -73,7 +76,7 @@ PageType {
implicitWidth: listview.width implicitWidth: listview.width
implicitHeight: col.implicitHeight implicitHeight: col.implicitHeight
property alias portTextField: portTextField property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
ColumnLayout { ColumnLayout {
@ -95,17 +98,36 @@ PageType {
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
id: portTextField id: vpnAddressSubnetTextField
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 40 Layout.topMargin: 40
enabled: delegateItem.isEnabled enabled: delegateItem.isEnabled
headerText: qsTr("VPN address subnet")
textFieldText: subnetAddress
textField.onEditingFinished: {
if (textFieldText !== subnetAddress) {
subnetAddress = textFieldText
}
}
checkEmptyText: true
}
TextFieldWithHeaderType {
id: portTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: delegateItem.isEnabled
headerText: qsTr("Port") headerText: qsTr("Port")
textFieldText: port textFieldText: port
textField.maximumLength: 5 textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 } textField.validator: IntValidator { bottom: 1; top: 65535 }
parentFlickable: fl
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== port) { if (textFieldText !== port) {
@ -114,8 +136,6 @@ PageType {
} }
checkEmptyText: true checkEmptyText: true
KeyNavigation.tab: junkPacketCountTextField.textField
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -126,7 +146,6 @@ PageType {
headerText: qsTr("Jc - Junk packet count") headerText: qsTr("Jc - Junk packet count")
textFieldText: serverJunkPacketCount textFieldText: serverJunkPacketCount
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText === "") { if (textFieldText === "") {
@ -139,8 +158,6 @@ PageType {
} }
checkEmptyText: true checkEmptyText: true
KeyNavigation.tab: junkPacketMinSizeTextField.textField
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -151,7 +168,6 @@ PageType {
headerText: qsTr("Jmin - Junk packet minimum size") headerText: qsTr("Jmin - Junk packet minimum size")
textFieldText: serverJunkPacketMinSize textFieldText: serverJunkPacketMinSize
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverJunkPacketMinSize) { if (textFieldText !== serverJunkPacketMinSize) {
@ -160,8 +176,6 @@ PageType {
} }
checkEmptyText: true checkEmptyText: true
KeyNavigation.tab: junkPacketMaxSizeTextField.textField
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -172,7 +186,6 @@ PageType {
headerText: qsTr("Jmax - Junk packet maximum size") headerText: qsTr("Jmax - Junk packet maximum size")
textFieldText: serverJunkPacketMaxSize textFieldText: serverJunkPacketMaxSize
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverJunkPacketMaxSize) { if (textFieldText !== serverJunkPacketMaxSize) {
@ -181,8 +194,6 @@ PageType {
} }
checkEmptyText: true checkEmptyText: true
KeyNavigation.tab: initPacketJunkSizeTextField.textField
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -193,7 +204,6 @@ PageType {
headerText: qsTr("S1 - Init packet junk size") headerText: qsTr("S1 - Init packet junk size")
textFieldText: serverInitPacketJunkSize textFieldText: serverInitPacketJunkSize
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverInitPacketJunkSize) { if (textFieldText !== serverInitPacketJunkSize) {
@ -203,7 +213,11 @@ PageType {
checkEmptyText: true checkEmptyText: true
KeyNavigation.tab: responsePacketJunkSizeTextField.textField onActiveFocusChanged: {
if(activeFocus) {
listview.positionViewAtEnd()
}
}
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -214,7 +228,6 @@ PageType {
headerText: qsTr("S2 - Response packet junk size") headerText: qsTr("S2 - Response packet junk size")
textFieldText: serverResponsePacketJunkSize textFieldText: serverResponsePacketJunkSize
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverResponsePacketJunkSize) { if (textFieldText !== serverResponsePacketJunkSize) {
@ -224,7 +237,11 @@ PageType {
checkEmptyText: true checkEmptyText: true
KeyNavigation.tab: initPacketMagicHeaderTextField.textField onActiveFocusChanged: {
if(activeFocus) {
listview.positionViewAtEnd()
}
}
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -235,7 +252,6 @@ PageType {
headerText: qsTr("H1 - Init packet magic header") headerText: qsTr("H1 - Init packet magic header")
textFieldText: serverInitPacketMagicHeader textFieldText: serverInitPacketMagicHeader
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverInitPacketMagicHeader) { if (textFieldText !== serverInitPacketMagicHeader) {
@ -244,8 +260,6 @@ PageType {
} }
checkEmptyText: true checkEmptyText: true
KeyNavigation.tab: responsePacketMagicHeaderTextField.textField
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -256,7 +270,6 @@ PageType {
headerText: qsTr("H2 - Response packet magic header") headerText: qsTr("H2 - Response packet magic header")
textFieldText: serverResponsePacketMagicHeader textFieldText: serverResponsePacketMagicHeader
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverResponsePacketMagicHeader) { if (textFieldText !== serverResponsePacketMagicHeader) {
@ -265,8 +278,6 @@ PageType {
} }
checkEmptyText: true checkEmptyText: true
KeyNavigation.tab: transportPacketMagicHeaderTextField.textField
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -277,7 +288,6 @@ PageType {
headerText: qsTr("H4 - Transport packet magic header") headerText: qsTr("H4 - Transport packet magic header")
textFieldText: serverTransportPacketMagicHeader textFieldText: serverTransportPacketMagicHeader
textField.validator: IntValidator { bottom: 0 } textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== serverTransportPacketMagicHeader) { if (textFieldText !== serverTransportPacketMagicHeader) {
@ -286,15 +296,12 @@ PageType {
} }
checkEmptyText: true checkEmptyText: true
KeyNavigation.tab: underloadPacketMagicHeaderTextField.textField
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
id: underloadPacketMagicHeaderTextField id: underloadPacketMagicHeaderTextField
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
parentFlickable: fl
headerText: qsTr("H3 - Underload packet magic header") headerText: qsTr("H3 - Underload packet magic header")
textFieldText: serverUnderloadPacketMagicHeader textFieldText: serverUnderloadPacketMagicHeader
@ -307,13 +314,10 @@ PageType {
} }
checkEmptyText: true checkEmptyText: true
KeyNavigation.tab: saveRestartButton
} }
BasicButtonType { BasicButtonType {
id: saveRestartButton id: saveRestartButton
parentFlickable: fl
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24
@ -328,11 +332,16 @@ PageType {
junkPacketMaxSizeTextField.errorText === "" && junkPacketMaxSizeTextField.errorText === "" &&
junkPacketMinSizeTextField.errorText === "" && junkPacketMinSizeTextField.errorText === "" &&
junkPacketCountTextField.errorText === "" && junkPacketCountTextField.errorText === "" &&
portTextField.errorText === "" portTextField.errorText === "" &&
vpnAddressSubnetTextField.errorText === ""
text: qsTr("Save") text: qsTr("Save")
Keys.onTabPressed: lastItemTabClicked(focusItem) onActiveFocusChanged: {
if(activeFocus) {
listview.positionViewAtEnd()
}
}
clickedFunc: function() { clickedFunc: function() {
forceActiveFocus() forceActiveFocus()
@ -378,6 +387,4 @@ PageType {
} }
} }
} }
}
}
} }

View file

@ -16,13 +16,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: listview.currentItem.trafficFromField.textField
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: backButtonLayout id: backButtonLayout
@ -34,7 +27,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: listview.currentItem.trafficFromField.textField
} }
} }
@ -56,11 +48,13 @@ PageType {
ListView { ListView {
id: listview id: listview
property int selectedIndex: 0
width: parent.width width: parent.width
height: listview.contentItem.height height: listview.contentItem.height
clip: true clip: true
interactive: false reuseItems: true
model: CloakConfigModel model: CloakConfigModel
@ -110,8 +104,6 @@ PageType {
} }
} }
} }
KeyNavigation.tab: portTextField.textField
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -130,8 +122,6 @@ PageType {
port = textFieldText port = textFieldText
} }
} }
KeyNavigation.tab: cipherDropDown
} }
DropDownType { DropDownType {
@ -143,7 +133,6 @@ PageType {
headerText: qsTr("Cipher") headerText: qsTr("Cipher")
drawerParent: root drawerParent: root
KeyNavigation.tab: saveRestartButton
listView: ListViewWithRadioButtonType { listView: ListViewWithRadioButtonType {
id: cipherListView id: cipherListView
@ -161,7 +150,7 @@ PageType {
clickedFunction: function() { clickedFunction: function() {
cipherDropDown.text = selectedText cipherDropDown.text = selectedText
cipher = cipherDropDown.text cipher = cipherDropDown.text
cipherDropDown.close() cipherDropDown.closeTriggered()
} }
Component.onCompleted: { Component.onCompleted: {
@ -169,7 +158,7 @@ PageType {
for (var i = 0; i < cipherListView.model.count; i++) { for (var i = 0; i < cipherListView.model.count; i++) {
if (cipherListView.model.get(i).name === cipherDropDown.text) { if (cipherListView.model.get(i).name === cipherDropDown.text) {
currentIndex = i selectedIndex = i
} }
} }
} }
@ -184,7 +173,6 @@ PageType {
Layout.bottomMargin: 24 Layout.bottomMargin: 24
text: qsTr("Save") text: qsTr("Save")
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() { clickedFunc: function() {
forceActiveFocus() forceActiveFocus()

View file

@ -17,18 +17,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: listview.currentItem.vpnAddressSubnetTextField.textField
Item {
id: focusItem
KeyNavigation.tab: backButton
onActiveFocusChanged: {
if (activeFocus) {
fl.ensureVisible(focusItem)
}
}
}
ColumnLayout { ColumnLayout {
id: backButtonLayout id: backButtonLayout
@ -40,7 +28,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: listview.currentItem.vpnAddressSubnetTextField.textField
} }
} }
@ -104,7 +91,6 @@ PageType {
textFieldText: subnetAddress textFieldText: subnetAddress
parentFlickable: fl parentFlickable: fl
KeyNavigation.tab: transportProtoSelector
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== subnetAddress) { if (textFieldText !== subnetAddress) {
@ -132,8 +118,6 @@ PageType {
return transportProto === "tcp" ? 1 : 0 return transportProto === "tcp" ? 1 : 0
} }
KeyNavigation.tab: portTextField.enabled ? portTextField.textField : autoNegotiateEncryprionSwitcher
onCurrentIndexChanged: { onCurrentIndexChanged: {
if (transportProto === "tcp" && currentIndex === 0) { if (transportProto === "tcp" && currentIndex === 0) {
transportProto = "udp" transportProto = "udp"
@ -162,8 +146,6 @@ PageType {
port = textFieldText port = textFieldText
} }
} }
KeyNavigation.tab: autoNegotiateEncryprionSwitcher
} }
SwitcherType { SwitcherType {
@ -181,10 +163,6 @@ PageType {
autoNegotiateEncryprion = checked autoNegotiateEncryprion = checked
} }
} }
KeyNavigation.tab: hashDropDown.enabled ?
hashDropDown :
tlsAuthCheckBox
} }
DropDownType { DropDownType {
@ -198,10 +176,6 @@ PageType {
headerText: qsTr("Hash") headerText: qsTr("Hash")
drawerParent: root drawerParent: root
parentFlickable: fl
KeyNavigation.tab: cipherDropDown.enabled ?
cipherDropDown :
tlsAuthCheckBox
listView: ListViewWithRadioButtonType { listView: ListViewWithRadioButtonType {
id: hashListView id: hashListView
@ -224,7 +198,7 @@ PageType {
clickedFunction: function() { clickedFunction: function() {
hashDropDown.text = selectedText hashDropDown.text = selectedText
hash = hashDropDown.text hash = hashDropDown.text
hashDropDown.close() hashDropDown.closeTriggered()
} }
Component.onCompleted: { Component.onCompleted: {
@ -250,9 +224,6 @@ PageType {
headerText: qsTr("Cipher") headerText: qsTr("Cipher")
drawerParent: root drawerParent: root
parentFlickable: fl
KeyNavigation.tab: tlsAuthCheckBox
listView: ListViewWithRadioButtonType { listView: ListViewWithRadioButtonType {
id: cipherListView id: cipherListView
@ -275,7 +246,7 @@ PageType {
clickedFunction: function() { clickedFunction: function() {
cipherDropDown.text = selectedText cipherDropDown.text = selectedText
cipher = cipherDropDown.text cipher = cipherDropDown.text
cipherDropDown.close() cipherDropDown.closeTriggered()
} }
Component.onCompleted: { Component.onCompleted: {
@ -320,8 +291,6 @@ PageType {
text: qsTr("TLS auth") text: qsTr("TLS auth")
checked: tlsAuth checked: tlsAuth
KeyNavigation.tab: blockDnsCheckBox
onCheckedChanged: { onCheckedChanged: {
if (checked !== tlsAuth) { if (checked !== tlsAuth) {
console.log("tlsAuth changed to: " + checked) console.log("tlsAuth changed to: " + checked)
@ -339,8 +308,6 @@ PageType {
text: qsTr("Block DNS requests outside of VPN") text: qsTr("Block DNS requests outside of VPN")
checked: blockDns checked: blockDns
KeyNavigation.tab: additionalClientCommandsSwitcher
onCheckedChanged: { onCheckedChanged: {
if (checked !== blockDns) { if (checked !== blockDns) {
blockDns = checked blockDns = checked
@ -355,9 +322,6 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 32 Layout.topMargin: 32
parentFlickable: fl parentFlickable: fl
KeyNavigation.tab: additionalClientCommandsTextArea.visible ?
additionalClientCommandsTextArea.textArea :
additionalServerCommandsSwitcher
checked: additionalClientCommands !== "" checked: additionalClientCommands !== ""
@ -376,7 +340,7 @@ PageType {
Layout.topMargin: 16 Layout.topMargin: 16
visible: additionalClientCommandsSwitcher.checked visible: additionalClientCommandsSwitcher.checked
KeyNavigation.tab: additionalServerCommandsSwitcher
parentFlickable: fl parentFlickable: fl
textAreaText: additionalClientCommands textAreaText: additionalClientCommands
@ -394,9 +358,6 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
parentFlickable: fl parentFlickable: fl
KeyNavigation.tab: additionalServerCommandsTextArea.visible ?
additionalServerCommandsTextArea.textArea :
saveRestartButton
checked: additionalServerCommands !== "" checked: additionalServerCommands !== ""
@ -419,7 +380,6 @@ PageType {
textAreaText: additionalServerCommands textAreaText: additionalServerCommands
placeholderText: qsTr("Commands:") placeholderText: qsTr("Commands:")
parentFlickable: fl parentFlickable: fl
KeyNavigation.tab: saveRestartButton
textArea.onEditingFinished: { textArea.onEditingFinished: {
if (additionalServerCommands !== textAreaText) { if (additionalServerCommands !== textAreaText) {
additionalServerCommands = textAreaText additionalServerCommands = textAreaText
@ -436,7 +396,6 @@ PageType {
text: qsTr("Save") text: qsTr("Save")
parentFlickable: fl parentFlickable: fl
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() { clickedFunc: function() {
forceActiveFocus() forceActiveFocus()

View file

@ -19,13 +19,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: header id: header
@ -37,7 +30,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: listView
} }
HeaderType { HeaderType {
@ -75,13 +67,6 @@ PageType {
activeFocusOnTab: true activeFocusOnTab: true
focus: true focus: true
onActiveFocusChanged: {
if (focus) {
listView.currentIndex = 0
listView.currentItem.focusItem.forceActiveFocus()
}
}
delegate: Item { delegate: Item {
implicitWidth: parent.width implicitWidth: parent.width
implicitHeight: delegateContent.implicitHeight implicitHeight: delegateContent.implicitHeight
@ -101,11 +86,9 @@ PageType {
text: qsTr("Show connection options") text: qsTr("Show connection options")
clickedFunction: function() { clickedFunction: function() {
configContentDrawer.open() configContentDrawer.openTriggered()
} }
KeyNavigation.tab: removeButton
MouseArea { MouseArea {
anchors.fill: button anchors.fill: button
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@ -120,31 +103,12 @@ PageType {
expandedHeight: root.height * 0.9 expandedHeight: root.height * 0.9
onClosed: {
if (!GC.isMobile()) {
defaultActiveFocusItem.forceActiveFocus()
}
}
parent: root parent: root
anchors.fill: parent anchors.fill: parent
expandedContent: Item { expandedStateContent: Item {
implicitHeight: configContentDrawer.expandedHeight implicitHeight: configContentDrawer.expandedHeight
Connections {
target: configContentDrawer
enabled: !GC.isMobile()
function onOpened() {
focusItem1.forceActiveFocus()
}
}
Item {
id: focusItem1
KeyNavigation.tab: backButton1
}
BackButtonType { BackButtonType {
id: backButton1 id: backButton1
@ -154,10 +118,8 @@ PageType {
anchors.topMargin: 16 anchors.topMargin: 16
backButtonFunction: function() { backButtonFunction: function() {
configContentDrawer.close() configContentDrawer.closeTriggered()
} }
KeyNavigation.tab: focusItem1
} }
FlickableType { FlickableType {
@ -226,7 +188,6 @@ PageType {
text: qsTr("Remove ") + ContainersModel.getProcessedContainerName() text: qsTr("Remove ") + ContainersModel.getProcessedContainerName()
textColor: AmneziaStyle.color.vibrantRed textColor: AmneziaStyle.color.vibrantRed
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunction: function() { clickedFunction: function() {
var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName()) var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName())
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")

View file

@ -16,15 +16,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: listview.currentItem.focusItemId.enabled ?
listview.currentItem.focusItemId.textField :
focusItem
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: backButtonLayout id: backButtonLayout
@ -36,9 +27,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: listview.currentItem.focusItemId.enabled ?
listview.currentItem.focusItemId.textField :
focusItem
} }
} }
@ -114,8 +102,6 @@ PageType {
port = textFieldText port = textFieldText
} }
} }
KeyNavigation.tab: cipherDropDown
} }
DropDownType { DropDownType {
@ -129,9 +115,9 @@ PageType {
headerText: qsTr("Cipher") headerText: qsTr("Cipher")
drawerParent: root drawerParent: root
KeyNavigation.tab: saveRestartButton
listView: ListViewWithRadioButtonType { listView: ListViewWithRadioButtonType {
id: cipherListView id: cipherListView
rootWidth: root.width rootWidth: root.width
@ -147,7 +133,7 @@ PageType {
clickedFunction: function() { clickedFunction: function() {
cipherDropDown.text = selectedText cipherDropDown.text = selectedText
cipher = cipherDropDown.text cipher = cipherDropDown.text
cipherDropDown.close() cipherDropDown.closeTriggered()
} }
Component.onCompleted: { Component.onCompleted: {
@ -172,7 +158,6 @@ PageType {
enabled: isPortEditable | isCipherEditable enabled: isPortEditable | isCipherEditable
text: qsTr("Save") text: qsTr("Save")
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() { clickedFunc: function() {
forceActiveFocus() forceActiveFocus()

View file

@ -16,8 +16,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: listview.currentItem.mtuTextField.textField
Item { Item {
id: focusItem id: focusItem
onFocusChanged: { onFocusChanged: {
@ -150,8 +148,6 @@ PageType {
text: qsTr("Save") text: qsTr("Save")
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() { clickedFunc: function() {
forceActiveFocus() forceActiveFocus()
var headerText = qsTr("Save settings?") var headerText = qsTr("Save settings?")

View file

@ -16,13 +16,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: listview
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: backButtonLayout id: backButtonLayout
@ -34,7 +27,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: listview
} }
} }
@ -64,17 +56,10 @@ PageType {
model: WireGuardConfigModel model: WireGuardConfigModel
activeFocusOnTab: true
onActiveFocusChanged: {
if (activeFocus) {
listview.itemAtIndex(0)?.focusItemId.forceActiveFocus()
}
}
delegate: Item { delegate: Item {
id: delegateItem id: delegateItem
property alias focusItemId: portTextField.textField property alias focusItemId: vpnAddressSubnetTextField
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
implicitWidth: listview.width implicitWidth: listview.width
@ -98,19 +83,36 @@ PageType {
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
id: portTextField id: vpnAddressSubnetTextField
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 40 Layout.topMargin: 40
enabled: delegateItem.isEnabled enabled: delegateItem.isEnabled
headerText: qsTr("VPN address subnet")
textFieldText: subnetAddress
textField.onEditingFinished: {
if (textFieldText !== subnetAddress) {
subnetAddress = textFieldText
}
}
checkEmptyText: true
}
TextFieldWithHeaderType {
id: portTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: delegateItem.isEnabled
headerText: qsTr("Port") headerText: qsTr("Port")
textFieldText: port textFieldText: port
textField.maximumLength: 5 textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 } textField.validator: IntValidator { bottom: 1; top: 65535 }
KeyNavigation.tab: saveButton
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== port) { if (textFieldText !== port) {
port = textFieldText port = textFieldText
@ -126,12 +128,11 @@ PageType {
Layout.topMargin: 24 Layout.topMargin: 24
Layout.bottomMargin: 24 Layout.bottomMargin: 24
enabled: portTextField.errorText === "" enabled: portTextField.errorText === "" &&
vpnAddressSubnetTextField.errorText === ""
text: qsTr("Save") text: qsTr("Save")
Keys.onTabPressed: lastItemTabClicked(focusItem)
onClicked: function() { onClicked: function() {
forceActiveFocus() forceActiveFocus()

View file

@ -17,13 +17,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: listview
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: backButtonLayout id: backButtonLayout
@ -35,7 +28,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: listview
} }
} }
@ -65,13 +57,6 @@ PageType {
model: XrayConfigModel model: XrayConfigModel
activeFocusOnTab: true
onActiveFocusChanged: {
if (activeFocus) {
listview.itemAtIndex(0)?.focusItemId.forceActiveFocus()
}
}
delegate: Item { delegate: Item {
property alias focusItemId: textFieldWithHeaderType.textField property alias focusItemId: textFieldWithHeaderType.textField
@ -103,8 +88,6 @@ PageType {
headerText: qsTr("Disguised as traffic from") headerText: qsTr("Disguised as traffic from")
textFieldText: site textFieldText: site
KeyNavigation.tab: basicButton
textField.onEditingFinished: { textField.onEditingFinished: {
if (textFieldText !== site) { if (textFieldText !== site) {
var tmpText = textFieldText var tmpText = textFieldText
@ -128,8 +111,6 @@ PageType {
text: qsTr("Save") text: qsTr("Save")
Keys.onTabPressed: lastItemTabClicked(focusItem)
onClicked: { onClicked: {
forceActiveFocus() forceActiveFocus()

View file

@ -16,13 +16,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: backButtonLayout id: backButtonLayout
@ -34,7 +27,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: removeButton
} }
} }
@ -72,8 +64,6 @@ PageType {
text: qsTr("Remove ") + ContainersModel.getProcessedContainerName() text: qsTr("Remove ") + ContainersModel.getProcessedContainerName()
textColor: AmneziaStyle.color.vibrantRed textColor: AmneziaStyle.color.vibrantRed
Keys.onTabPressed: root.lastItemTabClicked()
clickedFunction: function() { clickedFunction: function() {
var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName()) var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName())
var yesButtonText = qsTr("Continue") var yesButtonText = qsTr("Continue")

View file

@ -16,8 +16,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
Connections { Connections {
target: InstallController target: InstallController
@ -26,11 +24,6 @@ PageType {
} }
} }
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: backButtonLayout id: backButtonLayout
@ -42,7 +35,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: listview
} }
} }
@ -107,7 +99,6 @@ PageType {
Layout.topMargin: 32 Layout.topMargin: 32
parentFlickable: fl parentFlickable: fl
KeyNavigation.tab: portLabel.rightButton
text: qsTr("Host") text: qsTr("Host")
descriptionText: ServersModel.getProcessedServerData("hostName") descriptionText: ServersModel.getProcessedServerData("hostName")
@ -136,7 +127,6 @@ PageType {
descriptionOnTop: true descriptionOnTop: true
parentFlickable: fl parentFlickable: fl
KeyNavigation.tab: usernameLabel.rightButton
rightImageSource: "qrc:/images/controls/copy.svg" rightImageSource: "qrc:/images/controls/copy.svg"
rightImageColor: AmneziaStyle.color.paleGray rightImageColor: AmneziaStyle.color.paleGray
@ -160,7 +150,6 @@ PageType {
descriptionOnTop: true descriptionOnTop: true
parentFlickable: fl parentFlickable: fl
KeyNavigation.tab: passwordLabel.eyeButton
rightImageSource: "qrc:/images/controls/copy.svg" rightImageSource: "qrc:/images/controls/copy.svg"
rightImageColor: AmneziaStyle.color.paleGray rightImageColor: AmneziaStyle.color.paleGray
@ -184,14 +173,6 @@ PageType {
descriptionOnTop: true descriptionOnTop: true
parentFlickable: fl parentFlickable: fl
eyeButton.KeyNavigation.tab: passwordLabel.rightButton
rightButton.Keys.onTabPressed: {
if (mountButton.visible) {
mountButton.forceActiveFocus()
} else {
detailedInstructionsButton.forceActiveFocus()
}
}
rightImageSource: "qrc:/images/controls/copy.svg" rightImageSource: "qrc:/images/controls/copy.svg"
rightImageColor: AmneziaStyle.color.paleGray rightImageColor: AmneziaStyle.color.paleGray
@ -225,7 +206,6 @@ PageType {
borderWidth: 1 borderWidth: 1
parentFlickable: fl parentFlickable: fl
KeyNavigation.tab: detailedInstructionsButton
text: qsTr("Mount folder on device") text: qsTr("Mount folder on device")
@ -290,7 +270,6 @@ PageType {
text: qsTr("Detailed instructions") text: qsTr("Detailed instructions")
parentFlickable: fl parentFlickable: fl
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() { clickedFunc: function() {
// Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") // Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest")

View file

@ -17,8 +17,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: listview
Connections { Connections {
target: InstallController target: InstallController
@ -27,11 +25,6 @@ PageType {
} }
} }
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: backButtonLayout id: backButtonLayout
@ -43,7 +36,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: listview
} }
} }
@ -99,7 +91,6 @@ PageType {
Layout.topMargin: 32 Layout.topMargin: 32
parentFlickable: fl parentFlickable: fl
KeyNavigation.tab: portLabel.rightButton
text: qsTr("Host") text: qsTr("Host")
descriptionText: ServersModel.getProcessedServerData("hostName") descriptionText: ServersModel.getProcessedServerData("hostName")
@ -128,7 +119,6 @@ PageType {
descriptionOnTop: true descriptionOnTop: true
parentFlickable: fl parentFlickable: fl
KeyNavigation.tab: usernameLabel.rightButton
rightImageSource: "qrc:/images/controls/copy.svg" rightImageSource: "qrc:/images/controls/copy.svg"
rightImageColor: AmneziaStyle.color.paleGray rightImageColor: AmneziaStyle.color.paleGray
@ -152,7 +142,6 @@ PageType {
descriptionOnTop: true descriptionOnTop: true
parentFlickable: fl parentFlickable: fl
KeyNavigation.tab: passwordLabel.eyeButton
rightImageSource: "qrc:/images/controls/copy.svg" rightImageSource: "qrc:/images/controls/copy.svg"
rightImageColor: AmneziaStyle.color.paleGray rightImageColor: AmneziaStyle.color.paleGray
@ -176,8 +165,6 @@ PageType {
descriptionOnTop: true descriptionOnTop: true
parentFlickable: fl parentFlickable: fl
eyeButton.KeyNavigation.tab: passwordLabel.rightButton
rightButton.KeyNavigation.tab: changeSettingsButton
rightImageSource: "qrc:/images/controls/copy.svg" rightImageSource: "qrc:/images/controls/copy.svg"
rightImageColor: AmneziaStyle.color.paleGray rightImageColor: AmneziaStyle.color.paleGray
@ -200,13 +187,7 @@ PageType {
anchors.fill: parent anchors.fill: parent
expandedHeight: root.height * 0.9 expandedHeight: root.height * 0.9
onClosed: { expandedStateContent: ColumnLayout {
if (!GC.isMobile()) {
focusItem.forceActiveFocus()
}
}
expandedContent: ColumnLayout {
property string tempPort: port property string tempPort: port
property string tempUsername: username property string tempUsername: username
property string tempPassword: password property string tempPassword: password
@ -222,9 +203,6 @@ PageType {
Connections { Connections {
target: changeSettingsDrawer target: changeSettingsDrawer
function onOpened() { function onOpened() {
if (!GC.isMobile()) {
drawerFocusItem.forceActiveFocus()
}
tempPort = port tempPort = port
tempUsername = username tempUsername = username
tempPassword = password tempPassword = password
@ -239,11 +217,6 @@ PageType {
} }
} }
Item {
id: drawerFocusItem
KeyNavigation.tab: portTextField.textField
}
HeaderType { HeaderType {
Layout.fillWidth: true Layout.fillWidth: true
@ -268,8 +241,6 @@ PageType {
port = textFieldText port = textFieldText
} }
} }
KeyNavigation.tab: usernameTextField.textField
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -290,8 +261,6 @@ PageType {
username = textFieldText username = textFieldText
} }
} }
KeyNavigation.tab: passwordTextField.textField
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -322,8 +291,6 @@ PageType {
password = textFieldText password = textFieldText
} }
} }
KeyNavigation.tab: saveButton
} }
BasicButtonType { BasicButtonType {
@ -334,7 +301,6 @@ PageType {
Layout.bottomMargin: 24 Layout.bottomMargin: 24
text: qsTr("Change connection settings") text: qsTr("Change connection settings")
Keys.onTabPressed: lastItemTabClicked(drawerFocusItem)
clickedFunc: function() { clickedFunc: function() {
forceActiveFocus() forceActiveFocus()
@ -356,7 +322,7 @@ PageType {
tempPort = portTextField.textFieldText tempPort = portTextField.textFieldText
tempUsername = usernameTextField.textFieldText tempUsername = usernameTextField.textFieldText
tempPassword = passwordTextField.textFieldText tempPassword = passwordTextField.textFieldText
changeSettingsDrawer.close() changeSettingsDrawer.closeTriggered()
} }
} }
} }
@ -372,11 +338,10 @@ PageType {
Layout.rightMargin: 16 Layout.rightMargin: 16
text: qsTr("Change connection settings") text: qsTr("Change connection settings")
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() { clickedFunc: function() {
forceActiveFocus() forceActiveFocus()
changeSettingsDrawer.open() changeSettingsDrawer.openTriggered()
} }
} }
} }

View file

@ -17,8 +17,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
Connections { Connections {
target: InstallController target: InstallController
@ -27,11 +25,6 @@ PageType {
} }
} }
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: backButtonLayout id: backButtonLayout
@ -43,7 +36,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: websiteName.rightButton
} }
} }
@ -88,8 +80,6 @@ PageType {
rightImageSource: "qrc:/images/controls/copy.svg" rightImageSource: "qrc:/images/controls/copy.svg"
rightImageColor: AmneziaStyle.color.paleGray rightImageColor: AmneziaStyle.color.paleGray
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunction: function() { clickedFunction: function() {
GC.copyToClipBoard(descriptionText) GC.copyToClipBoard(descriptionText)
PageController.showNotificationMessage(qsTr("Copied")) PageController.showNotificationMessage(qsTr("Copied"))

View file

@ -14,8 +14,6 @@ import "../Config"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: header
FlickableType { FlickableType {
id: fl id: fl
anchors.top: parent.top anchors.top: parent.top
@ -39,8 +37,6 @@ PageType {
Layout.leftMargin: 16 Layout.leftMargin: 16
headerText: qsTr("Settings") headerText: qsTr("Settings")
KeyNavigation.tab: account.rightButton
} }
LabelWithButtonType { LabelWithButtonType {
@ -55,8 +51,6 @@ PageType {
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsServersList) PageController.goToPage(PageEnum.PageSettingsServersList)
} }
KeyNavigation.tab: connection.rightButton
} }
DividerType {} DividerType {}
@ -72,8 +66,6 @@ PageType {
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsConnection) PageController.goToPage(PageEnum.PageSettingsConnection)
} }
KeyNavigation.tab: application.rightButton
} }
DividerType {} DividerType {}
@ -89,14 +81,13 @@ PageType {
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsApplication) PageController.goToPage(PageEnum.PageSettingsApplication)
} }
KeyNavigation.tab: backup.rightButton
} }
DividerType {} DividerType {}
LabelWithButtonType { LabelWithButtonType {
id: backup id: backup
visible: !SettingsController.isOnTv()
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Backup") text: qsTr("Backup")
@ -106,11 +97,11 @@ PageType {
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsBackup) PageController.goToPage(PageEnum.PageSettingsBackup)
} }
KeyNavigation.tab: about.rightButton
} }
DividerType {} DividerType {
visible: !SettingsController.isOnTv()
}
LabelWithButtonType { LabelWithButtonType {
id: about id: about
@ -123,8 +114,6 @@ PageType {
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsAbout) PageController.goToPage(PageEnum.PageSettingsAbout)
} }
KeyNavigation.tab: close
} }
DividerType {} DividerType {}
@ -138,8 +127,6 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
leftImageSource: "qrc:/images/controls/bug.svg" leftImageSource: "qrc:/images/controls/bug.svg"
// Keys.onTabPressed: lastItemTabClicked(header)
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageDevMenu) PageController.goToPage(PageEnum.PageDevMenu)
} }
@ -159,8 +146,6 @@ PageType {
leftImageSource: "qrc:/images/controls/x-circle.svg" leftImageSource: "qrc:/images/controls/x-circle.svg"
isLeftImageHoverEnabled: false isLeftImageHoverEnabled: false
Keys.onTabPressed: lastItemTabClicked(header)
clickedFunction: function() { clickedFunction: function() {
PageController.closeApplication() PageController.closeApplication()
} }

View file

@ -14,19 +14,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
Item {
id: focusItem
KeyNavigation.tab: backButton
onFocusChanged: {
if (focusItem.activeFocus) {
fl.contentY = 0
}
}
}
BackButtonType { BackButtonType {
id: backButton id: backButton
@ -35,21 +22,107 @@ PageType {
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20
KeyNavigation.tab: telegramButton onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) {
listView.positionViewAtBeginning()
}
}
} }
FlickableType { QtObject {
id: fl id: telegramGroup
readonly property string title: qsTr("Telegram group")
readonly property string description: qsTr("To discuss features")
readonly property string imageSource: "qrc:/images/controls/telegram.svg"
readonly property var handler: function() {
Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_en"))
}
}
QtObject {
id: mail
readonly property string title: qsTr("support@amnezia.org")
readonly property string description: qsTr("For reviews and bug reports")
readonly property string imageSource: "qrc:/images/controls/mail.svg"
readonly property var handler: function() {
GC.copyToClipBoard(title)
PageController.showNotificationMessage(qsTr("Copied"))
}
}
QtObject {
id: github
readonly property string title: qsTr("GitHub")
readonly property string description: qsTr("Discover the source code")
readonly property string imageSource: "qrc:/images/controls/github.svg"
readonly property var handler: function() {
Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client"))
}
}
QtObject {
id: website
readonly property string title: qsTr("Website")
readonly property string description: qsTr("Visit official website")
readonly property string imageSource: "qrc:/images/controls/amnezia.svg"
readonly property var handler: function() {
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())
}
}
property list<QtObject> contacts: [
telegramGroup,
mail,
github,
website
]
ListView {
id: listView
anchors.top: backButton.bottom anchors.top: backButton.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
contentHeight: content.height
ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
ScrollBar.vertical: ScrollBarType {}
model: contacts
clip: true
header: ColumnLayout {
width: listView.width
Image { Image {
id: image id: image
@ -96,81 +169,29 @@ PageType {
text: qsTr("Contacts") text: qsTr("Contacts")
} }
}
delegate: ColumnLayout {
width: listView.width
LabelWithButtonType { LabelWithButtonType {
id: telegramButton id: telegramButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 6
text: qsTr("Telegram group") text: title
descriptionText: qsTr("To discuss features") descriptionText: description
leftImageSource: "qrc:/images/controls/telegram.svg" leftImageSource: imageSource
KeyNavigation.tab: mailButton clickedFunction: handler
parentFlickable: fl
clickedFunction: function() {
Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_en"))
}
} }
DividerType {} DividerType {}
LabelWithButtonType {
id: mailButton
Layout.fillWidth: true
text: qsTr("support@amnezia.org")
descriptionText: qsTr("For reviews and bug reports")
leftImageSource: "qrc:/images/controls/mail.svg"
KeyNavigation.tab: githubButton
parentFlickable: fl
clickedFunction: function() {
GC.copyToClipBoard(text)
PageController.showNotificationMessage(qsTr("Copied"))
} }
} footer: ColumnLayout {
width: listView.width
DividerType {}
LabelWithButtonType {
id: githubButton
Layout.fillWidth: true
text: qsTr("GitHub")
leftImageSource: "qrc:/images/controls/github.svg"
KeyNavigation.tab: websiteButton
parentFlickable: fl
clickedFunction: function() {
Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client"))
}
}
DividerType {}
LabelWithButtonType {
id: websiteButton
Layout.fillWidth: true
text: qsTr("Website")
leftImageSource: "qrc:/images/controls/amnezia.svg"
KeyNavigation.tab: checkUpdatesButton
parentFlickable: fl
clickedFunction: function() {
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())
}
}
DividerType {}
CaptionTextType { CaptionTextType {
Layout.fillWidth: true Layout.fillWidth: true
@ -196,6 +217,7 @@ PageType {
BasicButtonType { BasicButtonType {
id: checkUpdatesButton id: checkUpdatesButton
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 8 Layout.topMargin: 8
Layout.bottomMargin: 16 Layout.bottomMargin: 16
@ -209,9 +231,6 @@ PageType {
text: qsTr("Check for updates") text: qsTr("Check for updates")
KeyNavigation.tab: privacyPolicyButton
parentFlickable: fl
clickedFunc: function() { clickedFunc: function() {
Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest")
} }
@ -219,6 +238,7 @@ PageType {
BasicButtonType { BasicButtonType {
id: privacyPolicyButton id: privacyPolicyButton
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 16 Layout.bottomMargin: 16
Layout.topMargin: -15 Layout.topMargin: -15
@ -232,9 +252,6 @@ PageType {
text: qsTr("Privacy Policy") text: qsTr("Privacy Policy")
Keys.onTabPressed: lastItemTabClicked()
parentFlickable: fl
clickedFunc: function() { clickedFunc: function() {
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/policy") Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/policy")
} }

View file

@ -18,27 +18,24 @@ PageType {
ListView { ListView {
id: menuContent id: menuContent
property var selectedText property bool isFocusable: true
width: parent.width width: parent.width
height: menuContent.contentItem.height height: parent.height
clip: true clip: true
interactive: false interactive: true
model: ApiCountryModel model: ApiCountryModel
ButtonGroup { ButtonGroup {
id: containersRadioButtonGroup id: containersRadioButtonGroup
} }
delegate: Item { delegate: ColumnLayout {
implicitWidth: parent.width
implicitHeight: content.implicitHeight
ColumnLayout {
id: content id: content
anchors.fill: parent width: menuContent.width
height: content.implicitHeight
RowLayout { RowLayout {
VerticalRadioButton { VerticalRadioButton {
@ -105,5 +102,4 @@ PageType {
} }
} }
} }
}
} }

View file

@ -15,8 +15,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
FlickableType { FlickableType {
id: fl id: fl
anchors.top: parent.top anchors.top: parent.top
@ -32,11 +30,6 @@ PageType {
spacing: 0 spacing: 0
Item {
id: focusItem
// KeyNavigation.tab: backButton
}
LabelWithImageType { LabelWithImageType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 16 Layout.margins: 16
@ -111,9 +104,6 @@ PageType {
descriptionOnTop: true descriptionOnTop: true
// parentFlickable: fl
// KeyNavigation.tab: passwordLabel.eyeButton
rightImageSource: "qrc:/images/controls/copy.svg" rightImageSource: "qrc:/images/controls/copy.svg"
rightImageColor: AmneziaStyle.color.paleGray rightImageColor: AmneziaStyle.color.paleGray
@ -141,8 +131,6 @@ PageType {
text: qsTr("Reload API config") text: qsTr("Reload API config")
// Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() { clickedFunc: function() {
var headerText = qsTr("Reload API config?") var headerText = qsTr("Reload API config?")
var yesButtonText = qsTr("Continue") var yesButtonText = qsTr("Continue")
@ -181,8 +169,6 @@ PageType {
text: qsTr("Remove from application") text: qsTr("Remove from application")
// Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() { clickedFunc: function() {
var headerText = qsTr("Remove from application?") var headerText = qsTr("Remove from application?")
var yesButtonText = qsTr("Continue") var yesButtonText = qsTr("Continue")

Some files were not shown because too many files have changed in this diff Show more