2023-04-12 19:13:41 +03:00
import QtQuick
import QtQuick . Window
import QtQuick . Controls
import QtQuick . Layouts
2023-09-06 22:20:59 +05:00
import QtQuick . Dialogs
2023-04-12 19:13:41 +03:00
2023-06-01 11:25:33 +08:00
import PageEnum 1.0
2024-07-07 13:42:38 +03:00
import Style 1.0
2023-04-12 19:13:41 +03:00
import "Config"
2023-06-01 11:25:33 +08:00
import "Controls2"
2024-02-16 15:24:06 +05:00
import "Components"
2024-08-20 16:54:05 +07:00
import "Pages2"
2023-04-12 19:13:41 +03:00
Window {
id: root
2023-09-06 22:20:59 +05:00
objectName: "mainWindow"
2024-12-31 04:16:52 +01:00
2025-11-04 11:43:36 +08:00
Connections {
target: Qt . application
function onStateChanged ( ) {
2025-12-15 16:56:36 +03:00
if ( Qt . platform . os === "android" ) {
if ( Qt . application . state === Qt . ApplicationActive ) {
2026-03-16 08:03:20 +03:00
root . visible = true
2025-12-15 16:56:36 +03:00
refreshTimer . restart ( )
}
2025-11-04 11:43:36 +08:00
}
}
}
2026-03-24 17:13:31 +03:00
// Hide the window immediately when Android Activity.onPause() fires so that
// Qt's render loop stops before the EGL surface is disconnected. This
// prevents "QRhiGles2: Failed to make context current" and the resulting
// black screen that appears after swiping home and returning.
Connections {
target: SettingsController
function onActivityPaused ( ) {
if ( Qt . platform . os === "android" ) root . visible = false
}
function onActivityResumed ( ) {
if ( Qt . platform . os === "android" ) root . visible = true
}
}
2025-11-04 11:43:36 +08:00
Timer {
id: refreshTimer
interval: 150
repeat: false
onTriggered: {
2026-04-30 14:53:03 +08:00
if ( Qt . platform . os === "android" && PageController . isEdgeToEdgeEnabled ( ) ) {
2025-11-04 11:43:36 +08:00
console . log ( "QML: Application resumed with edge-to-edge" )
}
}
}
2023-04-12 19:13:41 +03:00
visible: true
width: GC . screenWidth
height: GC . screenHeight
minimumWidth: GC . isDesktop ( ) ? 360 : 0
minimumHeight: GC . isDesktop ( ) ? 640 : 0
2023-09-01 17:39:23 +05:00
maximumWidth: 600
maximumHeight: 800
2023-05-03 19:06:16 +03:00
2024-08-20 16:54:05 +07:00
color: AmneziaStyle . color . midnightBlack
2023-06-10 05:25:41 +03:00
2025-08-10 06:12:19 +03:00
onClosing: function ( close ) {
close . accepted = false
2023-07-24 16:31:04 +09:00
PageController . closeWindow ( )
2023-04-12 19:13:41 +03:00
}
2026-03-03 17:04:45 +03:00
onSceneGraphError: function ( error , message ) {
// Prevent qFatal crash on Android when EGL context is lost
console . warn ( "Scene graph error:" , error , message )
}
2023-04-12 19:13:41 +03:00
title: "AmneziaVPN"
2024-12-31 04:16:52 +01:00
Item { // This item is needed for focus handling
id: defaultFocusItem
objectName: "defaultFocusItem"
focus: true
Keys.onPressed: function ( event ) {
switch ( event . key ) {
case Qt.Key_Tab:
case Qt.Key_Down:
case Qt.Key_Right:
FocusController . nextKeyTabItem ( )
break
case Qt.Key_Backtab:
case Qt.Key_Up:
case Qt.Key_Left:
FocusController . previousKeyTabItem ( )
break
default:
PageController . keyPressEvent ( event . key )
event . accepted = true
}
}
}
2026-02-05 17:57:15 +03:00
Loader {
active: Qt . platform . os === "android"
2026-02-09 05:40:48 +03:00
source: Qt . platform . os === "android" ? "Components/GamepadLoader.qml" : ""
2026-02-05 17:57:15 +03:00
}
2023-06-01 11:25:33 +08:00
Connections {
2024-12-31 04:16:52 +01:00
objectName: "pageControllerConnections"
2023-06-01 11:25:33 +08:00
target: PageController
2023-07-24 16:31:04 +09:00
function onRaiseMainWindow ( ) {
2023-07-14 13:14:50 +09:00
root . show ( )
root . raise ( )
root . requestActivate ( )
}
2023-07-24 16:31:04 +09:00
function onHideMainWindow ( ) {
root . hide ( )
}
2023-07-31 00:13:08 +09:00
function onShowErrorMessage ( errorMessage ) {
popupErrorMessage . text = errorMessage
popupErrorMessage . open ( )
}
function onShowNotificationMessage ( message ) {
popupNotificationMessage . text = message
popupNotificationMessage . closeButtonVisible = false
popupNotificationMessage . open ( )
popupNotificationTimer . start ( )
}
2023-08-02 20:37:43 +09:00
function onShowPassphraseRequestDrawer ( ) {
2024-12-31 04:16:52 +01:00
privateKeyPassphraseDrawer . openTriggered ( )
2023-08-02 20:37:43 +09:00
}
2023-09-13 16:11:08 +05:00
function onGoToPageSettingsBackup ( ) {
PageController . goToPage ( PageEnum . PageSettingsBackup )
}
2024-03-06 14:22:44 +05:00
function onShowBusyIndicator ( visible ) {
busyIndicator . visible = visible
PageController . disableControls ( visible )
}
2026-05-04 07:37:19 +03:00
function onShowChangelogDrawer ( ) {
changelogDrawer . openTriggered ( )
}
2023-07-31 00:13:08 +09:00
}
2023-08-16 12:11:34 +05:00
Connections {
2024-12-31 04:16:52 +01:00
objectName: "settingsControllerConnections"
2023-08-16 12:11:34 +05:00
target: SettingsController
2024-03-20 21:22:29 +07:00
2023-08-16 12:11:34 +05:00
function onChangeSettingsFinished ( finishedMessage ) {
PageController . showNotificationMessage ( finishedMessage )
}
}
2024-08-20 16:54:05 +07:00
PageStart {
2024-12-31 04:16:52 +01:00
objectName: "pageStart"
2024-09-20 15:36:20 +04:00
width: root . width
height: root . height
2024-08-20 16:54:05 +07:00
}
2023-07-31 00:13:08 +09:00
Item {
2024-12-31 04:16:52 +01:00
objectName: "popupNotificationItem"
2023-07-31 00:13:08 +09:00
anchors.right: parent . right
anchors.left: parent . left
anchors.bottom: parent . bottom
implicitHeight: popupNotificationMessage . height
PopupType {
id: popupNotificationMessage
}
Timer {
id: popupNotificationTimer
interval: 3000
repeat: false
running: false
onTriggered: {
popupNotificationMessage . close ( )
}
}
}
Item {
2024-12-31 04:16:52 +01:00
objectName: "popupErrorMessageItem"
2023-07-31 00:13:08 +09:00
anchors.right: parent . right
anchors.left: parent . left
anchors.bottom: parent . bottom
implicitHeight: popupErrorMessage . height
PopupType {
id: popupErrorMessage
}
2023-04-12 19:13:41 +03:00
}
2023-08-02 20:37:43 +09:00
2026-05-28 08:51:26 +03:00
Item {
objectName: "captchaDialogItem"
anchors.fill: parent
CaptchaDialogType {
id: captchaDialog
onCaptchaSolved: function ( captchaId , solution ) {
PageController . showBusyIndicator ( true )
Qt . callLater ( function ( ) {
SubscriptionUiController . onCaptchaSolved ( captchaId , solution )
} )
}
onRefreshCaptchaRequested: function ( ) {
SubscriptionUiController . onRefreshCaptchaRequested ( )
}
}
}
2023-08-02 20:37:43 +09:00
Item {
2024-12-31 04:16:52 +01:00
objectName: "privateKeyPassphraseDrawerItem"
2024-02-16 15:24:06 +05:00
anchors.fill: parent
2023-08-02 20:37:43 +09:00
2024-02-16 15:24:06 +05:00
DrawerType2 {
2023-08-02 20:37:43 +09:00
id: privateKeyPassphraseDrawer
2026-06-04 15:45:53 +01:00
property bool isCloseByUser: false
2024-02-16 15:24:06 +05:00
anchors.fill: parent
2026-04-30 14:53:03 +08:00
expandedHeight: root . height * 0.35 + PageController . safeAreaBottomMargin + PageController . imeHeight
2023-08-02 20:37:43 +09:00
2024-12-31 04:16:52 +01:00
expandedStateContent: ColumnLayout {
2023-08-02 20:37:43 +09:00
anchors.top: parent . top
anchors.left: parent . left
anchors.right: parent . right
anchors.topMargin: 16
anchors.leftMargin: 16
anchors.rightMargin: 16
2024-02-16 15:24:06 +05:00
Connections {
target: privateKeyPassphraseDrawer
function onOpened ( ) {
2025-01-24 23:27:01 +07:00
passphrase . textField . text = ""
2024-02-16 15:24:06 +05:00
passphrase . textField . forceActiveFocus ( )
}
function onAboutToHide ( ) {
2026-06-04 15:45:53 +01:00
if ( privateKeyPassphraseDrawer . isCloseByUser === false ) {
privateKeyPassphraseDrawer . isCloseByUser = true
PageController . passphraseRequestDrawerClosed ( "" )
}
2025-01-24 23:27:01 +07:00
if ( passphrase . textField . text !== "" ) {
2024-02-16 15:24:06 +05:00
PageController . showBusyIndicator ( true )
}
}
function onAboutToShow ( ) {
PageController . showBusyIndicator ( false )
}
}
2023-08-02 20:37:43 +09:00
TextFieldWithHeaderType {
id: passphrase
property bool hidePassword: true
Layout.fillWidth: true
headerText: qsTr ( "Private key passphrase" )
textField.echoMode: hidePassword ? TextInput.Password : TextInput . Normal
buttonImageSource: hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg"
clickedFunc: function ( ) {
hidePassword = ! hidePassword
}
}
BasicButtonType {
2024-02-17 23:09:05 +02:00
id: saveButton
2023-08-02 20:37:43 +09:00
Layout.fillWidth: true
2024-07-07 13:42:38 +03:00
defaultColor: AmneziaStyle . color . transparent
2024-08-20 16:54:05 +07:00
hoveredColor: AmneziaStyle . color . translucentWhite
pressedColor: AmneziaStyle . color . sheerWhite
disabledColor: AmneziaStyle . color . mutedGray
textColor: AmneziaStyle . color . paleGray
2023-08-02 20:37:43 +09:00
borderWidth: 1
text: qsTr ( "Save" )
2024-02-17 23:09:05 +02:00
clickedFunc: function ( ) {
2026-06-04 15:45:53 +01:00
privateKeyPassphraseDrawer . isCloseByUser = true
2024-12-31 04:16:52 +01:00
privateKeyPassphraseDrawer . closeTriggered ( )
2025-01-24 23:27:01 +07:00
PageController . passphraseRequestDrawerClosed ( passphrase . textField . text )
2023-08-02 20:37:43 +09:00
}
}
}
}
}
2023-09-06 22:20:59 +05:00
2024-02-16 15:24:06 +05:00
Item {
2024-12-31 04:16:52 +01:00
objectName: "questionDrawerItem"
2024-02-16 15:24:06 +05:00
anchors.fill: parent
QuestionDrawer {
id: questionDrawer
anchors.fill: parent
}
}
2026-03-24 17:45:02 +03:00
Item {
objectName: "subscriptionExpiredDrawerItem"
anchors.fill: parent
SubscriptionExpiredDrawer {
id: subscriptionExpiredDrawer
anchors.fill: parent
}
}
2026-05-15 12:33:36 +08:00
Connections {
target: PageController
function onUnsupportedConnectDrawerRequested ( ) {
root . showUnsupportedConnectDrawer ( )
}
}
2026-03-24 17:45:02 +03:00
Connections {
2026-04-30 14:53:03 +08:00
target: SubscriptionUiController
2026-03-24 17:45:02 +03:00
function onSubscriptionExpiredOnServer ( ) {
subscriptionExpiredDrawer . openTriggered ( )
}
2026-05-28 08:51:26 +03:00
function onCaptchaRequired ( captchaId , captchaImageBase64 , hint ) {
if ( captchaDialog . opened ) {
PageController . showBusyIndicator ( false )
}
captchaDialog . captchaId = captchaId
captchaDialog . captchaImageBase64 = captchaImageBase64
captchaDialog . hint = hint
captchaDialog . open ( )
}
function onCaptchaFlowDismissRequested ( ) {
PageController . showBusyIndicator ( false )
captchaDialog . close ( )
}
function onErrorOccurred ( error ) {
if ( captchaDialog . opened ) {
PageController . showBusyIndicator ( false )
}
}
2026-03-24 17:45:02 +03:00
}
Connections {
2026-04-30 14:53:03 +08:00
target: SubscriptionUiController
2026-03-24 17:45:02 +03:00
function onRenewalLinkReceived ( url ) {
Qt . openUrlExternally ( url )
}
}
2024-03-06 14:22:44 +05:00
Item {
2024-12-31 04:16:52 +01:00
objectName: "busyIndicatorItem"
2024-03-06 14:22:44 +05:00
anchors.fill: parent
BusyIndicatorType {
id: busyIndicator
anchors.centerIn: parent
z: 1
}
}
2026-05-15 12:33:36 +08:00
function showUnsupportedConnectDrawer ( ) {
let headerText = qsTr ( "This subscription format is no longer supported" )
let descriptionText = qsTr ( "This legacy Amnezia subscription type can no longer be used to connect in this application version.\nRemove the server from the app to continue." )
let yesButtonText = qsTr ( "Continue" )
let noButtonText = qsTr ( "Cancel" )
let yesButtonFunction = function ( ) {
if ( ConnectionController . isConnected ) {
PageController . showNotificationMessage ( qsTr ( "Cannot remove server during active connection" ) )
return
}
PageController . showBusyIndicator ( true )
InstallController . removeServer ( ServersUiController . defaultServerId )
PageController . showBusyIndicator ( false )
}
let noButtonFunction = function ( ) {
}
showQuestionDrawer ( headerText , descriptionText , yesButtonText , noButtonText , yesButtonFunction , noButtonFunction )
}
2024-02-16 15:24:06 +05:00
function showQuestionDrawer ( headerText , descriptionText , yesButtonText , noButtonText , yesButtonFunction , noButtonFunction ) {
questionDrawer . headerText = headerText
questionDrawer . descriptionText = descriptionText
questionDrawer . yesButtonText = yesButtonText
questionDrawer . noButtonText = noButtonText
questionDrawer . yesButtonFunction = function ( ) {
2024-12-31 04:16:52 +01:00
questionDrawer . closeTriggered ( )
2024-02-16 15:24:06 +05:00
if ( yesButtonFunction && typeof yesButtonFunction === "function" ) {
yesButtonFunction ( )
}
}
questionDrawer . noButtonFunction = function ( ) {
2024-12-31 04:16:52 +01:00
questionDrawer . closeTriggered ( )
2024-02-16 15:24:06 +05:00
if ( noButtonFunction && typeof noButtonFunction === "function" ) {
noButtonFunction ( )
}
}
2024-12-31 04:16:52 +01:00
questionDrawer . openTriggered ( )
2024-02-16 15:24:06 +05:00
}
2023-09-06 22:20:59 +05:00
FileDialog {
id: mainFileDialog
2024-12-31 04:16:52 +01:00
objectName: "mainFileDialog"
2023-09-06 22:20:59 +05:00
2023-09-07 22:45:01 +05:00
property bool isSaveMode: false
2023-09-06 22:20:59 +05:00
2023-09-07 22:45:01 +05:00
fileMode: isSaveMode ? FileDialog.SaveFile : FileDialog . OpenFile
2023-09-06 22:20:59 +05:00
2023-09-07 22:45:01 +05:00
onAccepted: SystemController . fileDialogClosed ( true )
onRejected: SystemController . fileDialogClosed ( false )
2023-09-06 22:20:59 +05:00
}
2026-05-04 07:37:19 +03:00
Item {
anchors.fill: parent
ChangelogDrawer {
id: changelogDrawer
anchors.fill: parent
}
}
2023-04-12 19:13:41 +03:00
}