Improve navigation cpp (#1061)

* add focusController class

* add more key handlers

* add focus navigation to qml

* fixed language selector

* add reverse focus change to FocusController

* add default focus item

* update transitions

* update pages

* add ListViewFocusController

* fix ListView navigation

* update CardType for using with focus navigation

* remove useless key navigation

* remove useless slots, logs, Drawer open and close

* fix reverse focus move on listView

* fix drawer radio buttons selection

* fix drawer layout and focus move

* fix PageSetupWizardProtocolSettings focus move

* fix back navigation on default focus item

* fix crashes after ListView navigation

* fix protocol settings focus move

* fix focus on users on page share

* clean up page share

* fix server rename

* fix page share default server selection

* refactor about page for correct focus move

* fix focus move on list views with header and-or footer

* minor fixes

* fix server list back button handler

* fix spawn signals on switch

* fix share details drawer

* fix drawer open close usage

* refactor listViewFocusController

* refactor focusController to make the logic more
straightforward

* fix focus on notification

* update config page for scrolling with tab

* fix crash on return with esc key

* fix focus navigation in dynamic delegate of list view

* fix focus move on qr code on share page

* refactor page logging settings for focus navigation

* update popup

* Bump version

* Add mandatory requirement for android.software.leanback.

* Fix importing files on TVs

* fix: add separate method for reading files to fix file reading on Android TV

* fix(android): add CHANGE_NETWORK_STATE permission for all Android versions

* Fix connection check for AWG/WG

* chore: minor fixes (#1235)

* fix: add a workaround to open files on Android TV due to lack of SAF

* fix: change the banner format for TV

* refactor: make TvFilePicker activity more sustainable

* fix: add the touch emulation method for Android TV

* fix: null uri processing

* fix: add the touch emulation method for Android TV

* fix: hide UI elements that use file saving

* chore: bump version code

* add `ScrollBarType`

* update initial config page

* refactor credentials setup page to handle the focus navigation

* add `setDelegateIndex` method to `listViewFocusController`

* fix focus behavior on new page/popup

* make minor fixes and clean up

* fix: get rid of the assign function call

* Scrollbar is on if the content is larger than a screen

* Fix selection in language change list

* Update select language list

* update logging settings page

* fix checked item in lists

* fix split tunneling settings

* make unchangable properties readonly

* refactor SwitcherType

* fix hide/unhide password

* `PageShare` readonly properties

* Fix list view focus moving on `PageShare`

* remove manual focus control on `PageShare`

* format `ListViewFocusController`

* format `FocusController`

* add `focusControl` with utility functions for
focus control

* refactor `listViewFocusController` acoording to `focusControl`

* refactor `focusConroller` according to `focusControl`

* add `printSectionName` method to `listViewController`

* remove arrow from `Close application` item

* fix focus movement in `ServersListView`

* `Restore from backup` is visible only on start screen

* `I have nothing` is visible only on start screen

* fix back button on `SelectLanguageDrawer`

* rename `focusControl` to `qmlUtils`

* fix `CMakeLists.txt`

* fix `ScrollBarType`

* fix `PageSetupWizardApiServicesList`

* fix focus movement on dynamic delegates in listView

* refactor `PageSetupWizardProtocols`

* remove comments and clean up

* fix `ListViewWithLabelsType`

* fix `PageProtocolCloakSettings`

* fix `PageSettingsAppSplitTunneling`

* fix `PageDevMenu`

* remove debug output from `FocusController`

* remove debug output from `ListViewFocusController`

* remove debug output from `focusControl`

* `focusControl` => `FocusControl`

---------

Co-authored-by: albexk <albexk@proton.me>
Co-authored-by: Nethius <nethiuswork@gmail.com>
This commit is contained in:
Cyril Anisimov 2024-12-31 04:16:52 +01:00 committed by GitHub
parent 212e9b3a91
commit 6acaab0ffa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
109 changed files with 4036 additions and 3700 deletions

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,21 +523,25 @@ class AmneziaActivity : QtActivity() {
type = "text/*" type = "text/*"
putExtra(Intent.EXTRA_TITLE, fileName) putExtra(Intent.EXTRA_TITLE, fileName)
}.also { }.also {
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler( try {
onSuccess = { startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
it?.data?.let { uri -> onSuccess = {
Log.v(TAG, "Save file to $uri") it?.data?.let { uri ->
try { Log.v(TAG, "Save file to $uri")
contentResolver.openOutputStream(uri)?.use { os -> try {
os.bufferedWriter().use { it.write(data) } contentResolver.openOutputStream(uri)?.use { os ->
os.bufferedWriter().use { it.write(data) }
}
} catch (e: IOException) {
Log.e(TAG, "Failed to save file $uri: $e")
// todo: send error to Qt
} }
} catch (e: IOException) {
Log.e(TAG, "Failed to save file $uri: $e")
// todo: send error to Qt
} }
} }
} ))
)) } catch (_: ActivityNotFoundException) {
Toast.makeText(this@AmneziaActivity, "Unsupported", Toast.LENGTH_LONG).show()
}
} }
} }
} }
@ -537,35 +550,46 @@ 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 mimeTypes = if (!filter.isNullOrEmpty()) { val intent = if (!isOnTv()) {
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE) val mimeTypes = if (!filter.isNullOrEmpty()) {
val mime = MimeTypeMap.getSingleton() val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
extensionRegex.findAll(filter).map { val mime = MimeTypeMap.getSingleton()
it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*" extensionRegex.findAll(filter).map {
}.toSet() it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
} else emptySet() }.toSet()
} else emptySet()
Intent(Intent.ACTION_OPEN_DOCUMENT).apply { Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
Log.v(TAG, "File mimyType filter: $mimeTypes") Log.v(TAG, "File mimyType filter: $mimeTypes")
if ("*/*" in mimeTypes) { if ("*/*" in mimeTypes) {
type = "*/*" type = "*/*"
} else { } else {
when (mimeTypes.size) { when (mimeTypes.size) {
1 -> type = mimeTypes.first() 1 -> type = mimeTypes.first()
in 2..Int.MAX_VALUE -> { in 2..Int.MAX_VALUE -> {
type = "*/*" type = "*/*"
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray()) putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
}
else -> type = "*/*"
} }
else -> type = "*/*"
} }
} }
}.also { } else {
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler( 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,10 +597,68 @@ 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")
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA) fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
@ -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

@ -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

@ -1,225 +1,227 @@
<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/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/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/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>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

@ -0,0 +1,210 @@
#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 { QSharedPointer<QQuickItem>() },
m_lvfc { nullptr }
{
QObject::connect(m_engine.get(), &QQmlApplicationEngine::objectCreated, this,
[this](QObject *object, const QUrl &url) {
QQuickItem *newDefaultFocusItem = object->findChild<QQuickItem *>("defaultFocusItem");
if (newDefaultFocusItem && m_defaultFocusItem != newDefaultFocusItem) {
m_defaultFocusItem.reset(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.get());
}
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();
QSharedPointer<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;
QSharedPointer<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)) { emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false);
QString data = file.readAll(); return false;
m_configFileName = QFileInfo(file.fileName()).fileName();
return extractConfigFromData(data);
} }
m_configFileName = QFileInfo(QFile(fileName).fileName()).fileName();
emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false); #ifdef Q_OS_ANDROID
return false; 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);
} }

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

