feat: add UI to protocol auto-switching

This commit is contained in:
NickVs2015
2025-12-26 18:32:55 +03:00
parent de066b4dc9
commit 26607551be
9 changed files with 457 additions and 52 deletions

View File

@@ -250,6 +250,8 @@
<file>ui/qml/Components/AwgTextField.qml</file>
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
<file>ui/qml/Components/SmartScroll.qml</file>
<file>ui/qml/Pages2/PageSettingsConnectionType.qml</file>
<file>ui/qml/Pages2/PageSettingsConnectionProtocols.qml</file>
</qresource>
<qresource prefix="/countriesFlags">
<file>images/flagKit/ZW.svg</file>

View File

@@ -882,6 +882,18 @@ bool ApiConfigsController::isVlessProtocol()
return false;
}
bool ApiConfigsController::isAwgProtocol()
{
auto serverIndex = m_serversModel->getProcessedServerIndex();
auto serverConfigObject = m_serversModel->getServerConfig(serverIndex);
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
if (apiConfigObject[configKey::serviceProtocol].toString() == "awg") {
return true;
}
return false;
}
QList<QString> ApiConfigsController::getQrCodes()
{
return m_qrCodes;

View File

@@ -40,6 +40,7 @@ public slots:
void setCurrentProtocol(const QString &protocolName);
bool isVlessProtocol();
bool isAwgProtocol();
signals:
void errorOccurred(ErrorCode errorCode);

View File

@@ -14,6 +14,15 @@
#include "protocols/protocols_defs.h"
#include "version.h"
namespace
{
namespace configKey
{
constexpr char apiConfig[] = "api_config";
constexpr char serviceProtocol[] = "service_protocol";
}
}
ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<ClientManagementModel> &clientManagementModel,
@@ -98,31 +107,7 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state)
break;
}
case Vpn::ConnectionState::Connecting: {
{
const int serverIndex = m_serversModel->getDefaultServerIndex();
if (serverIndex >= 0) {
const QVariant containerVar = m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole);
if (containerVar.isValid()) {
const DockerContainer container = qvariant_cast<DockerContainer>(containerVar);
const Proto proto = ContainerProps::defaultProtocol(container);
if (proto == Proto::Awg) {
const QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex);
if (apiUtils::isPremiumServer(serverConfig)) {
m_awgStateTimer.start(10000);
} else {
if (m_awgStateTimer.isActive()) {
m_awgStateTimer.stop();
}
}
} else {
if (m_awgStateTimer.isActive()) {
m_awgStateTimer.stop();
}
}
}
}
}
checkAndStartAwgStateTimer();
m_isConnectionInProgress = true;
break;
}
@@ -320,3 +305,41 @@ bool ConnectionController::isConnected() const
{
return m_isConnected;
}
void ConnectionController::checkAndStartAwgStateTimer()
{
const int serverIndex = m_serversModel->getDefaultServerIndex();
if (serverIndex < 0) {
return;
}
const QVariant containerVar = m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole);
if (!containerVar.isValid()) {
return;
}
const DockerContainer container = qvariant_cast<DockerContainer>(containerVar);
const Proto proto = ContainerProps::defaultProtocol(container);
if (proto != Proto::Awg) {
if (m_awgStateTimer.isActive()) {
m_awgStateTimer.stop();
}
return;
}
const QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex);
// Auto-switching only works in auto mode, not when protocol is explicitly selected
const QJsonObject apiConfig = serverConfig.value(configKey::apiConfig).toObject();
const QString serviceProtocol = apiConfig.value(configKey::serviceProtocol).toString();
// Auto mode: empty serviceProtocol means use default protocol (AWG for AWG container)
const bool isAutoMode = serviceProtocol.isEmpty();
if (isAutoMode && apiUtils::isPremiumServer(serverConfig)) {
m_awgStateTimer.start(10000);
} else {
if (m_awgStateTimer.isActive()) {
m_awgStateTimer.stop();
}
}
}

View File

