Compare commits

...

8 Commits

Author SHA1 Message Date
NickVs2015
65664d69b9 fix: dev rebase errors 2026-05-30 13:48:17 +03:00
NickVs2015
dbabac3bd5 fix: add russian transaltion and fix margin 2026-05-30 13:48:17 +03:00
NickVs2015
c203540754 fix: reset defaultContainer to AWG when switching to auto mode 2026-05-30 13:48:17 +03:00
NickVs2015
cff1e2962c feat: fix AWG auto-switch — isAutoMode guard, V1/ExternalPremium coverage, reconnect on container update 2026-05-30 13:48:17 +03:00
NickVs2015
586e1f0b71 feat: fix auto switch 2026-05-30 13:48:17 +03:00
NickVs2015
65ba95f344 feat: add UI to protocol auto-switching 2026-05-30 13:48:17 +03:00
NickVs2015
d7f301ea3b feat: add auto switch from AWG to Xray (only premium) 2026-05-30 13:48:17 +03:00
NickVs2015
a1e28ba9af feat: add auto switch from AWG to Xray 2026-05-30 13:48:17 +03:00
15 changed files with 649 additions and 40 deletions

View File

@@ -418,7 +418,9 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
}
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
QString serviceProtocol = apiV2->serviceProtocol();
ProtocolData protocolData = generateProtocolData(serviceProtocol);
// Auto mode (empty) defaults to AWG — gateway requires public_key for all requests
const QString effectiveProtocol = serviceProtocol.isEmpty() ? configKey::awg : serviceProtocol;
ProtocolData protocolData = generateProtocolData(effectiveProtocol);
QJsonObject authDataJson = apiV2->authData.toJson();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
@@ -432,7 +434,7 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
authDataJson };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
appendProtocolDataToApiPayload(effectiveProtocol, protocolData, apiPayload);
if (isConnectEvent) {
apiPayload[apiDefs::key::isConnectEvent] = true;
@@ -451,11 +453,11 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
}
QJsonObject serverConfigJson;
errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
errorCode = extractServerConfigJsonFromResponse(responseBody, effectiveProtocol, protocolData, serverConfigJson);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
updateApiConfigInJson(serverConfigJson, apiV2->apiConfig.serviceType, serviceProtocol, apiV2->apiConfig.userCountryCode, responseBody);
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
@@ -741,6 +743,9 @@ void SubscriptionController::setCurrentProtocol(const QString &serverId, const Q
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (apiV2.has_value()) {
apiV2->apiConfig.serviceProtocol = protocolName;
if (protocolName.isEmpty()) {
apiV2->defaultContainer = DockerContainer::Awg;
}
m_serversRepository->editServer(serverId, apiV2->toJson(),
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
}
@@ -752,6 +757,12 @@ bool SubscriptionController::isVlessProtocol(const QString &serverId) const
return apiV2.has_value() && apiV2->serviceProtocol() == "vless";
}
bool SubscriptionController::isAwgProtocol(const QString &serverId) const
{
auto apiV2 = m_serversRepository->apiV2Config(serverId);
return apiV2.has_value() && apiV2->serviceProtocol() == "awg";
}
ErrorCode SubscriptionController::processAppStorePurchase(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const QString &productId,
int *duplicateServerIndex)

View File

@@ -86,6 +86,7 @@ public:
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
bool isVlessProtocol(const QString &serverId) const;
bool isAwgProtocol(const QString &serverId) const;
ErrorCode getAccountInfo(const QString &serverId, QJsonObject &accountInfo);
QFuture<QPair<ErrorCode, QString>> getRenewalLink(const QString &serverId);

View File

@@ -225,6 +225,22 @@ void CoreController::initControllers()
m_connectionController, this);
setQmlContextProperty("SubscriptionUiController", m_subscriptionUiController);
connect(m_connectionUiController, &ConnectionUiController::requestSetCurrentProtocol,
m_subscriptionUiController, &SubscriptionUiController::setCurrentProtocol, Qt::QueuedConnection);
connect(m_connectionUiController, &ConnectionUiController::requestUpdateServiceFromGateway,
m_subscriptionUiController, &SubscriptionUiController::updateServiceFromGateway, Qt::QueuedConnection);
connect(m_subscriptionUiController, &SubscriptionUiController::updateServiceFromGatewayCompleted,
m_connectionUiController, &ConnectionUiController::onUpdateServiceFromGatewayCompleted,
Qt::QueuedConnection);
connect(m_connectionUiController, &ConnectionUiController::requestSetProcessedServer,
this, [this](const QString &serverId) {
m_serversUiController->setProcessedServerId(serverId);
}, Qt::QueuedConnection);
connect(m_installUiController, &InstallUiController::updateContainerFinished,
m_connectionUiController, [this](const QString &, bool) {
m_connectionUiController->onCurrentContainerUpdated();
});
m_apiNewsUiController = new ApiNewsUiController(m_newsModel, m_newsController, this);
setQmlContextProperty("ApiNewsController", m_apiNewsUiController);