@ -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,167 +30,148 @@ 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 }
Header2Type {
id: header
Layout.fillWidth: true
Layout.topMargin: 16
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Choose language")
} }
} }
FlickableType { ListView {
id: listView
anchors.top: backButtonLayout.bottom anchors.top: backButtonLayout.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
contentHeight: content.implicitHeight
ColumnLayout { property bool isFocusable: true
id: content property int selectedIndex: LanguageModel.currentLanguageIndex
anchors.fill: parent clip: true
reuseItems: true
Header2Type { ScrollBar.vertical: ScrollBarType {}
id: header
Layout.fillWidth: true
Layout.topMargin: 16
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Choose language") model: LanguageModel
}
ListView { ButtonGroup {
id: listView id: buttonGroup
}
Layout.fillWidth: true delegate: Item {
height: listView.contentItem.height implicitWidth: root.width
implicitHeight: delegateContent.implicitHeight
clip: true ColumnLayout {
interactive: false id: delegateContent
model: LanguageModel anchors.fill: parent
currentIndex: LanguageModel.currentLanguageIndex
ButtonGroup { RadioButton {
id: buttonGroup id: radioButton
}
property int currentFocusIndex: 0 implicitWidth: parent.width
implicitHeight: radioButtonContent.implicitHeight
activeFocusOnTab: true hoverEnabled: true
onActiveFocusChanged: {
if (activeFocus) {
this.currentFocusIndex = 0
this.itemAtIndex(currentFocusIndex).forceActiveFocus()
}
}
Keys.onTabPressed: { property bool isFocusable: true
if (currentFocusIndex < this.count - 1) {
currentFocusIndex += 1
this.itemAtIndex(currentFocusIndex).forceActiveFocus()
} else {
listViewFocusItem.forceActiveFocus()
focusItem.forceActiveFocus()
}
}
Item {
id: listViewFocusItem
Keys.onTabPressed: { Keys.onTabPressed: {
root.forceActiveFocus() FocusController.nextKeyTabItem()
} }
}
onVisibleChanged: { Keys.onBacktabPressed: {
if (visible) { FocusController.previousKeyTabItem()
listViewFocusItem.forceActiveFocus()
focusItem.forceActiveFocus()
} }
}
delegate: Item { Keys.onUpPressed: {
implicitWidth: root.width FocusController.nextKeyUpItem()
implicitHeight: delegateContent.implicitHeight }
onActiveFocusChanged: { Keys.onDownPressed: {
if (activeFocus) { FocusController.nextKeyDownItem()
radioButton.forceActiveFocus() }
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
indicator: Rectangle {
width: parent.width - 1
height: parent.height
color: radioButton.hovered ? AmneziaStyle.color.slateGray : AmneziaStyle.color.onyxBlack
border.color: radioButton.focus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.transparent
border.width: radioButton.focus ? 1 : 0
Behavior on color {
PropertyAnimation { duration: 200 }
}
Behavior on border.color {
PropertyAnimation { duration: 200 }
} }
} }
ColumnLayout { RowLayout {
id: delegateContent id: radioButtonContent
anchors.fill: parent anchors.fill: parent
RadioButton { anchors.rightMargin: 16
id: radioButton anchors.leftMargin: 16
implicitWidth: parent.width spacing: 0
implicitHeight: radioButtonContent.implicitHeight
hoverEnabled: true z: 1
indicator: Rectangle { ParagraphTextType {
width: parent.width - 1 Layout.fillWidth: true
height: parent.height Layout.topMargin: 20
color: radioButton.hovered ? AmneziaStyle.color.slateGray : AmneziaStyle.color.onyxBlack Layout.bottomMargin: 20
border.color: radioButton.focus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.transparent
border.width: radioButton.focus ? 1 : 0
Behavior on color { text: languageName
PropertyAnimation { duration: 200 } }
}
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
}
RowLayout { Image {
id: radioButtonContent source: "qrc:/images/controls/check.svg"
anchors.fill: parent visible: radioButton.checked
anchors.rightMargin: 16 width: 24
anchors.leftMargin: 16 height: 24
spacing: 0 Layout.rightMargin: 8
z: 1
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 20
Layout.bottomMargin: 20
text: languageName
}
Image {
source: "qrc:/images/controls/check.svg"
visible: radioButton.checked
width: 24
height: 24
Layout.rightMargin: 8
}
}
ButtonGroup.group: buttonGroup
checked: listView.currentIndex === index
onClicked: {
listView.currentIndex = index
LanguageModel.changeLanguage(languageIndex)
root.close()
}
} }
} }
Keys.onEnterPressed: radioButton.clicked() ButtonGroup.group: buttonGroup
Keys.onReturnPressed: radioButton.clicked() checked: listView.selectedIndex === index
onClicked: {
listView.selectedIndex = index
LanguageModel.changeLanguage(languageIndex)
root.closeTriggered()
}
} }
} }
Keys.onEnterPressed: radioButton.clicked()
Keys.onReturnPressed: radioButton.clicked()
} }
} }
} }

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 anchors.left: parent.left
anchors.right: parent.right
ColumnLayout { property bool isFocusable: true
id: content
anchors.top: parent.top ScrollBar.vertical: ScrollBarType {}
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16 model: 1
anchors.rightMargin: 16
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

@ -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)
} }
} }
@ -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,27 +209,10 @@ 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
@ -218,61 +224,35 @@ Item {
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 { Column {
id: flickable id: col
anchors.top: header.bottom anchors.top: header.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16 anchors.topMargin: 16
contentHeight: col.implicitHeight
Column { spacing: 16
id: col
anchors.top: parent.top Header2Type {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 16 headerText: root.headerText
Header2Type { width: parent.width
anchors.left: parent.left }
anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
headerText: root.headerText Loader {
id: listViewLoader
width: parent.width sourceComponent: root.listView
}
Loader {
id: listViewLoader
sourceComponent: root.listView
onLoaded: {
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,155 +20,132 @@ 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
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 {
id: content
property alias selectable: radioButton
implicitWidth: rootWidth implicitWidth: rootWidth
implicitHeight: content.implicitHeight
onActiveFocusChanged: { RadioButton {
if (activeFocus) { id: radioButton
radioButton.forceActiveFocus()
implicitWidth: parent.width
implicitHeight: radioButtonContent.implicitHeight
hoverEnabled: true
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
} }
}
ColumnLayout { Keys.onBacktabPressed: {
id: content FocusController.previousKeyTabItem()
}
anchors.fill: parent Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
RadioButton { Keys.onDownPressed: {
id: radioButton FocusController.nextKeyDownItem()
}
implicitWidth: parent.width Keys.onLeftPressed: {
implicitHeight: radioButtonContent.implicitHeight FocusController.nextKeyLeftItem()
}
hoverEnabled: true Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
indicator: Rectangle { indicator: Rectangle {
width: parent.width - 1 width: parent.width - 1
height: parent.height height: parent.height
color: radioButton.hovered ? AmneziaStyle.color.slateGray : AmneziaStyle.color.onyxBlack color: radioButton.hovered ? AmneziaStyle.color.slateGray : AmneziaStyle.color.onyxBlack
border.color: radioButton.focus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.transparent border.color: radioButton.focus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.transparent
border.width: radioButton.focus ? 1 : 0 border.width: radioButton.focus ? 1 : 0
Behavior on color { Behavior on color {
PropertyAnimation { duration: 200 } PropertyAnimation { duration: 200 }
} }
Behavior on border.color { Behavior on border.color {
PropertyAnimation { duration: 200 } PropertyAnimation { duration: 200 }
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
} }
RowLayout { MouseArea {
id: radioButtonContent
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
}
anchors.rightMargin: 16 RowLayout {
anchors.leftMargin: 16 id: radioButtonContent
anchors.fill: parent
z: 1 anchors.rightMargin: 16
anchors.leftMargin: 16
ParagraphTextType { z: 1
Layout.fillWidth: true
Layout.topMargin: 20
Layout.bottomMargin: 20
text: name ParagraphTextType {
maximumLineCount: root.textMaximumLineCount Layout.fillWidth: true
elide: root.textElide Layout.topMargin: 20
Layout.bottomMargin: 20
} text: name
maximumLineCount: root.textMaximumLineCount
elide: root.textElide
Image {
source: imageSource
visible: radioButton.checked
width: 24
height: 24
Layout.rightMargin: 8
}
} }
ButtonGroup.group: buttonGroup Image {
checked: root.currentIndex === index source: imageSource
visible: radioButton.checked
onClicked: { width: 24
root.currentIndex = index height: 24
root.selectedText = name
if (clickedFunction && typeof clickedFunction === "function") { Layout.rightMargin: 8
clickedFunction() }
} }
ButtonGroup.group: buttonGroup
checked: root.selectedIndex === index
onClicked: {
root.selectedIndex = index
root.selectedText = name
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
} }
} }
} }
Component.onCompleted: { Component.onCompleted: {
if (root.currentIndex === index) { if (root.selectedIndex === index) {
root.selectedText = name root.selectedText = name
} }
} }

View file

@ -9,53 +9,22 @@ 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) { console.debug(">>> PageType timer triggered")
defaultActiveFocusItem.forceActiveFocus() FocusController.resetRootObject()
} 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)
root.checked = !root.checked Keys.onReturnPressed: event => handleSwitch(event)
root.checkedChanged() Keys.onSpacePressed: event => handleSwitch(event)
}
Keys.onReturnPressed: { function handleSwitch(event) {
root.checked = !root.checked if (!event.isAutoRepeat) {
root.checkedChanged() root.checked = !root.checked
root.checkedChanged()
}
event.accepted = true
} }
} }

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
@ -41,7 +66,7 @@ TabButton {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: false enabled: false
} }
Keys.onEnterPressed: { Keys.onEnterPressed: {
if (root.clickedFunc && typeof root.clickedFunc === "function") { if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc() root.clickedFunc()

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

@ -16,40 +16,28 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem BackButtonType {
id: backButton
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout {
id: backButtonLayout
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20
BackButtonType {
id: backButton
// KeyNavigation.tab: removeButton
}
} }
FlickableType { ListView {
id: fl id: listView
anchors.top: backButtonLayout.bottom anchors.top: backButton.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
contentHeight: content.implicitHeight anchors.right: parent.right
anchors.left: parent.left
ColumnLayout { property bool isFocusable: true
id: content
anchors.top: parent.top ScrollBar.vertical: ScrollBarType {}
anchors.left: parent.left
anchors.right: parent.right 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

@ -19,13 +19,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 +33,22 @@ PageType {
} }
Item { Item {
objectName: "homeColumnItem"
anchors.fill: parent anchors.fill: parent
anchors.bottomMargin: drawer.collapsedHeight anchors.bottomMargin: drawer.collapsedHeight
ColumnLayout { ColumnLayout {
objectName: "homeColumnLayout"
anchors.fill: parent anchors.fill: parent
anchors.topMargin: 34 anchors.topMargin: 34
anchors.bottomMargin: 34 anchors.bottomMargin: 34
Item {
id: focusItem
KeyNavigation.tab: loggingButton.visible ?
loggingButton :
connectButton
}
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 +68,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,13 +75,15 @@ 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 Layout.bottomMargin: 34
@ -116,53 +115,37 @@ PageType {
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
parent: root objectName: "homeSplitTunnelingDrawer"
onClosed: { parent: root
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
@ -181,6 +164,8 @@ PageType {
} }
RowLayout { RowLayout {
objectName: "rowLayout"
Layout.topMargin: 14 Layout.topMargin: 14
Layout.leftMargin: 24 Layout.leftMargin: 24
Layout.rightMargin: 24 Layout.rightMargin: 24
@ -189,9 +174,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 {
@ -199,8 +186,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 {
@ -209,7 +196,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 {
@ -220,6 +207,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
@ -228,8 +217,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 }
} }
@ -237,10 +224,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"
@ -255,18 +243,17 @@ 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.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16
@ -305,18 +292,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
@ -328,13 +306,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
@ -345,28 +319,29 @@ 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: { height: 500 // TODO: make calculated
if (containersDropDown.visible && !GC.isMobile()) {
focusItem1.forceActiveFocus()
}
}
Connections { Connections {
objectName: "rowLayoutConnections"
target: ServersModel target: ServersModel
function onDefaultServerIndexChanged() { function onDefaultServerIndexChanged() {
@ -408,167 +383,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,229 +27,235 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: listview.currentItem.mtuTextField.textField
} }
} }
FlickableType { ListView {
id: fl id: listview
anchors.top: backButtonLayout.bottom anchors.top: backButtonLayout.bottom
anchors.bottom: parent.bottom anchors.bottom: saveButton.top
contentHeight: content.implicitHeight + saveButton.implicitHeight + saveButton.anchors.bottomMargin + saveButton.anchors.topMargin
Column { width: parent.width
id: content
anchors.top: parent.top clip: true
anchors.left: parent.left
anchors.right: parent.right
ListView { property bool isFocusable: true
id: listview
width: parent.width Keys.onTabPressed: {
height: listview.contentItem.height FocusController.nextKeyTabItem()
}
clip: true Keys.onBacktabPressed: {
interactive: false FocusController.previousKeyTabItem()
}
model: AwgConfigModel Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
delegate: Item { Keys.onDownPressed: {
id: delegateItem FocusController.nextKeyDownItem()
implicitWidth: listview.width }
implicitHeight: col.implicitHeight
property alias mtuTextField: mtuTextField Keys.onLeftPressed: {
property bool isSaveButtonEnabled: mtuTextField.errorText === "" && FocusController.nextKeyLeftItem()
junkPacketMaxSizeTextField.errorText === "" && }
junkPacketMinSizeTextField.errorText === "" &&
junkPacketCountTextField.errorText === ""
ColumnLayout { Keys.onRightPressed: {
id: col FocusController.nextKeyRightItem()
}
anchors.top: parent.top model: AwgConfigModel
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16 delegate: Item {
anchors.rightMargin: 16 id: delegateItem
implicitWidth: listview.width
implicitHeight: col.implicitHeight
spacing: 0 property alias mtuTextField: mtuTextField
property bool isSaveButtonEnabled: mtuTextField.errorText === "" &&
junkPacketMaxSizeTextField.errorText === "" &&
junkPacketMinSizeTextField.errorText === "" &&
junkPacketCountTextField.errorText === ""
HeaderType { ColumnLayout {
Layout.fillWidth: true id: col
headerText: qsTr("AmneziaWG settings") anchors.top: parent.top
} anchors.left: parent.left
anchors.right: parent.right
TextFieldWithHeaderType { anchors.leftMargin: 16
id: mtuTextField anchors.rightMargin: 16
Layout.fillWidth: true
Layout.topMargin: 40
headerText: qsTr("MTU") spacing: 0
textFieldText: clientMtu
textField.validator: IntValidator { bottom: 576; top: 65535 }
textField.onEditingFinished: { HeaderType {
if (textFieldText !== clientMtu) { Layout.fillWidth: true
clientMtu = textFieldText
}
}
checkEmptyText: true
KeyNavigation.tab: junkPacketCountTextField.textField
}
TextFieldWithHeaderType { headerText: qsTr("AmneziaWG settings")
id: junkPacketCountTextField }
Layout.fillWidth: true
Layout.topMargin: 16
headerText: "Jc - Junk packet count" TextFieldWithHeaderType {
textFieldText: clientJunkPacketCount id: mtuTextField
textField.validator: IntValidator { bottom: 0 } Layout.fillWidth: true
parentFlickable: fl Layout.topMargin: 40
textField.onEditingFinished: { headerText: qsTr("MTU")
if (textFieldText !== clientJunkPacketCount) { textFieldText: clientMtu
clientJunkPacketCount = textFieldText textField.validator: IntValidator { bottom: 576; top: 65535 }
}
}
checkEmptyText: true textField.onEditingFinished: {
if (textFieldText !== clientMtu) {
KeyNavigation.tab: junkPacketMinSizeTextField.textField clientMtu = textFieldText
}
TextFieldWithHeaderType {
id: junkPacketMinSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: "Jmin - Junk packet minimum size"
textFieldText: clientJunkPacketMinSize
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
if (textFieldText !== clientJunkPacketMinSize) {
clientJunkPacketMinSize = textFieldText
}
}
checkEmptyText: true
KeyNavigation.tab: junkPacketMaxSizeTextField.textField
}
TextFieldWithHeaderType {
id: junkPacketMaxSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: "Jmax - Junk packet maximum size"
textFieldText: clientJunkPacketMaxSize
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
if (textFieldText !== clientJunkPacketMaxSize) {
clientJunkPacketMaxSize = textFieldText
}
}
checkEmptyText: true
Keys.onTabPressed: saveButton.forceActiveFocus()
}
Header2TextType {
Layout.fillWidth: true
Layout.topMargin: 16
text: qsTr("Server settings")
}
TextFieldWithHeaderType {
id: portTextField
Layout.fillWidth: true
Layout.topMargin: 8
enabled: false
headerText: qsTr("Port")
textFieldText: port
}
TextFieldWithHeaderType {
id: initPacketJunkSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: false
headerText: "S1 - Init packet junk size"
textFieldText: serverInitPacketJunkSize
}
TextFieldWithHeaderType {
id: responsePacketJunkSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: false
headerText: "S2 - Response packet junk size"
textFieldText: serverResponsePacketJunkSize
}
TextFieldWithHeaderType {
id: initPacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: false
headerText: "H1 - Init packet magic header"
textFieldText: serverInitPacketMagicHeader
}
TextFieldWithHeaderType {
id: responsePacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: false
headerText: "H2 - Response packet magic header"
textFieldText: serverResponsePacketMagicHeader
}
TextFieldWithHeaderType {
id: underloadPacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
parentFlickable: fl
enabled: false
headerText: "H3 - Underload packet magic header"
textFieldText: serverUnderloadPacketMagicHeader
}
TextFieldWithHeaderType {
id: transportPacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: false
headerText: "H4 - Transport packet magic header"
textFieldText: serverTransportPacketMagicHeader
} }
} }
checkEmptyText: true
KeyNavigation.tab: junkPacketCountTextField.textField
}
TextFieldWithHeaderType {
id: junkPacketCountTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: "Jc - Junk packet count"
textFieldText: clientJunkPacketCount
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textFieldText !== clientJunkPacketCount) {
clientJunkPacketCount = textFieldText
}
}
checkEmptyText: true
KeyNavigation.tab: junkPacketMinSizeTextField.textField
}
TextFieldWithHeaderType {
id: junkPacketMinSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: "Jmin - Junk packet minimum size"
textFieldText: clientJunkPacketMinSize
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textFieldText !== clientJunkPacketMinSize) {
clientJunkPacketMinSize = textFieldText
}
}
checkEmptyText: true
KeyNavigation.tab: junkPacketMaxSizeTextField.textField
}
TextFieldWithHeaderType {
id: junkPacketMaxSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: "Jmax - Junk packet maximum size"
textFieldText: clientJunkPacketMaxSize
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textFieldText !== clientJunkPacketMaxSize) {
clientJunkPacketMaxSize = textFieldText
}
}
checkEmptyText: true
}
Header2TextType {
Layout.fillWidth: true
Layout.topMargin: 16
text: qsTr("Server settings")
}
TextFieldWithHeaderType {
id: portTextField
Layout.fillWidth: true
Layout.topMargin: 8
enabled: false
headerText: qsTr("Port")
textFieldText: port
}
TextFieldWithHeaderType {
id: initPacketJunkSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: false
headerText: "S1 - Init packet junk size"
textFieldText: serverInitPacketJunkSize
}
TextFieldWithHeaderType {
id: responsePacketJunkSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: false
headerText: "S2 - Response packet junk size"
textFieldText: serverResponsePacketJunkSize
}
TextFieldWithHeaderType {
id: initPacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: false
headerText: "H1 - Init packet magic header"
textFieldText: serverInitPacketMagicHeader
}
TextFieldWithHeaderType {
id: responsePacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: false
headerText: "H2 - Response packet magic header"
textFieldText: serverResponsePacketMagicHeader
}
TextFieldWithHeaderType {
id: underloadPacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: false
headerText: "H3 - Underload packet magic header"
textFieldText: serverUnderloadPacketMagicHeader
}
TextFieldWithHeaderType {
id: transportPacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: false
headerText: "H4 - Transport packet magic header"
textFieldText: serverTransportPacketMagicHeader
} }
} }
} }
@ -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,341 +30,357 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: listview.currentItem.portTextField.textField
} }
} }
FlickableType { ListView {
id: fl id: listview
anchors.top: backButtonLayout.bottom anchors.top: backButtonLayout.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
contentHeight: content.implicitHeight
Column { width: parent.width
id: content
anchors.top: parent.top property bool isFocusable: true
anchors.left: parent.left
anchors.right: parent.right
ListView { Keys.onTabPressed: {
id: listview FocusController.nextKeyTabItem()
}
width: parent.width Keys.onBacktabPressed: {
height: listview.contentItem.height FocusController.previousKeyTabItem()
}
clip: true Keys.onUpPressed: {
interactive: false FocusController.nextKeyUpItem()
}
model: AwgConfigModel Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
delegate: Item { Keys.onLeftPressed: {
id: delegateItem FocusController.nextKeyLeftItem()
implicitWidth: listview.width }
implicitHeight: col.implicitHeight
property alias portTextField: portTextField Keys.onRightPressed: {
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() FocusController.nextKeyRightItem()
}
ColumnLayout { clip: true
id: col
anchors.top: parent.top model: AwgConfigModel
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16 delegate: Item {
anchors.rightMargin: 16 id: delegateItem
implicitWidth: listview.width
implicitHeight: col.implicitHeight
spacing: 0 property alias portTextField: portTextField
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
HeaderType { ColumnLayout {
Layout.fillWidth: true id: col
headerText: qsTr("AmneziaWG settings") anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 0
HeaderType {
Layout.fillWidth: true
headerText: qsTr("AmneziaWG settings")
}
TextFieldWithHeaderType {
id: portTextField
Layout.fillWidth: true
Layout.topMargin: 40
enabled: delegateItem.isEnabled
headerText: qsTr("Port")
textFieldText: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: {
if (textFieldText !== port) {
port = textFieldText
}
}
checkEmptyText: true
}
TextFieldWithHeaderType {
id: mtuTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("MTU")
textFieldText: mtu
textField.validator: IntValidator { bottom: 576; top: 65535 }
textField.onEditingFinished: {
if (textFieldText === "") {
textFieldText = "0"
}
if (textFieldText !== mtu) {
mtu = textFieldText
}
}
checkEmptyText: true
}
TextFieldWithHeaderType {
id: junkPacketCountTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("Jc - Junk packet count")
textFieldText: serverJunkPacketCount
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textFieldText === "") {
textFieldText = "0"
} }
TextFieldWithHeaderType { if (textFieldText !== serverJunkPacketCount) {
id: portTextField serverJunkPacketCount = textFieldText
Layout.fillWidth: true }
Layout.topMargin: 40 }
enabled: delegateItem.isEnabled checkEmptyText: true
}
headerText: qsTr("Port") TextFieldWithHeaderType {
textFieldText: port id: junkPacketMinSizeTextField
textField.maximumLength: 5 Layout.fillWidth: true
textField.validator: IntValidator { bottom: 1; top: 65535 } Layout.topMargin: 16
parentFlickable: fl
textField.onEditingFinished: { headerText: qsTr("Jmin - Junk packet minimum size")
if (textFieldText !== port) { textFieldText: serverJunkPacketMinSize
port = textFieldText textField.validator: IntValidator { bottom: 0 }
}
textField.onEditingFinished: {
if (textFieldText !== serverJunkPacketMinSize) {
serverJunkPacketMinSize = textFieldText
}
}
checkEmptyText: true
}
TextFieldWithHeaderType {
id: junkPacketMaxSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("Jmax - Junk packet maximum size")
textFieldText: serverJunkPacketMaxSize
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textFieldText !== serverJunkPacketMaxSize) {
serverJunkPacketMaxSize = textFieldText
}
}
checkEmptyText: true
}
TextFieldWithHeaderType {
id: initPacketJunkSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("S1 - Init packet junk size")
textFieldText: serverInitPacketJunkSize
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textFieldText !== serverInitPacketJunkSize) {
serverInitPacketJunkSize = textFieldText
}
}
checkEmptyText: true
onActiveFocusChanged: {
if(activeFocus) {
listview.positionViewAtEnd()
}
}
}
TextFieldWithHeaderType {
id: responsePacketJunkSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("S2 - Response packet junk size")
textFieldText: serverResponsePacketJunkSize
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textFieldText !== serverResponsePacketJunkSize) {
serverResponsePacketJunkSize = textFieldText
}
}
checkEmptyText: true
onActiveFocusChanged: {
if(activeFocus) {
listview.positionViewAtEnd()
}
}
}
TextFieldWithHeaderType {
id: initPacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("H1 - Init packet magic header")
textFieldText: serverInitPacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textFieldText !== serverInitPacketMagicHeader) {
serverInitPacketMagicHeader = textFieldText
}
}
checkEmptyText: true
}
TextFieldWithHeaderType {
id: responsePacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("H2 - Response packet magic header")
textFieldText: serverResponsePacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textFieldText !== serverResponsePacketMagicHeader) {
serverResponsePacketMagicHeader = textFieldText
}
}
checkEmptyText: true
}
TextFieldWithHeaderType {
id: transportPacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("H4 - Transport packet magic header")
textFieldText: serverTransportPacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textFieldText !== serverTransportPacketMagicHeader) {
serverTransportPacketMagicHeader = textFieldText
}
}
checkEmptyText: true
}
TextFieldWithHeaderType {
id: underloadPacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("H3 - Underload packet magic header")
textFieldText: serverUnderloadPacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textFieldText !== serverUnderloadPacketMagicHeader) {
serverUnderloadPacketMagicHeader = textFieldText
}
}
checkEmptyText: true
}
BasicButtonType {
id: saveRestartButton
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 24
enabled: underloadPacketMagicHeaderTextField.errorText === "" &&
transportPacketMagicHeaderTextField.errorText === "" &&
responsePacketMagicHeaderTextField.errorText === "" &&
initPacketMagicHeaderTextField.errorText === "" &&
responsePacketJunkSizeTextField.errorText === "" &&
initPacketJunkSizeTextField.errorText === "" &&
junkPacketMaxSizeTextField.errorText === "" &&
junkPacketMinSizeTextField.errorText === "" &&
junkPacketCountTextField.errorText === "" &&
portTextField.errorText === ""
text: qsTr("Save")
onActiveFocusChanged: {
if(activeFocus) {
listview.positionViewAtEnd()
}
}
clickedFunc: function() {
forceActiveFocus()
if (delegateItem.isEnabled) {
if (AwgConfigModel.isHeadersEqual(underloadPacketMagicHeaderTextField.textField.text,
transportPacketMagicHeaderTextField.textField.text,
responsePacketMagicHeaderTextField.textField.text,
initPacketMagicHeaderTextField.textField.text)) {
PageController.showErrorMessage(qsTr("The values of the H1-H4 fields must be unique"))
return
} }
checkEmptyText: true if (AwgConfigModel.isPacketSizeEqual(parseInt(initPacketJunkSizeTextField.textField.text),
parseInt(responsePacketJunkSizeTextField.textField.text))) {
KeyNavigation.tab: junkPacketCountTextField.textField PageController.showErrorMessage(qsTr("The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92)"))
} return
TextFieldWithHeaderType {
id: junkPacketCountTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("Jc - Junk packet count")
textFieldText: serverJunkPacketCount
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
if (textFieldText === "") {
textFieldText = "0"
}
if (textFieldText !== serverJunkPacketCount) {
serverJunkPacketCount = textFieldText
}
}
checkEmptyText: true
KeyNavigation.tab: junkPacketMinSizeTextField.textField
}
TextFieldWithHeaderType {
id: junkPacketMinSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("Jmin - Junk packet minimum size")
textFieldText: serverJunkPacketMinSize
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
if (textFieldText !== serverJunkPacketMinSize) {
serverJunkPacketMinSize = textFieldText
}
}
checkEmptyText: true
KeyNavigation.tab: junkPacketMaxSizeTextField.textField
}
TextFieldWithHeaderType {
id: junkPacketMaxSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("Jmax - Junk packet maximum size")
textFieldText: serverJunkPacketMaxSize
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
if (textFieldText !== serverJunkPacketMaxSize) {
serverJunkPacketMaxSize = textFieldText
}
}
checkEmptyText: true
KeyNavigation.tab: initPacketJunkSizeTextField.textField
}
TextFieldWithHeaderType {
id: initPacketJunkSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("S1 - Init packet junk size")
textFieldText: serverInitPacketJunkSize
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
if (textFieldText !== serverInitPacketJunkSize) {
serverInitPacketJunkSize = textFieldText
}
}
checkEmptyText: true
KeyNavigation.tab: responsePacketJunkSizeTextField.textField
}
TextFieldWithHeaderType {
id: responsePacketJunkSizeTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("S2 - Response packet junk size")
textFieldText: serverResponsePacketJunkSize
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
if (textFieldText !== serverResponsePacketJunkSize) {
serverResponsePacketJunkSize = textFieldText
}
}
checkEmptyText: true
KeyNavigation.tab: initPacketMagicHeaderTextField.textField
}
TextFieldWithHeaderType {
id: initPacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("H1 - Init packet magic header")
textFieldText: serverInitPacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
if (textFieldText !== serverInitPacketMagicHeader) {
serverInitPacketMagicHeader = textFieldText
}
}
checkEmptyText: true
KeyNavigation.tab: responsePacketMagicHeaderTextField.textField
}
TextFieldWithHeaderType {
id: responsePacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("H2 - Response packet magic header")
textFieldText: serverResponsePacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
if (textFieldText !== serverResponsePacketMagicHeader) {
serverResponsePacketMagicHeader = textFieldText
}
}
checkEmptyText: true
KeyNavigation.tab: transportPacketMagicHeaderTextField.textField
}
TextFieldWithHeaderType {
id: transportPacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("H4 - Transport packet magic header")
textFieldText: serverTransportPacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
parentFlickable: fl
textField.onEditingFinished: {
if (textFieldText !== serverTransportPacketMagicHeader) {
serverTransportPacketMagicHeader = textFieldText
}
}
checkEmptyText: true
KeyNavigation.tab: underloadPacketMagicHeaderTextField.textField
}
TextFieldWithHeaderType {
id: underloadPacketMagicHeaderTextField
Layout.fillWidth: true
Layout.topMargin: 16
parentFlickable: fl
headerText: qsTr("H3 - Underload packet magic header")
textFieldText: serverUnderloadPacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textFieldText !== serverUnderloadPacketMagicHeader) {
serverUnderloadPacketMagicHeader = textFieldText
}
}
checkEmptyText: true
KeyNavigation.tab: saveRestartButton
}
BasicButtonType {
id: saveRestartButton
parentFlickable: fl
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 24
enabled: underloadPacketMagicHeaderTextField.errorText === "" &&
transportPacketMagicHeaderTextField.errorText === "" &&
responsePacketMagicHeaderTextField.errorText === "" &&
initPacketMagicHeaderTextField.errorText === "" &&
responsePacketJunkSizeTextField.errorText === "" &&
initPacketJunkSizeTextField.errorText === "" &&
junkPacketMaxSizeTextField.errorText === "" &&
junkPacketMinSizeTextField.errorText === "" &&
junkPacketCountTextField.errorText === "" &&
portTextField.errorText === ""
text: qsTr("Save")
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() {
forceActiveFocus()
if (delegateItem.isEnabled) {
if (AwgConfigModel.isHeadersEqual(underloadPacketMagicHeaderTextField.textField.text,
transportPacketMagicHeaderTextField.textField.text,
responsePacketMagicHeaderTextField.textField.text,
initPacketMagicHeaderTextField.textField.text)) {
PageController.showErrorMessage(qsTr("The values of the H1-H4 fields must be unique"))
return
}
if (AwgConfigModel.isPacketSizeEqual(parseInt(initPacketJunkSizeTextField.textField.text),
parseInt(responsePacketJunkSizeTextField.textField.text))) {
PageController.showErrorMessage(qsTr("The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92)"))
return
}
}
var headerText = qsTr("Save settings?")
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(AwgConfigModel.getConfig())
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
saveRestartButton.forceActiveFocus()
}
}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
} }
} }
var headerText = qsTr("Save settings?")
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(AwgConfigModel.getConfig())
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
saveRestartButton.forceActiveFocus()
}
}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
} }
} }
} }

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 {
@ -199,9 +177,6 @@ PageType {
drawerParent: root drawerParent: root
parentFlickable: fl parentFlickable: fl
KeyNavigation.tab: cipherDropDown.enabled ?
cipherDropDown :
tlsAuthCheckBox
listView: ListViewWithRadioButtonType { listView: ListViewWithRadioButtonType {
id: hashListView id: hashListView
@ -224,7 +199,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: {
@ -252,8 +227,6 @@ PageType {
drawerParent: root drawerParent: root
parentFlickable: fl parentFlickable: fl
KeyNavigation.tab: tlsAuthCheckBox
listView: ListViewWithRadioButtonType { listView: ListViewWithRadioButtonType {
id: cipherListView id: cipherListView
@ -275,7 +248,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 +293,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 +310,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 +324,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 +342,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 +360,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 +382,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 +398,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

@ -150,8 +150,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,13 +56,6 @@ PageType {
model: WireGuardConfigModel model: WireGuardConfigModel
activeFocusOnTab: true
onActiveFocusChanged: {
if (activeFocus) {
listview.itemAtIndex(0)?.focusItemId.forceActiveFocus()
}
}
delegate: Item { delegate: Item {
id: delegateItem id: delegateItem
@ -109,8 +94,6 @@ PageType {
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
@ -120,6 +103,26 @@ PageType {
checkEmptyText: true checkEmptyText: true
} }
TextFieldWithHeaderType {
id: mtuTextField
Layout.fillWidth: true
Layout.topMargin: 16
headerText: qsTr("MTU")
textFieldText: mtu
textField.validator: IntValidator { bottom: 576; top: 65535 }
textField.onEditingFinished: {
if (textFieldText === "") {
textFieldText = "0"
}
if (textFieldText !== mtu) {
mtu = textFieldText
}
}
checkEmptyText: true
}
BasicButtonType { BasicButtonType {
id: saveButton id: saveButton
Layout.fillWidth: true Layout.fillWidth: true
@ -130,8 +133,6 @@ PageType {
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)
} }
@ -157,9 +144,7 @@ PageType {
text: qsTr("Close application") text: qsTr("Close application")
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 anchors.right: parent.right
anchors.left: parent.left
ColumnLayout { property bool isFocusable: true
id: content
anchors.top: parent.top Keys.onTabPressed: {
anchors.left: parent.left FocusController.nextKeyTabItem()
anchors.right: parent.right }
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") footer: ColumnLayout {
descriptionText: qsTr("For reviews and bug reports") width: listView.width
leftImageSource: "qrc:/images/controls/mail.svg"
KeyNavigation.tab: githubButton
parentFlickable: fl
clickedFunction: function() {
GC.copyToClipBoard(text)
PageController.showNotificationMessage(qsTr("Copied"))
}
}
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,35 +231,30 @@ 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")
} }
} }
BasicButtonType { BasicButtonType {
id: privacyPolicyButton id: privacyPolicyButton
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 16
Layout.topMargin: -15
implicitHeight: 25
defaultColor: AmneziaStyle.color.transparent Layout.alignment: Qt.AlignHCenter
hoveredColor: AmneziaStyle.color.translucentWhite Layout.bottomMargin: 16
pressedColor: AmneziaStyle.color.sheerWhite Layout.topMargin: -15
disabledColor: AmneziaStyle.color.mutedGray implicitHeight: 25
textColor: AmneziaStyle.color.goldenApricot
text: qsTr("Privacy Policy") defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.goldenApricot
Keys.onTabPressed: lastItemTabClicked() text: qsTr("Privacy Policy")
parentFlickable: fl
clickedFunc: function() { clickedFunc: function() {
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/policy") Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/policy")
} }
} }
} }
} }

View file

@ -31,78 +31,74 @@ PageType {
id: containersRadioButtonGroup id: containersRadioButtonGroup
} }
delegate: Item { delegate: ColumnLayout {
id: content
implicitWidth: parent.width implicitWidth: parent.width
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
ColumnLayout { RowLayout {
id: content VerticalRadioButton {
id: containerRadioButton
anchors.fill: parent
RowLayout {
VerticalRadioButton {
id: containerRadioButton
Layout.fillWidth: true
Layout.leftMargin: 16
text: countryName
ButtonGroup.group: containersRadioButtonGroup
imageSource: "qrc:/images/controls/download.svg"
checked: index === ApiCountryModel.currentIndex
checkable: !ConnectionController.isConnected
onClicked: {
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Unable change server location while there is an active connection"))
return
}
if (index !== ApiCountryModel.currentIndex) {
PageController.showBusyIndicator(true)
var prevIndex = ApiCountryModel.currentIndex
ApiCountryModel.currentIndex = index
if (!InstallController.updateServiceFromApi(ServersModel.defaultIndex, countryCode, countryName)) {
ApiCountryModel.currentIndex = prevIndex
}
}
}
MouseArea {
anchors.fill: containerRadioButton
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onEnterPressed: {
if (checkable) {
checked = true
}
containerRadioButton.clicked()
}
Keys.onReturnPressed: {
if (checkable) {
checked = true
}
containerRadioButton.clicked()
}
}
Image {
Layout.rightMargin: 32
Layout.alignment: Qt.AlignRight
source: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
}
}
DividerType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16
text: countryName
ButtonGroup.group: containersRadioButtonGroup
imageSource: "qrc:/images/controls/download.svg"
checked: index === ApiCountryModel.currentIndex
checkable: !ConnectionController.isConnected
onClicked: {
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Unable change server location while there is an active connection"))
return
}
if (index !== ApiCountryModel.currentIndex) {
PageController.showBusyIndicator(true)
var prevIndex = ApiCountryModel.currentIndex
ApiCountryModel.currentIndex = index
if (!InstallController.updateServiceFromApi(ServersModel.defaultIndex, countryCode, countryName)) {
ApiCountryModel.currentIndex = prevIndex
}
}
}
MouseArea {
anchors.fill: containerRadioButton
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onEnterPressed: {
if (checkable) {
checked = true
}
containerRadioButton.clicked()
}
Keys.onReturnPressed: {
if (checkable) {
checked = true
}
containerRadioButton.clicked()
}
} }
Image {
Layout.rightMargin: 32
Layout.alignment: Qt.AlignRight
source: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
}
}
DividerType {
Layout.fillWidth: true
} }
} }
} }

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")