@@ -40,6 +40,8 @@ public slots:
void onTranslationsUpdated();
void checkAndStartAwgStateTimer();
private slots:
void onAwgStateTimeout();

View File

@@ -42,6 +42,8 @@ namespace PageLoader
PageSettingsApiDevices,
PageSettingsApiSubscriptionKey,
PageSettingsKillSwitchExceptions,
PageSettingsConnectionType,
PageSettingsConnectionProtocols,
PageServiceSftpSettings,
PageServiceTorWebsiteSettings,

View File

@@ -151,32 +151,6 @@ PageType {
readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible")
SwitcherType {
id: switcher
readonly property bool isVlessProtocol: ApiConfigsController.isVlessProtocol()
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
Layout.leftMargin: 16
visible: ApiAccountInfoModel.data("isProtocolSelectionSupported")
text: qsTr("Use VLESS protocol")
checked: switcher.isVlessProtocol
onToggled: function() {
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
} else {
PageController.showBusyIndicator(true)
ApiConfigsController.setCurrentProtocol(switcher.isVlessProtocol ? "awg" : "vless")
ApiConfigsController.updateServiceFromGateway(ServersModel.processedIndex, "", "", true)
PageController.showBusyIndicator(false)
}
}
}
WarningType {
id: warning
@@ -201,11 +175,25 @@ PageType {
}
LabelWithButtonType {
id: vpnKey
id: connectionSwitcher
Layout.fillWidth: true
Layout.topMargin: warning.visible ? 16 : 32
text: qsTr("Connection")
descriptionText: qsTr("Protocol selection and local proxy setup")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsConnectionType)
}
}
DividerType {}
LabelWithButtonType {
id: vpnKey
Layout.fillWidth: true
visible: footer.isVisibleForAmneziaFree
text: qsTr("Subscription Key")

View File

@@ -0,0 +1,283 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Config"
PageType {
id: root
Timer {
id: updateProtocolTimer
interval: 100
repeat: false
property string savedProtocol: ""
onTriggered: {
if (!root || !root.visible) {
PageController.showBusyIndicator(false)
return
}
ApiConfigsController.updateServiceFromGateway(ServersModel.processedIndex, "", "", true)
// After update, restore the protocol we set (because updateServiceFromGateway may overwrite it)
if (savedProtocol !== "") {
Qt.callLater(function() {
try {
if (!root || !root.visible) {
PageController.showBusyIndicator(false)
return
}
var protocolToSet = savedProtocol === "auto" ? "" : savedProtocol
ApiConfigsController.setCurrentProtocol(protocolToSet)
PageController.showBusyIndicator(false)
Qt.callLater(function() {
try {
if (root && root.visible && typeof root.getCurrentProtocol === "function") {
root.currentProtocol = root.getCurrentProtocol()
}
} catch (e) {
console.log("Error updating protocol display:", e)
}
})
} catch (e) {
console.log("Error in updateProtocolTimer:", e)
PageController.showBusyIndicator(false)
}
})
} else {
PageController.showBusyIndicator(false)
Qt.callLater(function() {
try {
if (root && root.visible && typeof root.getCurrentProtocol === "function") {
root.currentProtocol = root.getCurrentProtocol()
}
} catch (e) {
console.log("Error updating protocol display:", e)
}
})
}
}
}
function getCurrentProtocol() {
try {
if (ApiConfigsController.isVlessProtocol()) {
return "vless"
}
if (ApiConfigsController.isAwgProtocol()) {
return "awg"
}
// If neither VLESS nor AWG, it's auto mode
return "auto"
} catch (e) {
console.log("Error getting current protocol:", e)
return "auto"
}
}
property string currentProtocol: "auto"
Connections {
target: ServersModel
function onDataChanged() {
if (!root || !root.visible) {
return
}
Qt.callLater(function() {
try {
if (root && root.visible && typeof root.getCurrentProtocol === "function") {
root.currentProtocol = root.getCurrentProtocol()
}
} catch (e) {
console.log("Error in ServersModel.onDataChanged:", e)
}
})
}
}
Connections {
target: ApiConfigsController
function onUpdateServerFromApiFinished() {
if (!root || !root.visible) {
return
}
Qt.callLater(function() {
try {
if (root && root.visible && typeof root.getCurrentProtocol === "function") {
root.currentProtocol = root.getCurrentProtocol()
}
} catch (e) {
console.log("Error in ApiConfigsController.onUpdateServerFromApiFinished:", e)
}
})
}
}
Component.onCompleted: {
root.currentProtocol = root.getCurrentProtocol()
}
onVisibleChanged: {
if (visible) {
try {
if (typeof root.getCurrentProtocol === "function") {
root.currentProtocol = root.getCurrentProtocol()
}
} catch (e) {
console.log("Error in onVisibleChanged:", e)
}
}
}
BackButtonType {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) {
flickable.contentY = 0
}
}
}
FlickableType {
id: flickable
anchors.top: backButton.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
contentHeight: content.height
ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
BaseHeaderType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 16
headerText: qsTr("VPN Protocol")
}
ButtonGroup {
id: protocolButtonGroup
}
VerticalRadioButton {
id: autoProtocolButton
Layout.fillWidth: true
Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
ButtonGroup.group: protocolButtonGroup
checked: root.currentProtocol === "auto"
enabled: !ConnectionController.isConnected || !ServersModel.isDefaultServerCurrentlyProcessed()
text: qsTr("Choose automatically")
descriptionText: qsTr("AmneziaWG is used by default. If the connection is unstable, the app will switch to VLESS. It won't switch back automatically")
onClicked: function() {
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
return
}
PageController.showBusyIndicator(true)
ApiConfigsController.setCurrentProtocol("")
updateProtocolTimer.savedProtocol = "auto"
updateProtocolTimer.start()
}
Keys.onEnterPressed: this.clicked()
Keys.onReturnPressed: this.clicked()
}
DividerType {}
VerticalRadioButton {
id: awgProtocolButton
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
ButtonGroup.group: protocolButtonGroup
checked: root.currentProtocol === "awg"
enabled: !ConnectionController.isConnected || !ServersModel.isDefaultServerCurrentlyProcessed()
text: qsTr("AmneziaWG")
onClicked: function() {
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
return
}
PageController.showBusyIndicator(true)
ApiConfigsController.setCurrentProtocol("awg")
updateProtocolTimer.savedProtocol = "awg"
updateProtocolTimer.start()
root.currentProtocol = "awg"
}
Keys.onEnterPressed: this.clicked()
Keys.onReturnPressed: this.clicked()
}
DividerType {}
VerticalRadioButton {
id: vlessProtocolButton
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
ButtonGroup.group: protocolButtonGroup
checked: root.currentProtocol === "vless"
enabled: !ConnectionController.isConnected || !ServersModel.isDefaultServerCurrentlyProcessed()
text: qsTr("XRay VLESS Reality")
onClicked: function() {
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
return
}
PageController.showBusyIndicator(true)
ApiConfigsController.setCurrentProtocol("vless")
updateProtocolTimer.savedProtocol = "vless"
updateProtocolTimer.start()
root.currentProtocol = "vless"
}
Keys.onEnterPressed: this.clicked()
Keys.onReturnPressed: this.clicked()
}
DividerType {}
}
}
}

View File

@@ -0,0 +1,92 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Config"
PageType {
id: root
BackButtonType {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) {
listView.positionViewAtBeginning()
}
}
}
ListViewType {
id: listView
anchors.top: backButton.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
header: ColumnLayout {
width: listView.width
BaseHeaderType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Connection")
}
}
model: 1
delegate: ColumnLayout {
width: listView.width
LabelWithButtonType {
id: vpnProtocolButton
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("VPN protocol")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsConnectionProtocols)
}
}
DividerType {}
LabelWithButtonType {
id: localProxyButton
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Local proxy")
descriptionText: qsTr("Running: 127.0.0.1:1080")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
// Placeholder - no action
}
}
DividerType {}
}
}
}