View File

@@ -82,11 +82,33 @@
#endif
class CoreSignalHandlers;
class TestMultipleImports;
class TestAdminSelfHostedExport;
class TestServerEdit;
class TestDefaultServerChange;
class TestServerEdgeCases;
class TestSignalOrder;
class TestServersModelSync;
class TestComplexOperations;
class TestSettingsSignals;
class TestUiServersModelAndController;
class TestSelfHostedServerSetup;
class CoreController : public QObject
{
Q_OBJECT
friend class CoreSignalHandlers;
friend class TestMultipleImports;
friend class TestAdminSelfHostedExport;
friend class TestServerEdit;
friend class TestDefaultServerChange;
friend class TestServerEdgeCases;
friend class TestSignalOrder;
friend class TestServersModelSync;
friend class TestComplexOperations;
friend class TestSettingsSignals;
friend class TestUiServersModelAndController;
friend class TestSelfHostedServerSetup;
public:
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,

View File

@@ -161,7 +161,12 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
return encRequestData.errorCode;
}
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
QNetworkAccessManager *nam = amnApp ? amnApp->networkManager() : nullptr;
if (!nam) {
return ErrorCode::InternalError;
}
QNetworkReply *reply = nam->post(encRequestData.request, encRequestData.requestBody);
QEventLoop wait;
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
@@ -236,7 +241,14 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
return promise->future();
}
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
QNetworkAccessManager *nam = amnApp ? amnApp->networkManager() : nullptr;
if (!nam) {
promise->addResult(qMakePair(ErrorCode::InternalError, QByteArray()));
promise->finish();
return promise->future();
}
QNetworkReply *reply = nam->post(encRequestData.request, encRequestData.requestBody);
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
@@ -378,9 +390,14 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
return {};
}
QNetworkAccessManager *nam = amnApp ? amnApp->networkManager() : nullptr;
if (!nam) {
return {};
}
for (const auto &proxyStorageUrl : proxyStorageUrls) {
request.setUrl(proxyStorageUrl);
reply = amnApp->networkManager()->get(request);
reply = nam->get(request);
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });

View File