View file

@ -21,8 +21,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
property bool pageEnabled property bool pageEnabled
Component.onCompleted: { Component.onCompleted: {
@ -48,13 +46,15 @@ PageType {
QtObject { QtObject {
id: onlyForwardApps id: onlyForwardApps
property string name: qsTr("Only the apps from the list should have access via VPN")
property int type: routeMode.onlyForwardApps readonly property string name: qsTr("Only the apps from the list should have access via VPN")
readonly property int type: routeMode.onlyForwardApps
} }
QtObject { QtObject {
id: allExceptApps id: allExceptApps
property string name: qsTr("Apps from the list should not have access via VPN")
property int type: routeMode.allExceptApps readonly property string name: qsTr("Apps from the list should not have access via VPN")
readonly property int type: routeMode.allExceptApps
} }
function getRouteModesModelIndex() { function getRouteModesModelIndex() {
@ -66,11 +66,6 @@ PageType {
} }
} }
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: header id: header
@ -82,7 +77,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: switcher
} }
RowLayout { RowLayout {
@ -103,10 +97,6 @@ PageType {
enabled: root.pageEnabled enabled: root.pageEnabled
KeyNavigation.tab: selector.enabled ?
selector :
searchField.textField
checked: AppSplitTunnelingModel.isTunnelingEnabled checked: AppSplitTunnelingModel.isTunnelingEnabled
onToggled: { onToggled: {
AppSplitTunnelingModel.toggleSplitTunneling(checked) AppSplitTunnelingModel.toggleSplitTunneling(checked)
@ -130,25 +120,23 @@ PageType {
enabled: Qt.platform.os === "android" && root.pageEnabled enabled: Qt.platform.os === "android" && root.pageEnabled
KeyNavigation.tab: searchField.textField
listView: ListViewWithRadioButtonType { listView: ListViewWithRadioButtonType {
rootWidth: root.width rootWidth: root.width
model: root.routeModesModel model: root.routeModesModel
currentIndex: getRouteModesModelIndex() selectedIndex: getRouteModesModelIndex()
clickedFunction: function() { clickedFunction: function() {
selector.text = selectedText selector.text = selectedText
selector.close() selector.closeTriggered()
if (AppSplitTunnelingModel.routeMode !== root.routeModesModel[currentIndex].type) { if (AppSplitTunnelingModel.routeMode !== root.routeModesModel[selectedIndex].type) {
AppSplitTunnelingModel.routeMode = root.routeModesModel[currentIndex].type AppSplitTunnelingModel.routeMode = root.routeModesModel[selectedIndex].type
} }
} }
Component.onCompleted: { Component.onCompleted: {
if (root.routeModesModel[currentIndex].type === AppSplitTunnelingModel.routeMode) { if (root.routeModesModel[selectedIndex].type === AppSplitTunnelingModel.routeMode) {
selector.text = selectedText selector.text = selectedText
} else { } else {
selector.text = root.routeModesModel[0].name selector.text = root.routeModesModel[0].name
@ -158,7 +146,7 @@ PageType {
Connections { Connections {
target: AppSplitTunnelingModel target: AppSplitTunnelingModel
function onRouteModeChanged() { function onRouteModeChanged() {
currentIndex = getRouteModesModelIndex() selectedIndex = getRouteModesModelIndex()
} }
} }
} }
@ -267,7 +255,6 @@ PageType {
textFieldPlaceholderText: qsTr("application name") textFieldPlaceholderText: qsTr("application name")
buttonImageSource: "qrc:/images/controls/plus.svg" buttonImageSource: "qrc:/images/controls/plus.svg"
Keys.onTabPressed: lastItemTabClicked(focusItem)
rightButtonClickedOnEnter: true rightButtonClickedOnEnter: true
clickedFunc: function() { clickedFunc: function() {
@ -281,7 +268,7 @@ PageType {
AppSplitTunnelingController.addApp(fileName) AppSplitTunnelingController.addApp(fileName)
} }
} else if (Qt.platform.os === "android"){ } else if (Qt.platform.os === "android"){
installedAppDrawer.open() installedAppDrawer.openTriggered()
} }
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)

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
@ -34,8 +21,6 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20
KeyNavigation.tab: GC.isMobile() ? switcher : switcherAutoStart
} }
FlickableType { FlickableType {
@ -77,8 +62,8 @@ PageType {
} }
} }
KeyNavigation.tab: Qt.platform.os === "android" && !SettingsController.isNotificationPermissionGranted ? // KeyNavigation.tab: Qt.platform.os === "android" && !SettingsController.isNotificationPermissionGranted ?
labelWithButtonNotification.rightButton : labelWithButtonLanguage.rightButton // labelWithButtonNotification.rightButton : labelWithButtonLanguage.rightButton
parentFlickable: fl parentFlickable: fl
} }
@ -95,7 +80,6 @@ PageType {
descriptionText: qsTr("Enable notifications to show the VPN state in the status bar") descriptionText: qsTr("Enable notifications to show the VPN state in the status bar")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
KeyNavigation.tab: labelWithButtonLanguage.rightButton
parentFlickable: fl parentFlickable: fl
clickedFunction: function() { clickedFunction: function() {
@ -117,7 +101,6 @@ PageType {
text: qsTr("Auto start") text: qsTr("Auto start")
descriptionText: qsTr("Launch the application every time the device is starts") descriptionText: qsTr("Launch the application every time the device is starts")
KeyNavigation.tab: switcherAutoConnect
parentFlickable: fl parentFlickable: fl
checked: SettingsController.isAutoStartEnabled() checked: SettingsController.isAutoStartEnabled()
@ -142,7 +125,6 @@ PageType {
text: qsTr("Auto connect") text: qsTr("Auto connect")
descriptionText: qsTr("Connect to VPN on app start") descriptionText: qsTr("Connect to VPN on app start")
KeyNavigation.tab: switcherStartMinimized
parentFlickable: fl parentFlickable: fl
checked: SettingsController.isAutoConnectEnabled() checked: SettingsController.isAutoConnectEnabled()
@ -167,7 +149,6 @@ PageType {
text: qsTr("Start minimized") text: qsTr("Start minimized")
descriptionText: qsTr("Launch application minimized") descriptionText: qsTr("Launch application minimized")
KeyNavigation.tab: labelWithButtonLanguage.rightButton
parentFlickable: fl parentFlickable: fl
checked: SettingsController.isStartMinimizedEnabled() checked: SettingsController.isStartMinimizedEnabled()
@ -190,11 +171,10 @@ PageType {
descriptionText: LanguageModel.currentLanguageName descriptionText: LanguageModel.currentLanguageName
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
KeyNavigation.tab: labelWithButtonLogging.rightButton
parentFlickable: fl parentFlickable: fl
clickedFunction: function() { clickedFunction: function() {
selectLanguageDrawer.open() selectLanguageDrawer.openTriggered()
} }
} }
@ -208,7 +188,6 @@ PageType {
descriptionText: SettingsController.isLoggingEnabled ? qsTr("Enabled") : qsTr("Disabled") descriptionText: SettingsController.isLoggingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
KeyNavigation.tab: labelWithButtonReset.rightButton
parentFlickable: fl parentFlickable: fl
clickedFunction: function() { clickedFunction: function() {
@ -226,7 +205,6 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
textColor: AmneziaStyle.color.vibrantRed textColor: AmneziaStyle.color.vibrantRed
Keys.onTabPressed: lastItemTabClicked()
parentFlickable: fl parentFlickable: fl
clickedFunction: function() { clickedFunction: function() {
@ -245,12 +223,12 @@ PageType {
} }
if (!GC.isMobile()) { if (!GC.isMobile()) {
root.defaultActiveFocusItem.forceActiveFocus() // root.defaultActiveFocusItem.forceActiveFocus()
} }
} }
var noButtonFunction = function() { var noButtonFunction = function() {
if (!GC.isMobile()) { if (!GC.isMobile()) {
root.defaultActiveFocusItem.forceActiveFocus() // root.defaultActiveFocusItem.forceActiveFocus()
} }
} }
@ -267,11 +245,5 @@ PageType {
width: root.width width: root.width
height: root.height height: root.height
onClosed: {
if (!GC.isMobile()) {
focusItem.forceActiveFocus()
}
}
} }
} }

View file

@ -17,8 +17,6 @@ import "../Controls2/TextTypes"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
Connections { Connections {
target: SettingsController target: SettingsController
@ -36,11 +34,6 @@ PageType {
} }
} }
Item {
id: focusItem
KeyNavigation.tab: backButton
}
BackButtonType { BackButtonType {
id: backButton id: backButton
@ -48,8 +41,6 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20
KeyNavigation.tab: makeBackupButton
} }
FlickableType { FlickableType {
@ -93,6 +84,8 @@ PageType {
text: qsTr("Make a backup") text: qsTr("Make a backup")
parentFlickable: fl
clickedFunc: function() { clickedFunc: function() {
var fileName = "" var fileName = ""
if (GC.isMobile()) { if (GC.isMobile()) {
@ -111,8 +104,6 @@ PageType {
PageController.showNotificationMessage(qsTr("Backup file saved")) PageController.showNotificationMessage(qsTr("Backup file saved"))
} }
} }
KeyNavigation.tab: restoreBackupButton
} }
BasicButtonType { BasicButtonType {
@ -129,6 +120,8 @@ PageType {
text: qsTr("Restore from backup") text: qsTr("Restore from backup")
parentFlickable: fl
clickedFunc: function() { clickedFunc: function() {
var filePath = SystemController.getFileName(qsTr("Open backup file"), var filePath = SystemController.getFileName(qsTr("Open backup file"),
qsTr("Backup files (*.backup)")) qsTr("Backup files (*.backup)"))
@ -136,8 +129,6 @@ PageType {
restoreBackup(filePath) restoreBackup(filePath)
} }
} }
Keys.onTabPressed: lastItemTabClicked()
} }
} }
} }

View file

@ -12,15 +12,8 @@ import "../Config"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android" property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android"
Item {
id: focusItem
KeyNavigation.tab: backButton
}
BackButtonType { BackButtonType {
id: backButton id: backButton
@ -28,8 +21,6 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20
KeyNavigation.tab: amneziaDnsSwitch
} }
FlickableType { FlickableType {
@ -67,8 +58,6 @@ PageType {
SettingsController.toggleAmneziaDns(checked) SettingsController.toggleAmneziaDns(checked)
} }
} }
KeyNavigation.tab: dnsServersButton.rightButton
} }
DividerType {} DividerType {}
@ -81,11 +70,11 @@ PageType {
descriptionText: qsTr("When AmneziaDNS is not used or installed") descriptionText: qsTr("When AmneziaDNS is not used or installed")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
parentFlickable: fl
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsDns) PageController.goToPage(PageEnum.PageSettingsDns)
} }
KeyNavigation.tab: splitTunnelingButton.rightButton
} }
DividerType {} DividerType {}
@ -98,19 +87,11 @@ PageType {
descriptionText: qsTr("Allows you to select which sites you want to access through the VPN") descriptionText: qsTr("Allows you to select which sites you want to access through the VPN")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
parentFlickable: fl
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsSplitTunneling) PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
} }
Keys.onTabPressed: {
if (splitTunnelingButton2.visible) {
return splitTunnelingButton2.rightButton.forceActiveFocus()
} else if (killSwitchSwitcher.visible) {
return killSwitchSwitcher.forceActiveFocus()
} else {
lastItemTabClicked()
}
}
} }
DividerType { DividerType {
@ -127,17 +108,11 @@ PageType {
descriptionText: qsTr("Allows you to use the VPN only for certain Apps") descriptionText: qsTr("Allows you to use the VPN only for certain Apps")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
parentFlickable: fl
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling) PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling)
} }
Keys.onTabPressed: {
if (killSwitchSwitcher.visible) {
return killSwitchSwitcher.forceActiveFocus()
} else {
lastItemTabClicked()
}
}
} }
DividerType { DividerType {
@ -154,6 +129,8 @@ PageType {
text: qsTr("KillSwitch") text: qsTr("KillSwitch")
descriptionText: qsTr("Disables your internet if your encrypted VPN connection drops out for any reason.") descriptionText: qsTr("Disables your internet if your encrypted VPN connection drops out for any reason.")
parentFlickable: fl
checked: SettingsController.isKillSwitchEnabled() checked: SettingsController.isKillSwitchEnabled()
checkable: !ConnectionController.isConnected checkable: !ConnectionController.isConnected
onCheckedChanged: { onCheckedChanged: {
@ -166,8 +143,6 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot change killSwitch settings during active connection")) PageController.showNotificationMessage(qsTr("Cannot change killSwitch settings during active connection"))
} }
} }
Keys.onTabPressed: lastItemTabClicked()
} }
DividerType { DividerType {

View file

@ -14,13 +14,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: primaryDns.textField
Item {
id: focusItem
KeyNavigation.tab: backButton
}
BackButtonType { BackButtonType {
id: backButton id: backButton
@ -28,8 +21,6 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20
KeyNavigation.tab: root.defaultActiveFocusItem
} }
FlickableType { FlickableType {
@ -80,8 +71,6 @@ PageType {
textField.validator: RegularExpressionValidator { textField.validator: RegularExpressionValidator {
regularExpression: InstallController.ipAddressRegExp() regularExpression: InstallController.ipAddressRegExp()
} }
KeyNavigation.tab: secondaryDns.textField
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -94,8 +83,6 @@ PageType {
textField.validator: RegularExpressionValidator { textField.validator: RegularExpressionValidator {
regularExpression: InstallController.ipAddressRegExp() regularExpression: InstallController.ipAddressRegExp()
} }
KeyNavigation.tab: restoreDefaultButton
} }
BasicButtonType { BasicButtonType {
@ -124,19 +111,17 @@ PageType {
PageController.showNotificationMessage(qsTr("Settings have been reset")) PageController.showNotificationMessage(qsTr("Settings have been reset"))
if (!GC.isMobile()) { if (!GC.isMobile()) {
defaultActiveFocusItem.forceActiveFocus() // defaultActiveFocusItem.forceActiveFocus()
} }
} }
var noButtonFunction = function() { var noButtonFunction = function() {
if (!GC.isMobile()) { if (!GC.isMobile()) {
defaultActiveFocusItem.forceActiveFocus() // defaultActiveFocusItem.forceActiveFocus()
} }
} }
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
} }
KeyNavigation.tab: saveButton
} }
BasicButtonType { BasicButtonType {
@ -155,8 +140,6 @@ PageType {
} }
PageController.showNotificationMessage(qsTr("Settings saved")) PageController.showNotificationMessage(qsTr("Settings saved"))
} }
Keys.onTabPressed: lastItemTabClicked(focusItem)
} }
} }
} }

View file

@ -16,13 +16,6 @@ import "../Controls2/TextTypes"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
Item {
id: focusItem
KeyNavigation.tab: backButton
}
BackButtonType { BackButtonType {
id: backButton id: backButton
@ -30,23 +23,22 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20
KeyNavigation.tab: switcher
} }
FlickableType { ListView {
id: fl id: listView
anchors.top: backButton.bottom anchors.top: backButton.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
contentHeight: content.height anchors.right: parent.right
anchors.left: parent.left
ColumnLayout { property bool isFocusable: true
id: content
anchors.top: parent.top ScrollBar.vertical: ScrollBarType {}
anchors.left: parent.left
anchors.right: parent.right header: ColumnLayout {
spacing: 0 width: listView.width
HeaderType { HeaderType {
Layout.fillWidth: true Layout.fillWidth: true
@ -60,6 +52,7 @@ PageType {
SwitcherType { SwitcherType {
id: switcher id: switcher
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
@ -68,7 +61,7 @@ PageType {
text: qsTr("Enable logs") text: qsTr("Enable logs")
checked: SettingsController.isLoggingEnabled checked: SettingsController.isLoggingEnabled
//KeyNavigation.tab: openFolderButton
onCheckedChanged: { onCheckedChanged: {
if (checked !== SettingsController.isLoggingEnabled) { if (checked !== SettingsController.isLoggingEnabled) {
SettingsController.isLoggingEnabled = checked SettingsController.isLoggingEnabled = checked
@ -79,7 +72,6 @@ PageType {
DividerType {} DividerType {}
LabelWithButtonType { LabelWithButtonType {
// id: labelWithButton2
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: -8 Layout.topMargin: -8
@ -87,8 +79,6 @@ PageType {
leftImageSource: "qrc:/images/controls/trash.svg" leftImageSource: "qrc:/images/controls/trash.svg"
isSmallLeftImage: true isSmallLeftImage: true
// KeyNavigation.tab: labelWithButton3
clickedFunction: function() { clickedFunction: function() {
var headerText = qsTr("Clear logs?") var headerText = qsTr("Clear logs?")
var yesButtonText = qsTr("Continue") var yesButtonText = qsTr("Continue")
@ -99,19 +89,28 @@ PageType {
SettingsController.clearLogs() SettingsController.clearLogs()
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
PageController.showNotificationMessage(qsTr("Logs have been cleaned up")) PageController.showNotificationMessage(qsTr("Logs have been cleaned up"))
if (!GC.isMobile()) {
focusItem.forceActiveFocus()
}
} }
var noButtonFunction = function() { var noButtonFunction = function() {
if (!GC.isMobile()) {
focusItem.forceActiveFocus()
}
} }
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
} }
} }
}
model: logTypes
clip: true
reuseItems: true
snapMode: ListView.SnapOneItem
delegate: ColumnLayout {
id: delegateContent
width: listView.width
enabled: isVisible
ListItemTitleType { ListItemTitleType {
Layout.fillWidth: true Layout.fillWidth: true
@ -119,7 +118,7 @@ PageType {
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
text: qsTr("Client logs") text: title
} }
ParagraphTextType { ParagraphTextType {
@ -129,11 +128,11 @@ PageType {
Layout.rightMargin: 16 Layout.rightMargin: 16
color: AmneziaStyle.color.mutedGray color: AmneziaStyle.color.mutedGray
text: qsTr("AmneziaVPN logs")
text: description
} }
LabelWithButtonType { LabelWithButtonType {
// id: labelWithButton2
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: -8 Layout.topMargin: -8
Layout.bottomMargin: -8 Layout.bottomMargin: -8
@ -142,17 +141,12 @@ PageType {
leftImageSource: "qrc:/images/controls/folder-open.svg" leftImageSource: "qrc:/images/controls/folder-open.svg"
isSmallLeftImage: true isSmallLeftImage: true
// KeyNavigation.tab: labelWithButton3 clickedFunction: openLogsHandler
clickedFunction: function() {
SettingsController.openLogsFolder()
}
} }
DividerType {} DividerType {}
LabelWithButtonType { LabelWithButtonType {
// id: labelWithButton2
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: -8 Layout.topMargin: -8
Layout.bottomMargin: -8 Layout.bottomMargin: -8
@ -161,114 +155,72 @@ PageType {
leftImageSource: "qrc:/images/controls/save.svg" leftImageSource: "qrc:/images/controls/save.svg"
isSmallLeftImage: true isSmallLeftImage: true
// KeyNavigation.tab: labelWithButton3 clickedFunction: exportLogsHandler
clickedFunction: function() {
var fileName = ""
if (GC.isMobile()) {
fileName = "AmneziaVPN.log"
} else {
fileName = SystemController.getFileName(qsTr("Save"),
qsTr("Logs files (*.log)"),
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN",
true,
".log")
}
if (fileName !== "") {
PageController.showBusyIndicator(true)
SettingsController.exportLogsFile(fileName)
PageController.showBusyIndicator(false)
PageController.showNotificationMessage(qsTr("Logs file saved"))
}
}
} }
DividerType {} DividerType {}
}
}
ListItemTitleType { property list<QtObject> logTypes: [
visible: !GC.isMobile() clientLogs,
serviceLogs
]
Layout.fillWidth: true QtObject {
Layout.topMargin: 32 id: clientLogs
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Service logs") readonly property string title: qsTr("Client logs")
readonly property string description: qsTr("AmneziaVPN logs")
readonly property bool isVisible: true
readonly property var openLogsHandler: function() {
SettingsController.openLogsFolder()
}
readonly property var exportLogsHandler: function() {
var fileName = ""
if (GC.isMobile()) {
fileName = "AmneziaVPN.log"
} else {
fileName = SystemController.getFileName(qsTr("Save"),
qsTr("Logs files (*.log)"),
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN",
true,
".log")
} }
if (fileName !== "") {
ParagraphTextType { PageController.showBusyIndicator(true)
visible: !GC.isMobile() SettingsController.exportLogsFile(fileName)
PageController.showBusyIndicator(false)
Layout.fillWidth: true PageController.showNotificationMessage(qsTr("Logs file saved"))
Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
color: AmneziaStyle.color.mutedGray
text: qsTr("AmneziaVPN-service logs")
} }
}
}
LabelWithButtonType { QtObject {
// id: labelWithButton2 id: serviceLogs
visible: !GC.isMobile() readonly property string title: qsTr("Service logs")
readonly property string description: qsTr("AmneziaVPN-service logs")
Layout.fillWidth: true readonly property bool isVisible: !GC.isMobile()
Layout.topMargin: -8 readonly property var openLogsHandler: function() {
Layout.bottomMargin: -8 SettingsController.openServiceLogsFolder()
}
text: qsTr("Open logs folder") readonly property var exportLogsHandler: function() {
leftImageSource: "qrc:/images/controls/folder-open.svg" var fileName = ""
isSmallLeftImage: true if (GC.isMobile()) {
fileName = "AmneziaVPN-service.log"
// KeyNavigation.tab: labelWithButton3 } else {
fileName = SystemController.getFileName(qsTr("Save"),
clickedFunction: function() { qsTr("Logs files (*.log)"),
SettingsController.openServiceLogsFolder() StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN-service",
} true,
".log")
} }
if (fileName !== "") {
DividerType { PageController.showBusyIndicator(true)
visible: !GC.isMobile() SettingsController.exportServiceLogsFile(fileName)
} PageController.showBusyIndicator(false)
PageController.showNotificationMessage(qsTr("Logs file saved"))
LabelWithButtonType {
// id: labelWithButton2
visible: !GC.isMobile()
Layout.fillWidth: true
Layout.topMargin: -8
Layout.bottomMargin: -8
text: qsTr("Export logs")
leftImageSource: "qrc:/images/controls/save.svg"
isSmallLeftImage: true
// KeyNavigation.tab: labelWithButton3
clickedFunction: function() {
var fileName = ""
if (GC.isMobile()) {
fileName = "AmneziaVPN-service.log"
} else {
fileName = SystemController.getFileName(qsTr("Save"),
qsTr("Logs files (*.log)"),
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN-service",
true,
".log")
}
if (fileName !== "") {
PageController.showBusyIndicator(true)
SettingsController.exportServiceLogsFile(fileName)
PageController.showBusyIndicator(false)
PageController.showNotificationMessage(qsTr("Logs file saved"))
}
}
}
DividerType {
visible: !GC.isMobile()
} }
} }
} }

View file

@ -100,8 +100,6 @@ PageType {
text: qsTr("Check the server for previously installed Amnezia services") text: qsTr("Check the server for previously installed Amnezia services")
descriptionText: qsTr("Add them to the application if they were not displayed") descriptionText: qsTr("Add them to the application if they were not displayed")
KeyNavigation.tab: labelWithButton2
clickedFunction: function() { clickedFunction: function() {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
InstallController.scanServerForInstalledContainers() InstallController.scanServerForInstalledContainers()
@ -121,8 +119,6 @@ PageType {
text: qsTr("Reboot server") text: qsTr("Reboot server")
textColor: AmneziaStyle.color.vibrantRed textColor: AmneziaStyle.color.vibrantRed
KeyNavigation.tab: labelWithButton3
clickedFunction: function() { clickedFunction: function() {
var headerText = qsTr("Do you want to reboot the server?") var headerText = qsTr("Do you want to reboot the server?")
var descriptionText = qsTr("The reboot process may take approximately 30 seconds. Are you sure you wish to proceed?") var descriptionText = qsTr("The reboot process may take approximately 30 seconds. Are you sure you wish to proceed?")
@ -162,16 +158,6 @@ PageType {
text: qsTr("Remove server from application") text: qsTr("Remove server from application")
textColor: AmneziaStyle.color.vibrantRed textColor: AmneziaStyle.color.vibrantRed
Keys.onTabPressed: {
if (content.isServerWithWriteAccess) {
labelWithButton4.forceActiveFocus()
} else {
labelWithButton5.visible ?
labelWithButton5.forceActiveFocus() :
lastItemTabClickedSignal()
}
}
clickedFunction: function() { clickedFunction: function() {
var headerText = qsTr("Do you want to remove the server from application?") var headerText = qsTr("Do you want to remove the server from application?")
var descriptionText = qsTr("All installed AmneziaVPN services will still remain on the server.") var descriptionText = qsTr("All installed AmneziaVPN services will still remain on the server.")
@ -210,10 +196,6 @@ PageType {
text: qsTr("Clear server from Amnezia software") text: qsTr("Clear server from Amnezia software")
textColor: AmneziaStyle.color.vibrantRed textColor: AmneziaStyle.color.vibrantRed
Keys.onTabPressed: labelWithButton5.visible ?
labelWithButton5.forceActiveFocus() :
root.lastItemTabClickedSignal()
clickedFunction: function() { clickedFunction: function() {
var headerText = qsTr("Do you want to clear server from Amnezia software?") var headerText = qsTr("Do you want to clear server from Amnezia software?")
var descriptionText = qsTr("All users whom you shared a connection with will no longer be able to connect to it.") var descriptionText = qsTr("All users whom you shared a connection with will no longer be able to connect to it.")
@ -253,8 +235,6 @@ PageType {
text: qsTr("Reset API config") text: qsTr("Reset API config")
textColor: AmneziaStyle.color.vibrantRed textColor: AmneziaStyle.color.vibrantRed
Keys.onTabPressed: root.lastItemTabClickedSignal()
clickedFunction: function() { clickedFunction: function() {
var headerText = qsTr("Do you want to reset API config?") var headerText = qsTr("Do you want to reset API config?")
var descriptionText = "" var descriptionText = ""

View file

@ -19,21 +19,19 @@ import "../Components"
PageType { PageType {
id: root id: root
property int pageSettingsServerProtocols: 0 readonly property int pageSettingsServerProtocols: 0
property int pageSettingsServerServices: 1 readonly property int pageSettingsServerServices: 1
property int pageSettingsServerData: 2 readonly property int pageSettingsServerData: 2
property int pageSettingsApiServerInfo: 3 readonly property int pageSettingsApiServerInfo: 3
property int pageSettingsApiLanguageList: 4 readonly property int pageSettingsApiLanguageList: 4
property var processedServer property var processedServer
defaultActiveFocusItem: focusItem
Connections { Connections {
target: PageController target: PageController
function onGoToPageSettingsServerServices() { function onGoToPageSettingsServerServices() {
tabBar.currentIndex = root.pageSettingsServerServices tabBar.setCurrentIndex(root.pageSettingsServerServices)
} }
} }
@ -62,21 +60,17 @@ PageType {
} }
} }
Item {
id: focusItem
//KeyNavigation.tab: header
}
ColumnLayout { ColumnLayout {
objectName: "mainLayout"
anchors.fill: parent anchors.fill: parent
anchors.topMargin: 20
spacing: 4 spacing: 4
BackButtonType { BackButtonType {
id: backButton id: backButton
objectName: "backButton"
Layout.topMargin: 20
KeyNavigation.tab: headerContent.actionButton
backButtonFunction: function() { backButtonFunction: function() {
if (nestedStackView.currentIndex === root.pageSettingsApiServerInfo && if (nestedStackView.currentIndex === root.pageSettingsApiServerInfo &&
@ -90,9 +84,12 @@ PageType {
HeaderType { HeaderType {
id: headerContent id: headerContent
objectName: "headerContent"
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.bottomMargin: 10
actionButtonImage: nestedStackView.currentIndex === root.pageSettingsApiLanguageList ? "qrc:/images/controls/settings.svg" actionButtonImage: nestedStackView.currentIndex === root.pageSettingsApiLanguageList ? "qrc:/images/controls/settings.svg"
: "qrc:/images/controls/edit-3.svg" : "qrc:/images/controls/edit-3.svg"
@ -114,32 +111,25 @@ PageType {
} }
} }
KeyNavigation.tab: tabBar
actionButtonFunction: function() { actionButtonFunction: function() {
if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) { if (nestedStackView.currentIndex === root.pageSettingsApiLanguageList) {
nestedStackView.currentIndex = root.pageSettingsApiServerInfo nestedStackView.currentIndex = root.pageSettingsApiServerInfo
} else { } else {
serverNameEditDrawer.open() serverNameEditDrawer.openTriggered()
} }
} }
} }
DrawerType2 { DrawerType2 {
id: serverNameEditDrawer id: serverNameEditDrawer
objectName: "serverNameEditDrawer"
parent: root parent: root
anchors.fill: parent anchors.fill: parent
expandedHeight: root.height * 0.35 expandedHeight: root.height * 0.35
onClosed: { expandedStateContent: ColumnLayout {
if (!GC.isMobile()) {
headerContent.actionButton.forceActiveFocus()
}
}
expandedContent: ColumnLayout {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
@ -147,19 +137,6 @@ PageType {
anchors.leftMargin: 16 anchors.leftMargin: 16
anchors.rightMargin: 16 anchors.rightMargin: 16
Connections {
target: serverNameEditDrawer
enabled: !GC.isMobile()
function onOpened() {
serverName.textField.forceActiveFocus()
}
}
Item {
id: focusItem1
KeyNavigation.tab: serverName.textField
}
TextFieldWithHeaderType { TextFieldWithHeaderType {
id: serverName id: serverName
@ -168,8 +145,6 @@ PageType {
textFieldText: root.processedServer.name textFieldText: root.processedServer.name
textField.maximumLength: 30 textField.maximumLength: 30
checkEmptyText: true checkEmptyText: true
KeyNavigation.tab: saveButton
} }
BasicButtonType { BasicButtonType {
@ -178,7 +153,6 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Save") text: qsTr("Save")
KeyNavigation.tab: focusItem1
clickedFunc: function() { clickedFunc: function() {
if (serverName.textFieldText === "") { if (serverName.textFieldText === "") {
@ -188,7 +162,7 @@ PageType {
if (serverName.textFieldText !== root.processedServer.name) { if (serverName.textFieldText !== root.processedServer.name) {
ServersModel.setProcessedServerData("name", serverName.textFieldText); ServersModel.setProcessedServerData("name", serverName.textFieldText);
} }
serverNameEditDrawer.close() serverNameEditDrawer.closeTriggered()
} }
} }
} }
@ -209,35 +183,27 @@ PageType {
visible: !ServersModel.getProcessedServerData("isServerFromGatewayApi") visible: !ServersModel.getProcessedServerData("isServerFromGatewayApi")
activeFocusOnTab: true
onFocusChanged: {
if (activeFocus) {
protocolsTab.forceActiveFocus()
}
}
TabButtonType { TabButtonType {
id: protocolsTab id: protocolsTab
visible: protocolsPage.installedProtocolsCount visible: protocolsPage.installedProtocolsCount
width: protocolsPage.installedProtocolsCount ? undefined : 0 width: protocolsPage.installedProtocolsCount ? undefined : 0
isSelected: tabBar.currentIndex === root.pageSettingsServerProtocols isSelected: TabBar.tabBar.currentIndex === root.pageSettingsServerProtocols
text: qsTr("Protocols") text: qsTr("Protocols")
KeyNavigation.tab: servicesTab Keys.onReturnPressed: TabBar.tabBar.setCurrentIndex(root.pageSettingsServerProtocols)
Keys.onReturnPressed: tabBar.currentIndex = root.pageSettingsServerProtocols Keys.onEnterPressed: TabBar.tabBar.setCurrentIndex(root.pageSettingsServerProtocols)
Keys.onEnterPressed: tabBar.currentIndex = root.pageSettingsServerProtocols
} }
TabButtonType { TabButtonType {
id: servicesTab id: servicesTab
visible: servicesPage.installedServicesCount visible: servicesPage.installedServicesCount
width: servicesPage.installedServicesCount ? undefined : 0 width: servicesPage.installedServicesCount ? undefined : 0
isSelected: tabBar.currentIndex === root.pageSettingsServerServices isSelected: TabBar.tabBar.currentIndex === root.pageSettingsServerServices
text: qsTr("Services") text: qsTr("Services")
KeyNavigation.tab: dataTab Keys.onReturnPressed: TabBar.tabBar.setCurrentIndex(root.pageSettingsServerServices)
Keys.onReturnPressed: tabBar.currentIndex = root.pageSettingsServerServices Keys.onEnterPressed: TabBar.tabBar.setCurrentIndex(root.pageSettingsServerServices)
Keys.onEnterPressed: tabBar.currentIndex = root.pageSettingsServerServices
} }
TabButtonType { TabButtonType {
@ -245,22 +211,14 @@ PageType {
isSelected: tabBar.currentIndex === root.pageSettingsServerData isSelected: tabBar.currentIndex === root.pageSettingsServerData
text: qsTr("Management") text: qsTr("Management")
Keys.onReturnPressed: tabBar.currentIndex = root.pageSettingsServerData Keys.onReturnPressed: TabBar.tabBar.setCurrentIndex(root.pageSettingsServerData)
Keys.onEnterPressed: tabBar.currentIndex = root.pageSettingsServerData Keys.onEnterPressed: TabBar.tabBar.setCurrentIndex(root.pageSettingsServerData)
Keys.onTabPressed: function() {
if (nestedStackView.currentIndex === root.pageSettingsServerProtocols) {
return protocolsPage
} else if (nestedStackView.currentIndex === root.pageSettingsServerProtocols) {
return servicesPage
} else {
return dataPage
}
}
} }
} }
StackLayout { StackLayout {
id: nestedStackView id: nestedStackView
Layout.fillWidth: true Layout.fillWidth: true
currentIndex: ServersModel.getProcessedServerData("isServerFromGatewayApi") ? currentIndex: ServersModel.getProcessedServerData("isServerFromGatewayApi") ?
@ -270,38 +228,27 @@ PageType {
PageSettingsServerProtocols { PageSettingsServerProtocols {
id: protocolsPage id: protocolsPage
stackView: root.stackView stackView: root.stackView
onLastItemTabClickedSignal: lastItemTabClicked(focusItem)
} }
PageSettingsServerServices { PageSettingsServerServices {
id: servicesPage id: servicesPage
stackView: root.stackView stackView: root.stackView
onLastItemTabClickedSignal: lastItemTabClicked(focusItem)
} }
PageSettingsServerData { PageSettingsServerData {
id: dataPage id: dataPage
stackView: root.stackView stackView: root.stackView
onLastItemTabClickedSignal: lastItemTabClicked(focusItem)
} }
PageSettingsApiServerInfo { PageSettingsApiServerInfo {
id: apiInfoPage id: apiInfoPage
stackView: root.stackView stackView: root.stackView
// onLastItemTabClickedSignal: lastItemTabClicked(focusItem)
} }
PageSettingsApiLanguageList { PageSettingsApiLanguageList {
id: apiLanguageListPage id: apiLanguageListPage
stackView: root.stackView stackView: root.stackView
// onLastItemTabClickedSignal: lastItemTabClicked(focusItem)
} }
} }
} }
} }

View file

@ -21,13 +21,6 @@ PageType {
property bool isClearCacheVisible: ServersModel.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ContainersModel.getProcessedContainerIndex()) property bool isClearCacheVisible: ServersModel.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ContainersModel.getProcessedContainerIndex())
defaultActiveFocusItem: focusItem
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: header id: header
@ -39,7 +32,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: protocols
} }
HeaderType { HeaderType {
@ -57,30 +49,36 @@ PageType {
height: protocols.contentItem.height height: protocols.contentItem.height
clip: true clip: true
interactive: true interactive: true
model: ProtocolsModel
property int currentFocusIndex: 0 property bool isFocusable: true
activeFocusOnTab: true
onActiveFocusChanged: {
if (activeFocus) {
this.currentFocusIndex = 0
protocols.itemAtIndex(currentFocusIndex).focusItem.forceActiveFocus()
}
}
Keys.onTabPressed: { Keys.onTabPressed: {
if (currentFocusIndex < this.count - 1) { FocusController.nextKeyTabItem()
currentFocusIndex += 1
protocols.itemAtIndex(currentFocusIndex).focusItem.forceActiveFocus()
} else {
clearCacheButton.forceActiveFocus()
}
} }
delegate: Item { Keys.onBacktabPressed: {
property var focusItem: clientSettings.rightButton FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
model: ProtocolsModel
delegate: Item {
implicitWidth: protocols.width implicitWidth: protocols.width
implicitHeight: delegateContent.implicitHeight implicitHeight: delegateContent.implicitHeight
@ -160,109 +158,112 @@ PageType {
} }
} }
} }
}
LabelWithButtonType { footer: ColumnLayout {
id: clearCacheButton width: header.width
Layout.fillWidth: true LabelWithButtonType {
id: clearCacheButton
visible: root.isClearCacheVisible Layout.fillWidth: true
KeyNavigation.tab: removeButton
text: qsTr("Clear profile") visible: root.isClearCacheVisible
clickedFunction: function() { text: qsTr("Clear profile")
var headerText = qsTr("Clear %1 profile?").arg(ContainersModel.getProcessedContainerName())
var descriptionText = qsTr("The connection configuration will be deleted for this device only")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() { clickedFunction: function() {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { var headerText = qsTr("Clear %1 profile?").arg(ContainersModel.getProcessedContainerName())
var message = qsTr("Unable to clear %1 profile while there is an active connection").arg(ContainersModel.getProcessedContainerName()) var descriptionText = qsTr("The connection configuration will be deleted for this device only")
PageController.showNotificationMessage(message) var yesButtonText = qsTr("Continue")
return var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
var message = qsTr("Unable to clear %1 profile while there is an active connection").arg(ContainersModel.getProcessedContainerName())
PageController.showNotificationMessage(message)
return
}
PageController.showBusyIndicator(true)
InstallController.clearCachedProfile()
PageController.showBusyIndicator(false)
}
var noButtonFunction = function() {
// if (!GC.isMobile()) {
// focusItem.forceActiveFocus()
// }
}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
} }
PageController.showBusyIndicator(true) MouseArea {
InstallController.clearCachedProfile() anchors.fill: clearCacheButton
PageController.showBusyIndicator(false) cursorShape: Qt.PointingHandCursor
} enabled: false
var noButtonFunction = function() {
if (!GC.isMobile()) {
focusItem.forceActiveFocus()
} }
} }
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) DividerType {
} Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
MouseArea { visible: root.isClearCacheVisible
anchors.fill: clearCacheButton
cursorShape: Qt.PointingHandCursor
enabled: false
}
}
DividerType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: root.isClearCacheVisible
}
LabelWithButtonType {
id: removeButton
Layout.fillWidth: true
visible: ServersModel.isProcessedServerHasWriteAccess()
Keys.onTabPressed: lastItemTabClicked(focusItem)
text: qsTr("Remove ")
textColor: AmneziaStyle.color.vibrantRed
clickedFunction: function() {
var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName())
var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected
&& ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
PageController.showNotificationMessage(qsTr("Cannot remove active container"))
} else
{
PageController.goToPage(PageEnum.PageDeinstalling)
InstallController.removeProcessedContainer()
}
} }
var noButtonFunction = function() {
if (!GC.isMobile()) { LabelWithButtonType {
focusItem.forceActiveFocus() id: removeButton
Layout.fillWidth: true
visible: ServersModel.isProcessedServerHasWriteAccess()
text: qsTr("Remove ")
textColor: AmneziaStyle.color.vibrantRed
clickedFunction: function() {
var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName())
var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected
&& ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
PageController.showNotificationMessage(qsTr("Cannot remove active container"))
} else
{
PageController.goToPage(PageEnum.PageDeinstalling)
InstallController.removeProcessedContainer()
}
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
focusItem.forceActiveFocus()
}
}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
MouseArea {
anchors.fill: removeButton
cursorShape: Qt.PointingHandCursor
enabled: false
} }
} }
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) DividerType {
} Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
MouseArea { visible: ServersModel.isProcessedServerHasWriteAccess()
anchors.fill: removeButton }
cursorShape: Qt.PointingHandCursor
enabled: false
} }
} }
DividerType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: ServersModel.isProcessedServerHasWriteAccess()
}
} }
} }

View file

@ -21,53 +21,45 @@ PageType {
property var installedProtocolsCount property var installedProtocolsCount
onFocusChanged: settingsContainersListView.forceActiveFocus() function resetView() {
signal lastItemTabClickedSignal() settingsContainersListView.positionViewAtBeginning()
}
FlickableType { SettingsContainersListView {
id: fl id: settingsContainersListView
anchors.top: parent.top
anchors.bottom: parent.bottom
contentHeight: content.implicitHeight
Column { anchors.fill: parent
id: content
anchors.top: parent.top Connections {
anchors.left: parent.left target: ServersModel
anchors.right: parent.right
SettingsContainersListView { function onProcessedServerIndexChanged() {
id: settingsContainersListView settingsContainersListView.updateContainersModelFilters()
Connections {
target: ServersModel
function onProcessedServerIndexChanged() {
settingsContainersListView.updateContainersModelFilters()
}
}
function updateContainersModelFilters() {
if (ServersModel.isProcessedServerHasWriteAccess()) {
proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters()
} else {
proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters()
}
root.installedProtocolsCount = proxyContainersModel.count
}
model: SortFilterProxyModel {
id: proxyContainersModel
sourceModel: ContainersModel
sorters: [
RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder },
RoleSorter { roleName: "installPageOrder"; sortOrder: Qt.AscendingOrder }
]
}
Component.onCompleted: updateContainersModelFilters()
} }
} }
function updateContainersModelFilters() {
if (ServersModel.isProcessedServerHasWriteAccess()) {
proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters()
} else {
proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters()
}
root.installedProtocolsCount = proxyContainersModel.count
}
model: SortFilterProxyModel {
id: proxyContainersModel
sourceModel: ContainersModel
sorters: [
RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder },
RoleSorter { roleName: "installPageOrder"; sortOrder: Qt.AscendingOrder }
]
}
Component.onCompleted: {
settingsContainersListView.isFocusable = true
settingsContainersListView.interactive = true
updateContainersModelFilters()
}
} }
} }

View file

@ -21,52 +21,40 @@ PageType {
property var installedServicesCount property var installedServicesCount
onFocusChanged: settingsContainersListView.forceActiveFocus() SettingsContainersListView {
signal lastItemTabClickedSignal() id: settingsContainersListView
FlickableType { anchors.fill: parent
id: fl
anchors.top: parent.top
anchors.bottom: parent.bottom
contentHeight: content.implicitHeight
Column { Connections {
id: content target: ServersModel
anchors.top: parent.top function onProcessedServerIndexChanged() {
anchors.left: parent.left settingsContainersListView.updateContainersModelFilters()
anchors.right: parent.right
SettingsContainersListView {
id: settingsContainersListView
Connections {
target: ServersModel
function onProcessedServerIndexChanged() {
settingsContainersListView.updateContainersModelFilters()
}
}
function updateContainersModelFilters() {
if (ServersModel.isProcessedServerHasWriteAccess()) {
proxyContainersModel.filters = ContainersModelFilters.getWriteAccessServicesListFilters()
} else {
proxyContainersModel.filters = ContainersModelFilters.getReadAccessServicesListFilters()
}
root.installedServicesCount = proxyContainersModel.count
}
model: SortFilterProxyModel {
id: proxyContainersModel
sourceModel: ContainersModel
sorters: [
RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder }
]
}
Component.onCompleted: updateContainersModelFilters()
} }
} }
function updateContainersModelFilters() {
if (ServersModel.isProcessedServerHasWriteAccess()) {
proxyContainersModel.filters = ContainersModelFilters.getWriteAccessServicesListFilters()
} else {
proxyContainersModel.filters = ContainersModelFilters.getReadAccessServicesListFilters()
}
root.installedServicesCount = proxyContainersModel.count
}
model: SortFilterProxyModel {
id: proxyContainersModel
sourceModel: ContainersModel
sorters: [
RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder }
]
}
Component.onCompleted: {
settingsContainersListView.isFocusable = true
settingsContainersListView.interactive = true
updateContainersModelFilters()
}
} }
} }

View file

@ -18,13 +18,6 @@ import "../Components"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout { ColumnLayout {
id: header id: header
@ -36,7 +29,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: servers
} }
HeaderType { HeaderType {
@ -48,95 +40,64 @@ PageType {
} }
} }
FlickableType { ListView {
id: fl id: servers
objectName: "servers"
width: parent.width
anchors.top: header.bottom anchors.top: header.bottom
anchors.topMargin: 16 anchors.topMargin: 16
contentHeight: col.implicitHeight anchors.left: parent.left
anchors.right: parent.right
Column { height: 500
id: col
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
ListView { property bool isFocusable: true
id: servers
width: parent.width
height: servers.contentItem.height
model: ServersModel model: ServersModel
clip: true clip: true
interactive: false reuseItems: true
activeFocusOnTab: true delegate: Item {
focus: true implicitWidth: servers.width
Keys.onTabPressed: { implicitHeight: delegateContent.implicitHeight
if (currentIndex < servers.count - 1) {
servers.incrementCurrentIndex()
} else {
servers.currentIndex = 0
focusItem.forceActiveFocus()
root.lastItemTabClicked()
}
fl.ensureVisible(this.currentItem) ColumnLayout {
} id: delegateContent
onVisibleChanged: { anchors.top: parent.top
if (visible) { anchors.left: parent.left
currentIndex = 0 anchors.right: parent.right
}
}
delegate: Item { LabelWithButtonType {
implicitWidth: servers.width id: server
implicitHeight: delegateContent.implicitHeight Layout.fillWidth: true
onFocusChanged: { text: name
if (focus) {
server.rightButton.forceActiveFocus()
}
}
ColumnLayout { descriptionText: {
id: delegateContent var servicesNameString = ""
var servicesName = ServersModel.getAllInstalledServicesName(index)
anchors.top: parent.top for (var i = 0; i < servicesName.length; i++) {
anchors.left: parent.left servicesNameString += servicesName[i] + " · "
anchors.right: parent.right
LabelWithButtonType {
id: server
Layout.fillWidth: true
text: name
parentFlickable: fl
descriptionText: {
var servicesNameString = ""
var servicesName = ServersModel.getAllInstalledServicesName(index)
for (var i = 0; i < servicesName.length; i++) {
servicesNameString += servicesName[i] + " · "
}
if (ServersModel.isServerFromApi(index)) {
return servicesNameString + serverDescription
} else {
return servicesNameString + hostName
}
}
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
ServersModel.processedIndex = index
PageController.goToPage(PageEnum.PageSettingsServerInfo)
}
} }
DividerType {} if (ServersModel.isServerFromApi(index)) {
return servicesNameString + serverDescription
} else {
return servicesNameString + hostName
}
}
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
ServersModel.processedIndex = index
PageController.goToPage(PageEnum.PageSettingsServerInfo)
} }
} }
DividerType {}
} }
} }
} }

View file

@ -23,13 +23,6 @@ PageType {
property var isServerFromTelegramApi: ServersModel.getDefaultServerData("isServerFromTelegramApi") property var isServerFromTelegramApi: ServersModel.getDefaultServerData("isServerFromTelegramApi")
defaultActiveFocusItem: searchField.textField
Item {
id: focusItem
KeyNavigation.tab: backButton
}
property bool pageEnabled property bool pageEnabled
Component.onCompleted: { Component.onCompleted: {
@ -99,7 +92,6 @@ PageType {
BackButtonType { BackButtonType {
id: backButton id: backButton
KeyNavigation.tab: switcher
} }
RowLayout { RowLayout {
@ -129,8 +121,6 @@ PageType {
onToggled: { onToggledFunc() } onToggled: { onToggledFunc() }
Keys.onEnterPressed: { onToggledFunc() } Keys.onEnterPressed: { onToggledFunc() }
Keys.onReturnPressed: { onToggledFunc() } Keys.onReturnPressed: { onToggledFunc() }
KeyNavigation.tab: selector
} }
} }
@ -154,18 +144,17 @@ PageType {
model: root.routeModesModel model: root.routeModesModel
currentIndex: getRouteModesModelIndex()
clickedFunction: function() { clickedFunction: function() {
selector.text = selectedText selector.text = selectedText
selector.close() selector.closeTriggered()
if (SitesModel.routeMode !== root.routeModesModel[currentIndex].type) { if (SitesModel.routeMode !== root.routeModesModel[selectedIndex].type) {
SitesModel.routeMode = root.routeModesModel[currentIndex].type SitesModel.routeMode = root.routeModesModel[selectedIndex].type
} }
} }
Component.onCompleted: { Component.onCompleted: {
if (root.routeModesModel[currentIndex].type === SitesModel.routeMode) { if (root.routeModesModel[selectedIndex].type === SitesModel.routeMode) {
selector.text = selectedText selector.text = selectedText
} else { } else {
selector.text = root.routeModesModel[0].name selector.text = root.routeModesModel[0].name
@ -175,128 +164,89 @@ PageType {
Connections { Connections {
target: SitesModel target: SitesModel
function onRouteModeChanged() { function onRouteModeChanged() {
currentIndex = getRouteModesModelIndex() selectedIndex = getRouteModesModelIndex()
} }
} }
} }
KeyNavigation.tab: {
return sites.count > 0 ?
sites :
searchField.textField
}
} }
} }
FlickableType { ListView {
id: fl id: listView
anchors.top: header.bottom anchors.top: header.bottom
anchors.topMargin: 16 anchors.topMargin: 16
contentHeight: col.implicitHeight + addSiteButton.implicitHeight + addSiteButton.anchors.bottomMargin + addSiteButton.anchors.topMargin anchors.bottom: addSiteButton.top
width: parent.width
enabled: root.pageEnabled enabled: root.pageEnabled
Column { property bool isFocusable: true
id: col
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
ListView { model: SortFilterProxyModel {
id: sites id: proxySitesModel
width: parent.width sourceModel: SitesModel
height: sites.contentItem.height filters: [
AnyOf {
model: SortFilterProxyModel { RegExpFilter {
id: proxySitesModel roleName: "url"
sourceModel: SitesModel pattern: ".*" + searchField.textField.text + ".*"
filters: [ caseSensitivity: Qt.CaseInsensitive
AnyOf { }
RegExpFilter { RegExpFilter {
roleName: "url" roleName: "ip"
pattern: ".*" + searchField.textField.text + ".*" pattern: ".*" + searchField.textField.text + ".*"
caseSensitivity: Qt.CaseInsensitive caseSensitivity: Qt.CaseInsensitive
}
RegExpFilter {
roleName: "ip"
pattern: ".*" + searchField.textField.text + ".*"
caseSensitivity: Qt.CaseInsensitive
}
}
]
}
clip: true
interactive: false
activeFocusOnTab: true
focus: true
Keys.onTabPressed: {
if (currentIndex < this.count - 1) {
this.incrementCurrentIndex()
} else {
currentIndex = 0
searchField.textField.forceActiveFocus()
} }
fl.ensureVisible(currentItem)
} }
]
}
delegate: Item { clip: true
implicitWidth: sites.width
implicitHeight: delegateContent.implicitHeight
onActiveFocusChanged: { reuseItems: true
if (activeFocus) {
delegate: ColumnLayout {
id: delegateContent
width: listView.width
LabelWithButtonType {
id: site
Layout.fillWidth: true
text: url
descriptionText: ip
rightImageSource: "qrc:/images/controls/trash.svg"
rightImageColor: AmneziaStyle.color.paleGray
clickedFunction: function() {
var headerText = qsTr("Remove ") + url + "?"
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
SitesController.removeSite(proxySitesModel.mapToSource(index))
if (!GC.isMobile()) {
site.rightButton.forceActiveFocus()
}
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
site.rightButton.forceActiveFocus() site.rightButton.forceActiveFocus()
} }
} }
ColumnLayout { showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
id: delegateContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
LabelWithButtonType {
id: site
Layout.fillWidth: true
text: url
descriptionText: ip
rightImageSource: "qrc:/images/controls/trash.svg"
rightImageColor: AmneziaStyle.color.paleGray
clickedFunction: function() {
var headerText = qsTr("Remove ") + url + "?"
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
SitesController.removeSite(proxySitesModel.mapToSource(index))
if (!GC.isMobile()) {
site.rightButton.forceActiveFocus()
}
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
site.rightButton.forceActiveFocus()
}
}
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
}
DividerType {}
}
} }
} }
DividerType {}
} }
} }
Rectangle { Rectangle {
anchors.fill: addSiteButton anchors.fill: addSiteButton
anchors.bottomMargin: -24 anchors.bottomMargin: -24
@ -325,7 +275,6 @@ PageType {
textFieldPlaceholderText: qsTr("website or IP") textFieldPlaceholderText: qsTr("website or IP")
buttonImageSource: "qrc:/images/controls/plus.svg" buttonImageSource: "qrc:/images/controls/plus.svg"
KeyNavigation.tab: GC.isMobile() ? focusItem : addSiteButtonImage
clickedFunc: function() { clickedFunc: function() {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
@ -344,13 +293,11 @@ PageType {
imageColor: AmneziaStyle.color.paleGray imageColor: AmneziaStyle.color.paleGray
onClicked: function () { onClicked: function () {
moreActionsDrawer.open() moreActionsDrawer.openTriggered()
} }
Keys.onReturnPressed: addSiteButtonImage.clicked() Keys.onReturnPressed: addSiteButtonImage.clicked()
Keys.onEnterPressed: addSiteButtonImage.clicked() Keys.onEnterPressed: addSiteButtonImage.clicked()
Keys.onTabPressed: lastItemTabClicked(focusItem)
} }
} }
@ -360,38 +307,13 @@ PageType {
anchors.fill: parent anchors.fill: parent
expandedHeight: parent.height * 0.4375 expandedHeight: parent.height * 0.4375
onClosed: { expandedStateContent: ColumnLayout {
if (root.defaultActiveFocusItem && !GC.isMobile()) {
root.defaultActiveFocusItem.forceActiveFocus()
}
}
expandedContent: ColumnLayout {
id: moreActionsDrawerContent id: moreActionsDrawerContent
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
Connections {
target: moreActionsDrawer
function onOpened() {
focusItem1.forceActiveFocus()
}
function onActiveFocusChanged() {
if (!GC.isMobile()) {
focusItem1.forceActiveFocus()
}
}
}
Item {
id: focusItem1
KeyNavigation.tab: importSitesButton.rightButton
}
Header2Type { Header2Type {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 16 Layout.margins: 16
@ -407,21 +329,18 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
importSitesDrawer.open() importSitesDrawer.openTriggered()
} }
KeyNavigation.tab: exportSitesButton
} }
DividerType {} DividerType {}
LabelWithButtonType { LabelWithButtonType {
id: exportSitesButton id: exportSitesButton
enabled: !SettingsController.isOnTv()
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Save site list") text: qsTr("Save site list")
KeyNavigation.tab: focusItem1
clickedFunction: function() { clickedFunction: function() {
var fileName = "" var fileName = ""
if (GC.isMobile()) { if (GC.isMobile()) {
@ -436,13 +355,15 @@ PageType {
if (fileName !== "") { if (fileName !== "") {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
SitesController.exportSites(fileName) SitesController.exportSites(fileName)
moreActionsDrawer.close() moreActionsDrawer.closeTriggered()
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
} }
} }
} }
DividerType {} DividerType {
enabled: !SettingsController.isOnTv()
}
} }
} }
@ -452,28 +373,9 @@ PageType {
anchors.fill: parent anchors.fill: parent
expandedHeight: parent.height * 0.4375 expandedHeight: parent.height * 0.4375
onClosed: { expandedStateContent: Item {
if (!GC.isMobile()) {
moreActionsDrawer.forceActiveFocus()
}
}
expandedContent: Item {
implicitHeight: importSitesDrawer.expandedHeight implicitHeight: importSitesDrawer.expandedHeight
Connections {
target: importSitesDrawer
enabled: !GC.isMobile()
function onOpened() {
focusItem2.forceActiveFocus()
}
}
Item {
id: focusItem2
KeyNavigation.tab: importSitesDrawerBackButton
}
BackButtonType { BackButtonType {
id: importSitesDrawerBackButton id: importSitesDrawerBackButton
@ -482,10 +384,8 @@ PageType {
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 16 anchors.topMargin: 16
KeyNavigation.tab: importSitesButton2
backButtonFunction: function() { backButtonFunction: function() {
importSitesDrawer.close() importSitesDrawer.closeTriggered()
} }
} }
@ -516,7 +416,6 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Replace site list") text: qsTr("Replace site list")
KeyNavigation.tab: importSitesButton3
clickedFunction: function() { clickedFunction: function() {
var fileName = SystemController.getFileName(qsTr("Open sites file"), var fileName = SystemController.getFileName(qsTr("Open sites file"),
@ -533,7 +432,6 @@ PageType {
id: importSitesButton3 id: importSitesButton3
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Add imported sites to existing ones") text: qsTr("Add imported sites to existing ones")
KeyNavigation.tab: focusItem2
clickedFunction: function() { clickedFunction: function() {
var fileName = SystemController.getFileName(qsTr("Open sites file"), var fileName = SystemController.getFileName(qsTr("Open sites file"),
@ -548,8 +446,8 @@ PageType {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
SitesController.importSites(fileName, replaceExistingSites) SitesController.importSites(fileName, replaceExistingSites)
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
importSitesDrawer.close() importSitesDrawer.closeTriggered()
moreActionsDrawer.close() moreActionsDrawer.closeTriggered()
} }
DividerType {} DividerType {}

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,15 +30,9 @@ PageType {
spacing: 0 spacing: 0
Item {
id: focusItem
KeyNavigation.tab: backButton
}
BackButtonType { BackButtonType {
id: backButton id: backButton
Layout.topMargin: 20 Layout.topMargin: 20
// KeyNavigation.tab: fileButton.rightButton
} }
HeaderType { HeaderType {

View file

@ -14,8 +14,6 @@ import "../Config"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
ColumnLayout { ColumnLayout {
id: header id: header
@ -25,15 +23,9 @@ PageType {
spacing: 0 spacing: 0
Item {
id: focusItem
KeyNavigation.tab: backButton
}
BackButtonType { BackButtonType {
id: backButton id: backButton
Layout.topMargin: 20 Layout.topMargin: 20
// KeyNavigation.tab: fileButton.rightButton
} }
HeaderType { HeaderType {
@ -50,6 +42,7 @@ PageType {
ListView { ListView {
id: servicesListView id: servicesListView
anchors.top: header.bottom anchors.top: header.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
@ -57,16 +50,21 @@ PageType {
anchors.topMargin: 16 anchors.topMargin: 16
spacing: 0 spacing: 0
currentIndex: 1 property bool isFocusable: true
clip: true clip: true
reuseItems: true
model: ApiServicesModel model: ApiServicesModel
ScrollBar.vertical: ScrollBar {} ScrollBar.vertical: ScrollBarType {}
delegate: Item { delegate: Item {
implicitWidth: servicesListView.width implicitWidth: servicesListView.width
implicitHeight: delegateContent.implicitHeight implicitHeight: delegateContent.implicitHeight
enabled: isServiceAvailable
ColumnLayout { ColumnLayout {
id: delegateContent id: delegateContent
@ -86,14 +84,15 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
enabled: isServiceAvailable
onClicked: { onClicked: {
if (isServiceAvailable) { if (isServiceAvailable) {
ApiServicesModel.setServiceIndex(index) ApiServicesModel.setServiceIndex(index)
PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo) PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
} }
} }
Keys.onEnterPressed: clicked()
Keys.onReturnPressed: clicked()
} }
} }
} }

View file

@ -25,31 +25,29 @@ PageType {
} }
} }
defaultActiveFocusItem: focusItem ListView {
id: listView
FlickableType { anchors.fill: parent
id: fl
anchors.top: parent.top
anchors.bottom: parent.bottom
contentHeight: content.height
ColumnLayout { property bool isFocusable: true
id: content
anchors.top: parent.top ScrollBar.vertical: ScrollBarType {}
anchors.left: parent.left
anchors.right: parent.right
spacing: 0 model: variants
Item { clip: true
id: focusItem
KeyNavigation.tab: textKey.textField reuseItems: true
}
header: ColumnLayout {
width: listView.width
HeaderType { HeaderType {
property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible() id: moreButton
property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible()
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 24
Layout.rightMargin: 16 Layout.rightMargin: 16
@ -59,7 +57,7 @@ PageType {
actionButtonImage: isVisible ? "qrc:/images/controls/more-vertical.svg" : "" actionButtonImage: isVisible ? "qrc:/images/controls/more-vertical.svg" : ""
actionButtonFunction: function() { actionButtonFunction: function() {
moreActionsDrawer.open() moreActionsDrawer.openTriggered()
} }
DrawerType2 { DrawerType2 {
@ -70,7 +68,7 @@ PageType {
anchors.fill: parent anchors.fill: parent
expandedHeight: root.height * 0.5 expandedHeight: root.height * 0.5
expandedContent: ColumnLayout { expandedStateContent: ColumnLayout {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
@ -130,6 +128,8 @@ PageType {
} }
ParagraphTextType { ParagraphTextType {
objectName: "insertKeyLabel"
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 32 Layout.topMargin: 32
Layout.rightMargin: 16 Layout.rightMargin: 16
@ -153,8 +153,6 @@ PageType {
textField.text = "" textField.text = ""
textField.paste() textField.paste()
} }
KeyNavigation.tab: continueButton
} }
BasicButtonType { BasicButtonType {
@ -168,7 +166,6 @@ PageType {
visible: textKey.textFieldText !== "" visible: textKey.textFieldText !== ""
text: qsTr("Continue") text: qsTr("Continue")
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() { clickedFunc: function() {
if (ImportController.extractConfigFromData(textKey.textFieldText)) { if (ImportController.extractConfigFromData(textKey.textFieldText)) {
@ -187,143 +184,129 @@ PageType {
color: AmneziaStyle.color.charcoalGray color: AmneziaStyle.color.charcoalGray
text: qsTr("Other connection options") text: qsTr("Other connection options")
} }
}
delegate: ColumnLayout {
width: listView.width
CardWithIconsType { CardWithIconsType {
id: apiInstalling
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.bottomMargin: 16 Layout.bottomMargin: 16
headerText: qsTr("VPN by Amnezia") visible: isVisible
bodyText: qsTr("Connect to classic paid and free VPN services from Amnezia")
headerText: title
bodyText: description
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
leftImageSource: "qrc:/images/controls/amnezia.svg" leftImageSource: imageSource
onClicked: function() { onClicked: { handler() }
PageController.showBusyIndicator(true)
var result = InstallController.fillAvailableServices()
PageController.showBusyIndicator(false)
if (result) {
PageController.goToPage(PageEnum.PageSetupWizardApiServicesList)
}
}
} }
}
}
CardWithIconsType { property list<QtObject> variants: [
id: manualInstalling amneziaVpn,
selfHostVpn,
backupRestore,
fileOpen,
qrScan,
siteLink
]
QtObject {
id: amneziaVpn
Layout.fillWidth: true property string title: qsTr("VPN by Amnezia")
Layout.rightMargin: 16 property string description: qsTr("Connect to classic paid and free VPN services from Amnezia")
Layout.leftMargin: 16 property string imageSource: "qrc:/images/controls/amnezia.svg"
Layout.bottomMargin: 16 property bool isVisible: true
property var handler: function() {
headerText: qsTr("Self-hosted VPN") PageController.showBusyIndicator(true)
bodyText: qsTr("Configure Amnezia VPN on your own server") var result = InstallController.fillAvailableServices()
PageController.showBusyIndicator(false)
rightImageSource: "qrc:/images/controls/chevron-right.svg" if (result) {
leftImageSource: "qrc:/images/controls/server.svg" PageController.goToPage(PageEnum.PageSetupWizardApiServicesList)
onClicked: {
PageController.goToPage(PageEnum.PageSetupWizardCredentials)
}
} }
}
}
CardWithIconsType { QtObject {
id: backupRestore id: selfHostVpn
Layout.fillWidth: true property string title: qsTr("Self-hosted VPN")
Layout.rightMargin: 16 property string description: qsTr("Configure Amnezia VPN on your own server")
Layout.leftMargin: 16 property string imageSource: "qrc:/images/controls/server.svg"
Layout.bottomMargin: 16 property bool isVisible: true
property var handler: function() {
PageController.goToPage(PageEnum.PageSetupWizardCredentials)
}
}
visible: PageController.isStartPageVisible() QtObject {
id: backupRestore
headerText: qsTr("Restore from backup") property string title: qsTr("Restore from backup")
property string description: qsTr("")
rightImageSource: "qrc:/images/controls/chevron-right.svg" property string imageSource: "qrc:/images/controls/archive-restore.svg"
leftImageSource: "qrc:/images/controls/archive-restore.svg" property bool isVisible: PageController.isStartPageVisible()
property var handler: function() {
onClicked: { var filePath = SystemController.getFileName(qsTr("Open backup file"),
var filePath = SystemController.getFileName(qsTr("Open backup file"), qsTr("Backup files (*.backup)"))
qsTr("Backup files (*.backup)")) if (filePath !== "") {
if (filePath !== "") { PageController.showBusyIndicator(true)
PageController.showBusyIndicator(true) SettingsController.restoreAppConfig(filePath)
SettingsController.restoreAppConfig(filePath) PageController.showBusyIndicator(false)
PageController.showBusyIndicator(false)
}
}
} }
}
}
CardWithIconsType { QtObject {
id: openFile id: fileOpen
Layout.fillWidth: true property string title: qsTr("File with connection settings")
Layout.rightMargin: 16 property string description: qsTr("")
Layout.leftMargin: 16 property string imageSource: "qrc:/images/controls/folder-search-2.svg"
Layout.bottomMargin: 16 property bool isVisible: true
property var handler: function() {
headerText: qsTr("File with connection settings") var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.json *.backup)" :
"Config files (*.vpn *.ovpn *.conf *.json)"
rightImageSource: "qrc:/images/controls/chevron-right.svg" var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter)
leftImageSource: "qrc:/images/controls/folder-search-2.svg" if (fileName !== "") {
if (ImportController.extractConfigFromFile(fileName)) {
onClicked: { PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.json *.backup)" :
"Config files (*.vpn *.ovpn *.conf *.json)"
var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter)
if (fileName !== "") {
if (ImportController.extractConfigFromFile(fileName)) {
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
}
}
}
}
CardWithIconsType {
id: scanQr
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 16
visible: SettingsController.isCameraPresent()
headerText: qsTr("QR code")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
leftImageSource: "qrc:/images/controls/scan-line.svg"
onClicked: {
ImportController.startDecodingQr()
if (Qt.platform.os === "ios") {
PageController.goToPage(PageEnum.PageSetupWizardQrReader)
}
}
}
CardWithIconsType {
id: siteLink
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 16
visible: PageController.isStartPageVisible()
headerText: qsTr("I have nothing")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
leftImageSource: "qrc:/images/controls/help-circle.svg"
onClicked: {
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())
} }
} }
} }
} }
QtObject {
id: qrScan
property string title: qsTr("QR code")
property string description: qsTr("")
property string imageSource: "qrc:/images/controls/scan-line.svg"
property bool isVisible: SettingsController.isCameraPresent()
property var handler: function() {
ImportController.startDecodingQr()
if (Qt.platform.os === "ios") {
PageController.goToPage(PageEnum.PageSetupWizardQrReader)
}
}
}
QtObject {
id: siteLink
property string title: qsTr("I have nothing")
property string description: qsTr("")
property string imageSource: "qrc:/images/controls/help-circle.svg"
property bool isVisible: PageController.isStartPageVisible()
property var handler: function() {
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())
}
}
} }

View file

@ -13,13 +13,6 @@ import "../Controls2/TextTypes"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: hostname.textField
Item {
id: focusItem
KeyNavigation.tab: backButton
}
BackButtonType { BackButtonType {
id: backButton id: backButton
@ -28,100 +21,133 @@ PageType {
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20
KeyNavigation.tab: hostname.textField onFocusChanged: {
if (this.activeFocus) {
listView.positionViewAtBeginning()
}
}
} }
FlickableType { ListView {
id: fl id: listView
anchors.top: backButton.bottom anchors.top: backButton.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
contentHeight: content.height anchors.right: parent.right
anchors.left: parent.left
ColumnLayout { property bool isFocusable: true
id: content
anchors.top: parent.top Keys.onTabPressed: {
anchors.left: parent.left FocusController.nextKeyTabItem()
anchors.right: parent.right }
anchors.rightMargin: 16
anchors.leftMargin: 16
spacing: 16 Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
ScrollBar.vertical: ScrollBarType {}
header: ColumnLayout {
width: listView.width
HeaderType { HeaderType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16
headerText: qsTr("Configure your server") headerText: qsTr("Configure your server")
} }
}
model: inputFields
spacing: 16
clip: true
reuseItems: true
delegate: ColumnLayout {
property alias textField: _textField.textField
width: listView.width
TextFieldWithHeaderType { TextFieldWithHeaderType {
id: hostname id: _textField
Layout.fillWidth: true Layout.fillWidth: true
headerText: qsTr("Server IP address [:port]") Layout.leftMargin: 16
textFieldPlaceholderText: qsTr("255.255.255.255:22") Layout.rightMargin: 16
textField.onFocusChanged: { property bool hidePassword: hideText
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
}
KeyNavigation.tab: username.textField headerText: title
} textField.echoMode: hideText ? TextInput.Password : TextInput.Normal
buttonImageSource: imageSource
textFieldPlaceholderText: placeholderText
textField.text: textFieldText
TextFieldWithHeaderType { rightButtonClickedOnEnter: true
id: username
Layout.fillWidth: true clickedFunc: function () {
headerText: qsTr("SSH Username") clickedHandler()
textFieldPlaceholderText: "root"
textField.onFocusChanged: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
}
KeyNavigation.tab: secretData.textField
}
TextFieldWithHeaderType {
id: secretData
property bool hidePassword: true
Layout.fillWidth: true
headerText: qsTr("Password or SSH private key")
textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal
buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg")
: ""
clickedFunc: function() {
hidePassword = !hidePassword
} }
textField.onFocusChanged: { textField.onFocusChanged: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '') var _currentIndex = listView.currentIndex
var _currentItem = listView.itemAtIndex(_currentIndex).children[0]
listView.model[_currentIndex].textFieldText = _currentItem.textFieldText.replace(/^\s+|\s+$/g, '')
} }
KeyNavigation.tab: continueButton textField.onTextChanged: {
var _currentIndex = listView.currentIndex
textFieldText = textField.text
if (_currentIndex === vars.secretDataIndex) {
buttonImageSource = textFieldText !== "" ? (hideText ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : ""
}
}
} }
}
footer: ColumnLayout {
width: listView.width
BasicButtonType { BasicButtonType {
id: continueButton id: continueButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 24 Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Continue") text: qsTr("Continue")
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() { clickedFunc: function() {
forceActiveFocus() if (!root.isCredentialsFilled()) {
if (!isCredentialsFilled()) {
return return
} }
InstallController.setShouldCreateServer(true) InstallController.setShouldCreateServer(true)
InstallController.setProcessedServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) var _hostname = listView.itemAtIndex(vars.hostnameIndex).children[0].textFieldText
var _username = listView.itemAtIndex(vars.usernameIndex).children[0].textFieldText
var _secretData = listView.itemAtIndex(vars.secretDataIndex).children[0].textFieldText
InstallController.setProcessedServerCredentials(_hostname, _username, _secretData)
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
var isConnectionOpened = InstallController.checkSshConnection() var isConnectionOpened = InstallController.checkSshConnection()
@ -136,7 +162,10 @@ PageType {
LabelTextType { LabelTextType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 12 Layout.topMargin: 24
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16
text: qsTr("All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties") text: qsTr("All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties")
} }
@ -145,6 +174,8 @@ PageType {
id: siteLink id: siteLink
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16 Layout.bottomMargin: 16
headerText: qsTr("How to run your VPN server") headerText: qsTr("How to run your VPN server")
@ -163,21 +194,78 @@ PageType {
function isCredentialsFilled() { function isCredentialsFilled() {
var hasEmptyField = false var hasEmptyField = false
if (hostname.textFieldText === "") { var _hostname = listView.itemAtIndex(vars.hostnameIndex).children[0]
hostname.errorText = qsTr("Ip address cannot be empty") if (_hostname.textFieldText === "") {
_hostname.errorText = qsTr("Ip address cannot be empty")
hasEmptyField = true hasEmptyField = true
} else if (!hostname.textField.acceptableInput) { } else if (!_hostname.textField.acceptableInput) {
hostname.errorText = qsTr("Enter the address in the format 255.255.255.255:88") _hostname.errorText = qsTr("Enter the address in the format 255.255.255.255:88")
} }
if (username.textFieldText === "") { var _username = listView.itemAtIndex(vars.usernameIndex).children[0]
username.errorText = qsTr("Login cannot be empty") if (_username.textFieldText === "") {
_username.errorText = qsTr("Login cannot be empty")
hasEmptyField = true hasEmptyField = true
} }
if (secretData.textFieldText === "") {
secretData.errorText = qsTr("Password/private key cannot be empty") var _secretData = listView.itemAtIndex(vars.secretDataIndex).children[0]
if (_secretData.textFieldText === "") {
_secretData.errorText = qsTr("Password/private key cannot be empty")
hasEmptyField = true hasEmptyField = true
} }
return !hasEmptyField return !hasEmptyField
} }
property list<QtObject> inputFields: [
hostname,
username,
secretData
]
QtObject {
id: hostname
property string title: qsTr("Server IP address [:port]")
readonly property string placeholderText: qsTr("255.255.255.255:22")
property string textFieldText: ""
property bool hideText: false
property string imageSource: ""
readonly property var clickedHandler: function() {
console.debug(">>> Server IP address text field was clicked!!!")
clicked()
}
}
QtObject {
id: username
property string title: qsTr("SSH Username")
readonly property string placeholderText: "root"
property string textFieldText: ""
property bool hideText: false
property string imageSource: ""
readonly property var clickedHandler: undefined
}
QtObject {
id: secretData
property string title: qsTr("Password or SSH private key")
readonly property string placeholderText: ""
property string textFieldText: ""
property bool hideText: true
property string imageSource: textFieldText !== "" ? (hideText ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : ""
readonly property var clickedHandler: function() {
hideText = !hideText
}
}
QtObject {
id: vars
readonly property int hostnameIndex: 0
readonly property int usernameIndex: 1
readonly property int secretDataIndex: 2
}
} }

View file

@ -17,7 +17,6 @@ PageType {
id: root id: root
property bool isEasySetup: true property bool isEasySetup: true
defaultActiveFocusItem: focusItem
SortFilterProxyModel { SortFilterProxyModel {
id: proxyContainersModel id: proxyContainersModel
@ -34,14 +33,6 @@ PageType {
} }
} }
Item {
id: focusItem
implicitWidth: 1
implicitHeight: 54
KeyNavigation.tab: backButton
}
BackButtonType { BackButtonType {
id: backButton id: backButton
@ -49,8 +40,6 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20
KeyNavigation.tab: continueButton
} }
FlickableType { FlickableType {
@ -98,6 +87,8 @@ PageType {
property int containerDefaultPort property int containerDefaultPort
property int containerDefaultTransportProto property int containerDefaultTransportProto
property bool isFocusable: true
delegate: Item { delegate: Item {
implicitWidth: containers.width implicitWidth: containers.width
implicitHeight: delegateContent.implicitHeight implicitHeight: delegateContent.implicitHeight
@ -163,7 +154,7 @@ PageType {
implicitWidth: parent.width implicitWidth: parent.width
text: qsTr("Continue") text: qsTr("Continue")
KeyNavigation.tab: setupLaterButton
parentFlickable: fl parentFlickable: fl
clickedFunc: function() { clickedFunc: function() {

View file

@ -49,6 +49,32 @@ PageType {
interactive: false interactive: false
model: proxyContainersModel model: proxyContainersModel
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()
}
delegate: Item { delegate: Item {
implicitWidth: processedContainerListView.width implicitWidth: processedContainerListView.width
implicitHeight: (delegateContent.implicitHeight > root.height) ? delegateContent.implicitHeight : root.height implicitHeight: (delegateContent.implicitHeight > root.height) ? delegateContent.implicitHeight : root.height
@ -62,19 +88,12 @@ PageType {
anchors.rightMargin: 16 anchors.rightMargin: 16
anchors.leftMargin: 16 anchors.leftMargin: 16
Item {
id: focusItem
KeyNavigation.tab: backButton
}
BackButtonType { BackButtonType {
id: backButton id: backButton
Layout.topMargin: 20 Layout.topMargin: 20
Layout.rightMargin: -16 Layout.rightMargin: -16
Layout.leftMargin: -16 Layout.leftMargin: -16
KeyNavigation.tab: showDetailsButton
} }
HeaderType { HeaderType {
@ -104,42 +123,19 @@ PageType {
KeyNavigation.tab: transportProtoSelector KeyNavigation.tab: transportProtoSelector
clickedFunc: function() { clickedFunc: function() {
showDetailsDrawer.open() showDetailsDrawer.openTriggered()
} }
} }
DrawerType2 { DrawerType2 {
id: showDetailsDrawer id: showDetailsDrawer
parent: root parent: root
onClosed: {
if (!GC.isMobile()) {
defaultActiveFocusItem.forceActiveFocus()
}
}
anchors.fill: parent anchors.fill: parent
expandedHeight: parent.height * 0.9 expandedHeight: parent.height * 0.9
expandedContent: Item { expandedStateContent: Item {
Connections {
target: showDetailsDrawer
enabled: !GC.isMobile()
function onOpened() {
focusItem2.forceActiveFocus()
}
}
implicitHeight: showDetailsDrawer.expandedHeight implicitHeight: showDetailsDrawer.expandedHeight
Item {
id: focusItem2
KeyNavigation.tab: showDetailsBackButton
onFocusChanged: {
if (focusItem2.activeFocus) {
fl.contentY = 0
}
}
}
BackButtonType { BackButtonType {
id: showDetailsBackButton id: showDetailsBackButton
@ -148,10 +144,8 @@ PageType {
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 16 anchors.topMargin: 16
KeyNavigation.tab: showDetailsCloseButton
backButtonFunction: function() { backButtonFunction: function() {
showDetailsDrawer.close() showDetailsDrawer.closeTriggered()
} }
} }
@ -205,10 +199,9 @@ PageType {
parentFlickable: fl parentFlickable: fl
text: qsTr("Close") text: qsTr("Close")
Keys.onTabPressed: lastItemTabClicked(focusItem2)
clickedFunc: function() { clickedFunc: function() {
showDetailsDrawer.close() showDetailsDrawer.closeTriggered()
} }
} }
} }
@ -229,8 +222,6 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
rootWidth: root.width rootWidth: root.width
KeyNavigation.tab: (port.visible && port.enabled) ? port.textField : installButton
} }
TextFieldWithHeaderType { TextFieldWithHeaderType {
@ -242,8 +233,6 @@ PageType {
headerText: qsTr("Port") headerText: qsTr("Port")
textField.maximumLength: 5 textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 } textField.validator: IntValidator { bottom: 1; top: 65535 }
KeyNavigation.tab: installButton
} }
Rectangle { Rectangle {
@ -259,8 +248,6 @@ PageType {
text: qsTr("Install") text: qsTr("Install")
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() { clickedFunc: function() {
if (!port.textField.acceptableInput && if (!port.textField.acceptableInput &&
ContainerProps.containerTypeToString(dockerContainer) !== "torwebsite" && ContainerProps.containerTypeToString(dockerContainer) !== "torwebsite" &&
@ -288,11 +275,6 @@ PageType {
var protocolSelectorVisible = ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) var protocolSelectorVisible = ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto)
transportProtoSelector.visible = protocolSelectorVisible transportProtoSelector.visible = protocolSelectorVisible
transportProtoHeader.visible = protocolSelectorVisible transportProtoHeader.visible = protocolSelectorVisible
if (port.visible && port.enabled)
defaultActiveFocusItem = port.textField
else
defaultActiveFocusItem = focusItem
} }
} }
} }

View file

@ -15,13 +15,6 @@ import "../Config"
PageType { PageType {
id: root id: root
defaultActiveFocusItem: focusItem
Item {
id: focusItem
KeyNavigation.tab: backButton
}
SortFilterProxyModel { SortFilterProxyModel {
id: proxyContainersModel id: proxyContainersModel
sourceModel: ContainersModel sourceModel: ContainersModel
@ -41,126 +34,66 @@ PageType {
} }
} }
ColumnLayout { BackButtonType {
id: backButtonLayout id: backButton
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 anchors.topMargin: 20
BackButtonType {
id: backButton
KeyNavigation.tab: containers
}
} }
FlickableType { ListView {
id: fl id: listView
anchors.top: backButtonLayout.bottom anchors.top: backButton.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
contentHeight: content.implicitHeight + content.anchors.topMargin + content.anchors.bottomMargin anchors.right: parent.right
anchors.left: parent.left
Column { property bool isFocusable: true
id: content
anchors.top: parent.top ScrollBar.vertical: ScrollBarType {}
anchors.left: parent.left
anchors.right: parent.right
anchors.bottomMargin: 20
Item { header: ColumnLayout {
width: parent.width width: listView.width
height: header.implicitHeight
HeaderType { HeaderType {
id: header id: header
anchors.fill: parent Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16
anchors.leftMargin: 16 headerText: qsTr("VPN protocol")
anchors.rightMargin: 16 descriptionText: qsTr("Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP.")
}
}
width: parent.width model: proxyContainersModel
clip: true
spacing: 0
reuseItems: true
snapMode: ListView.SnapToItem
headerText: qsTr("VPN protocol") delegate: ColumnLayout {
descriptionText: qsTr("Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP.") width: listView.width
LabelWithButtonType {
Layout.fillWidth: true
text: name
descriptionText: description
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
ContainersModel.setProcessedContainerIndex(proxyContainersModel.mapToSource(index))
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
} }
} }
ListView { DividerType {}
id: containers
width: parent.width
height: containers.contentItem.height
// currentIndex: -1
clip: true
interactive: false
model: proxyContainersModel
function ensureCurrentItemVisible() {
if (currentIndex >= 0) {
if (currentItem.y < fl.contentY) {
fl.contentY = currentItem.y
} else if (currentItem.y + currentItem.height + header.height > fl.contentY + fl.height) {
fl.contentY = currentItem.y + currentItem.height + header.height - fl.height + 40 // 40 is a bottom margin
}
}
}
activeFocusOnTab: true
Keys.onTabPressed: {
if (currentIndex < this.count - 1) {
this.incrementCurrentIndex()
} else {
this.currentIndex = 0
focusItem.forceActiveFocus()
}
ensureCurrentItemVisible()
}
onVisibleChanged: {
if (visible) {
currentIndex = 0
}
}
delegate: Item {
implicitWidth: containers.width
implicitHeight: delegateContent.implicitHeight
onActiveFocusChanged: {
if (activeFocus) {
container.rightButton.forceActiveFocus()
}
}
ColumnLayout {
id: delegateContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
LabelWithButtonType {
id: container
Layout.fillWidth: true
text: name
descriptionText: description
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
ContainersModel.setProcessedContainerIndex(proxyContainersModel.mapToSource(index))
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
}
}
DividerType {}
}
}
}
} }
} }
} }

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