@@ -2042,6 +2042,16 @@ Thank you for staying with us!</source>
<source>Cannot change protocol during active connection</source>
<translation>Невозможно изменить протокол во время активного соединения</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="289"/>
<source>Connection</source>
<translation>Соединение</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="290"/>
<source>Protocol selection and local proxy setup</source>
<translation>Выбор протокола и настройка локального прокси</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="319"/>
<source>Subscription Key</source>
@@ -5399,4 +5409,52 @@ FileZilla или другие SFTP-клиенты, а также смонтир
<translation>Сохранить</translation>
</message>
</context>
<context>
<name>PageSettingsConnectionProtocols</name>
<message>
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="142"/>
<source>VPN Protocol</source>
<translation>VPN-протокол</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="161"/>
<source>Choose automatically</source>
<translation>Выбирать автоматически</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="162"/>
<source>AmneziaWG is used by default. If the connection is unstable, the app will switch to VLESS. On the next launch, AmneziaWG will be tried again</source>
<translation>По умолчанию используется AmneziaWG. Если соединение нестабильно, приложение переключится на VLESS. При следующем запуске снова будет использован AmneziaWG</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="166"/>
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="196"/>
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="227"/>
<source>Cannot change protocol during active connection</source>
<translation>Невозможно изменить протокол во время активного соединения</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="192"/>
<source>AmneziaWG</source>
<translation>AmneziaWG</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="223"/>
<source>XRay VLESS Reality</source>
<translation>XRay VLESS Reality</translation>
</message>
</context>
<context>
<name>PageSettingsConnectionType</name>
<message>
<location filename="../ui/qml/Pages2/PageSettingsConnectionType.qml" line="46"/>
<source>Connection</source>
<translation>Соединение</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsConnectionType.qml" line="63"/>
<source>VPN protocol</source>
<translation>VPN-протокол</translation>
</message>
</context>
</TS>

View File

@@ -436,6 +436,7 @@ bool SubscriptionUiController::updateServiceFromGateway(const QString &serverId,
} else {
emit changeApiCountryFinished(tr("Successfully changed the country of connection to %1").arg(newCountryName));
}
emit updateServiceFromGatewayCompleted(true, serverId);
return true;
} else {
if (errorCode == ErrorCode::ApiSubscriptionExpiredError) {
@@ -443,6 +444,7 @@ bool SubscriptionUiController::updateServiceFromGateway(const QString &serverId,
} else {
emit errorOccurred(errorCode);
}
emit updateServiceFromGatewayCompleted(false, serverId);
return false;
}
}
@@ -508,6 +510,11 @@ bool SubscriptionUiController::isVlessProtocol(const QString &serverId)
return m_subscriptionController->isVlessProtocol(serverId);
}
bool SubscriptionUiController::isAwgProtocol(const QString &serverId)
{
return m_subscriptionController->isAwgProtocol(serverId);
}
void SubscriptionUiController::removeApiConfig(const QString &serverId)
{

View File

@@ -57,6 +57,7 @@ public slots:
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
bool isVlessProtocol(const QString &serverId);
bool isAwgProtocol(const QString &serverId);
bool isCaptchaAwaitingUser() const;
void onCaptchaSolved(const QString &captchaId, const QString &solution);
@@ -83,6 +84,7 @@ signals:
void changeApiCountryFinished(const QString &message);
void reloadServerFromApiFinished(const QString &message);
void updateServerFromApiFinished();
void updateServiceFromGatewayCompleted(bool success, const QString &serverId);
void subscriptionRefreshNeeded();
void apiConfigRemoved(const QString &message);

View File

@@ -8,6 +8,7 @@
#include "amneziaApplication.h"
#include "core/controllers/serversController.h"
#include "core/utils/containers/containerUtils.h"
ConnectionUiController::ConnectionUiController(ConnectionController* connectionController,
ServersController* serversController,
@@ -20,6 +21,9 @@ ConnectionUiController::ConnectionUiController(ConnectionController* connectionC
connect(this, &ConnectionUiController::connectButtonClicked, this, &ConnectionUiController::toggleConnection, Qt::QueuedConnection);
m_awgStateTimer.setSingleShot(true);
connect(&m_awgStateTimer, &QTimer::timeout, this, &ConnectionUiController::onAwgStateTimeout);
m_state = Vpn::ConnectionState::Disconnected;
}
@@ -56,6 +60,9 @@ void ConnectionUiController::onConnectionStateChanged(Vpn::ConnectionState state
m_connectionStateText = tr("Connecting...");
switch (state) {
case Vpn::ConnectionState::Connected: {
if (m_awgStateTimer.isActive()) {
m_awgStateTimer.stop();
}
amnApp->networkManager()->clearConnectionCache();
m_isConnectionInProgress = false;
@@ -64,36 +71,55 @@ void ConnectionUiController::onConnectionStateChanged(Vpn::ConnectionState state
break;
}
case Vpn::ConnectionState::Connecting: {
checkAndStartAwgStateTimer();
m_isConnectionInProgress = true;
break;
}
case Vpn::ConnectionState::Reconnecting: {
if (m_awgStateTimer.isActive()) {
m_awgStateTimer.stop();
}
m_isConnectionInProgress = true;
m_connectionStateText = tr("Reconnecting...");
break;
}
case Vpn::ConnectionState::Disconnected: {
if (m_awgStateTimer.isActive()) {
m_awgStateTimer.stop();
}
m_isConnectionInProgress = false;
m_connectionStateText = tr("Connect");
break;
}
case Vpn::ConnectionState::Disconnecting: {
if (m_awgStateTimer.isActive()) {
m_awgStateTimer.stop();
}
m_isConnectionInProgress = true;
m_connectionStateText = tr("Disconnecting...");
break;
}
case Vpn::ConnectionState::Preparing: {
if (m_awgStateTimer.isActive()) {
m_awgStateTimer.stop();
}
m_isConnectionInProgress = true;
m_connectionStateText = tr("Preparing...");
break;
}
case Vpn::ConnectionState::Error: {
if (m_awgStateTimer.isActive()) {
m_awgStateTimer.stop();
}
m_isConnectionInProgress = false;
m_connectionStateText = tr("Connect");
emit connectionErrorOccurred(getLastConnectionError());
break;
}
case Vpn::ConnectionState::Unknown: {
if (m_awgStateTimer.isActive()) {
m_awgStateTimer.stop();
}
m_isConnectionInProgress = false;
m_connectionStateText = tr("Connect");
emit connectionErrorOccurred(getLastConnectionError());
@@ -143,3 +169,113 @@ bool ConnectionUiController::isConnected() const
{
return m_isConnected;
}
void ConnectionUiController::onCurrentContainerUpdated()
{
if (m_isConnected || m_isConnectionInProgress) {
emit reconnectWithUpdatedContainer(tr("Settings updated successfully, reconnecting..."));
openConnection();
} else {
emit reconnectWithUpdatedContainer(tr("Settings updated successfully"));
}
}
void ConnectionUiController::checkAndStartAwgStateTimer()
{
const QString serverId = m_serversController->getDefaultServerId();
if (serverId.isEmpty()) {
if (m_awgStateTimer.isActive()) {
m_awgStateTimer.stop();
}
return;
}
const DockerContainer container = m_serversController->getDefaultContainer(serverId);
const Proto proto = ContainerUtils::defaultProtocol(container);
if (proto == Proto::Awg) {
const auto v2Config = m_serversController->apiV2Config(serverId);
if (v2Config.has_value() && (v2Config->isPremium() || v2Config->isExternalPremium())) {
const bool isAutoMode = v2Config->serviceProtocol().isEmpty();
if (isAutoMode) {
if (!m_awgStateTimer.isActive()) {
m_awgStateTimer.start(kAwgSwitchTimeoutMs);
}
return;
}
}
else if (m_serversController->isLegacyApiV1Server(serverId)) {
if (!m_awgStateTimer.isActive()) {
m_awgStateTimer.start(kAwgSwitchTimeoutMs);
}
return;
}
}
if (m_awgStateTimer.isActive()) {
m_awgStateTimer.stop();
}
}
void ConnectionUiController::onAwgStateTimeout()
{
if (m_state != Vpn::ConnectionState::Connecting) {
return;
}
const QString serverId = m_serversController->getDefaultServerId();
if (serverId.isEmpty()) {
return;
}
const DockerContainer container = m_serversController->getDefaultContainer(serverId);
const Proto proto = ContainerUtils::defaultProtocol(container);
if (proto != Proto::Awg) {
return;
}
closeConnection();
QTimer::singleShot(1000, this, [this, serverId]() {
if (m_isConnected || m_isConnectionInProgress) {
return;
}
qDebug().noquote() << "AWG connect timeout: trying to switch API protocol to VLESS and reload config from gateway";
m_pendingApiServerId = serverId;
m_apiSwitched = false;
m_waitingForApiUpdate = true;
emit requestSetProcessedServer(serverId);
emit requestSetCurrentProtocol(serverId, QStringLiteral("vless"));
emit requestUpdateServiceFromGateway(serverId, QString(), QString(), true);
});
}
void ConnectionUiController::onUpdateServiceFromGatewayCompleted(bool success, const QString &serverId)
{
if (!m_waitingForApiUpdate || m_pendingApiServerId != serverId) {
return;
}
m_waitingForApiUpdate = false;
m_apiSwitched = success;
if (success) {
const QMap<DockerContainer, ContainerConfig> containersMap = m_serversController->getServerContainersMap(serverId);
if (containersMap.contains(DockerContainer::Xray)) {
qDebug().noquote() << "AWG connect timeout (10s), switching default container to Xray and reconnecting";
m_serversController->setDefaultContainer(serverId, DockerContainer::Xray);
emit requestSetCurrentProtocol(serverId, QStringLiteral("vless"));
m_pendingApiServerId.clear();
if (!m_isConnected && !m_isConnectionInProgress) {
emit prepareConfig();
}
return;
}
}
qDebug().noquote() << "AWG connect timeout: no Xray available (API switch success ="
<< (m_apiSwitched ? "YES" : "NO") << ")";
m_pendingApiServerId.clear();
}

View File

@@ -2,6 +2,7 @@
#define CONNECTIONUICONTROLLER_H
#include <QObject>
#include <QTimer>
#include "core/controllers/connectionController.h"
#include "core/utils/errorCodes.h"
@@ -40,6 +41,14 @@ public slots:
void onTranslationsUpdated();
public slots:
void checkAndStartAwgStateTimer();
void onUpdateServiceFromGatewayCompleted(bool success, const QString &serverId);
void onCurrentContainerUpdated();
private slots:
void onAwgStateTimeout();
signals:
void connectionStateChanged();
@@ -49,9 +58,19 @@ signals:
void preparingConfig();
void prepareConfig();
// serverId + protocol — both carried so the receiver doesn't need to re-read default server
void requestSetCurrentProtocol(const QString &serverId, const QString &protocol);
void requestUpdateServiceFromGateway(const QString &serverId, const QString &newCountryCode,
const QString &newCountryName, bool reloadServiceConfig);
void requestSetProcessedServer(const QString &serverId);
void reconnectWithUpdatedContainer(const QString &message);
private:
Vpn::ConnectionState getCurrentConnectionState();
static constexpr int kAwgSwitchTimeoutMs = 10000;
QTimer m_awgStateTimer;
ConnectionController* m_connectionController;
ServersController* m_serversController;
@@ -60,6 +79,10 @@ private:
QString m_connectionStateText = tr("Connect");
Vpn::ConnectionState m_state;
QString m_pendingApiServerId;
bool m_apiSwitched = false;
bool m_waitingForApiUpdate = false;
};
#endif

View File

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

View File

@@ -263,46 +263,16 @@ PageType {
&& root.isSubscriptionRenewalAvailable && !root.isInAppPurchase
}
SwitcherType {
id: switcher
readonly property bool isVlessProtocol: SubscriptionUiController.isVlessProtocol(ServersUiController.processedServerId)
readonly property bool isProtocolSwitchBlocked: ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 24
visible: ApiAccountInfoModel.data("isProtocolSelectionSupported")
enabled: !switcher.isProtocolSwitchBlocked
text: qsTr("Use VLESS protocol")
checked: switcher.isVlessProtocol
onToggled: function() {
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
} else {
PageController.showBusyIndicator(true)
SubscriptionUiController.setCurrentProtocol(ServersUiController.processedServerId, switcher.isVlessProtocol ? "awg" : "vless")
SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, "", "", true)
PageController.showBusyIndicator(false)
}
}
}
DividerType {
visible: footer.isVisibleForAmneziaFree
}
WarningType {
id: warning
Layout.topMargin: 24
Layout.topMargin: visible ? 24 : 0
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.fillWidth: true
Layout.preferredHeight: visible ? implicitHeight : 0
backGroundColor: AmneziaStyle.color.translucentRichBrown
@@ -320,11 +290,25 @@ PageType {
}
LabelWithButtonType {
id: vpnKey
id: connectionSwitcher
Layout.fillWidth: true
Layout.topMargin: warning.visible ? 16 : 0
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,244 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Config"
PageType {
id: root
// Protocol to re-assert after updateServiceFromGateway completes (empty = auto)
property string pendingProtocol: ""
property bool waitingForGatewayUpdate: false
Timer {
id: updateProtocolTimer
interval: 100
repeat: false
onTriggered: {
var serverId = ServersUiController.getServerId(ServersUiController.processedServerIndex)
root.waitingForGatewayUpdate = true
SubscriptionUiController.updateServiceFromGateway(serverId, "", "", true)
}
}
function getCurrentProtocol() {
try {
if (SubscriptionUiController.isVlessProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex))) {
return "vless"
}
if (SubscriptionUiController.isAwgProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex))) {
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: SubscriptionUiController
function onUpdateServerFromApiFinished() {
if (!root.waitingForGatewayUpdate) {
return
}
root.waitingForGatewayUpdate = false
// Re-assert the protocol the user chose (gateway reload may have reset it)
if (root.pendingProtocol !== "") {
var protocolToSet = root.pendingProtocol === "auto" ? "" : root.pendingProtocol
SubscriptionUiController.setCurrentProtocol(
ServersUiController.getServerId(ServersUiController.processedServerIndex),
protocolToSet)
}
root.currentProtocol = root.getCurrentProtocol()
}
}
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 || !ServersUiController.isDefaultServerCurrentlyProcessed()
text: qsTr("Choose automatically")
descriptionText: qsTr("AmneziaWG is used by default. If the connection is unstable, the app will switch to VLESS. On the next launch, AmneziaWG will be tried again")
onClicked: function() {
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
return
}
SubscriptionUiController.setCurrentProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex), "")
root.pendingProtocol = "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 || !ServersUiController.isDefaultServerCurrentlyProcessed()
text: qsTr("AmneziaWG")
onClicked: function() {
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
return
}
root.currentProtocol = "awg"
root.pendingProtocol = "awg"
SubscriptionUiController.setCurrentProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex), "awg")
updateProtocolTimer.start()
}
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 || !ServersUiController.isDefaultServerCurrentlyProcessed()
text: qsTr("XRay VLESS Reality")
onClicked: function() {
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
return
}
root.currentProtocol = "vless"
root.pendingProtocol = "vless"
SubscriptionUiController.setCurrentProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex), "vless")
updateProtocolTimer.start()
}
Keys.onEnterPressed: this.clicked()
Keys.onReturnPressed: this.clicked()
}
DividerType {}
}
}
}

View File

@@ -0,0 +1,84 @@
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
text: qsTr("VPN protocol")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsConnectionProtocols)
}
}
// TODO: Local proxy
// 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() {}
// }
// DividerType {}
}
}
}

View File

@@ -102,6 +102,8 @@
<file>Pages2/PageSettingsAppSplitTunneling.qml</file>
<file>Pages2/PageSettingsBackup.qml</file>
<file>Pages2/PageSettingsConnection.qml</file>
<file>Pages2/PageSettingsConnectionProtocols.qml</file>
<file>Pages2/PageSettingsConnectionType.qml</file>
<file>Pages2/PageSettingsDns.qml</file>
<file>Pages2/PageSettingsKillSwitch.qml</file>
<file>Pages2/PageSettingsKillSwitchExceptions.qml</file>