From c57162c4ccbfa35f722785000d4f8228111c260f Mon Sep 17 00:00:00 2001 From: vkamn Date: Tue, 24 Mar 2026 09:29:51 +0700 Subject: [PATCH] feat: add base amnezia trial support (#2366) * feat: add base amnezia trial support * feat: add external-trial --- client/core/api/apiDefs.h | 4 +- client/core/api/apiUtils.cpp | 13 +- .../controllers/api/apiConfigsController.cpp | 2 +- client/ui/models/api/apiAccountInfoModel.cpp | 4 +- client/ui/models/api/apiServicesModel.cpp | 7 +- .../Pages2/PageSetupWizardApiServiceInfo.qml | 452 +++++++++--------- 6 files changed, 249 insertions(+), 233 deletions(-) diff --git a/client/core/api/apiDefs.h b/client/core/api/apiDefs.h index 8ec919b8c..84ef0e68d 100644 --- a/client/core/api/apiDefs.h +++ b/client/core/api/apiDefs.h @@ -10,8 +10,10 @@ namespace apiDefs AmneziaFreeV3, AmneziaPremiumV1, AmneziaPremiumV2, + AmneziaTrialV2, SelfHosted, - ExternalPremium + ExternalPremium, + ExternalTrial }; enum ConfigSource { diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp index b2bee8be5..2d16c384c 100644 --- a/client/core/api/apiUtils.cpp +++ b/client/core/api/apiUtils.cpp @@ -58,18 +58,24 @@ apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObjec }; case apiDefs::ConfigSource::AmneziaGateway: { constexpr QLatin1String servicePremium("amnezia-premium"); + constexpr QLatin1String serviceTrial("amnezia-trial"); constexpr QLatin1String serviceFree("amnezia-free"); constexpr QLatin1String serviceExternalPremium("external-premium"); + constexpr QLatin1String serviceExternalTrial("external-trial"); auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject(); auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString(); if (serviceType == servicePremium) { return apiDefs::ConfigType::AmneziaPremiumV2; + } else if (serviceType == serviceTrial) { + return apiDefs::ConfigType::AmneziaTrialV2; } else if (serviceType == serviceFree) { return apiDefs::ConfigType::AmneziaFreeV3; } else if (serviceType == serviceExternalPremium) { return apiDefs::ConfigType::ExternalPremium; + } else if (serviceType == serviceExternalTrial) { + return apiDefs::ConfigType::ExternalTrial; } } default: { @@ -133,7 +139,8 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList &ssl bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject) { static const QSet premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2, - apiDefs::ConfigType::ExternalPremium }; + apiDefs::ConfigType::AmneziaTrialV2, apiDefs::ConfigType::ExternalPremium, + apiDefs::ConfigType::ExternalTrial }; return premiumTypes.contains(getConfigType(serverConfigObject)); } @@ -177,7 +184,9 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject) QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject) { - if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) { + auto configType = apiUtils::getConfigType(serverConfigObject); + if (configType != apiDefs::ConfigType::AmneziaPremiumV2 && configType != apiDefs::ConfigType::AmneziaTrialV2 + && configType != apiDefs::ConfigType::ExternalPremium && configType != apiDefs::ConfigType::ExternalTrial) { return {}; } diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 83ca3284f..85872f59c 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -447,7 +447,7 @@ bool ApiConfigsController::importService() importSerivceFromAppStore(); return true; } - } else { + } else if (m_apiServicesModel->getSelectedServiceType() == serviceType::amneziaFree) { importServiceFromGateway(); return true; } diff --git a/client/ui/models/api/apiAccountInfoModel.cpp b/client/ui/models/api/apiAccountInfoModel.cpp index 0f3a8a4ed..65fc0083f 100644 --- a/client/ui/models/api/apiAccountInfoModel.cpp +++ b/client/ui/models/api/apiAccountInfoModel.cpp @@ -52,7 +52,9 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const } case IsComponentVisibleRole: { return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2 - || m_accountInfoData.configType == apiDefs::ConfigType::ExternalPremium; + || m_accountInfoData.configType == apiDefs::ConfigType::AmneziaTrialV2 + || m_accountInfoData.configType == apiDefs::ConfigType::ExternalPremium + || m_accountInfoData.configType == apiDefs::ConfigType::ExternalTrial; } case HasExpiredWorkerRole: { for (int i = 0; i < m_issuedConfigsInfo.size(); i++) { diff --git a/client/ui/models/api/apiServicesModel.cpp b/client/ui/models/api/apiServicesModel.cpp index 5ed9cca16..7d831f48c 100644 --- a/client/ui/models/api/apiServicesModel.cpp +++ b/client/ui/models/api/apiServicesModel.cpp @@ -41,6 +41,7 @@ namespace { constexpr char amneziaFree[] = "amnezia-free"; constexpr char amneziaPremium[] = "amnezia-premium"; + constexpr char amneziaTrial[] = "amnezia-trial"; } } @@ -69,7 +70,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const } case CardDescriptionRole: { auto speed = apiServiceData.serviceInfo.speed; - if (serviceType == serviceType::amneziaPremium) { + if (serviceType == serviceType::amneziaPremium || serviceType == serviceType::amneziaTrial) { return apiServiceData.serviceInfo.cardDescription.arg(speed); } else if (serviceType == serviceType::amneziaFree) { QString description = apiServiceData.serviceInfo.cardDescription; @@ -124,8 +125,10 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const case OrderRole: { if (serviceType == serviceType::amneziaPremium) { return 0; - } else if (serviceType == serviceType::amneziaFree) { + } else if (serviceType == serviceType::amneziaTrial) { return 1; + } else if (serviceType == serviceType::amneziaFree) { + return 2; } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml b/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml index c5e581af8..24308a126 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml @@ -1,226 +1,226 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Dialogs - -import PageEnum 1.0 -import Style 1.0 - -import "./" -import "../Controls2" -import "../Controls2/TextTypes" -import "../Config" -import "../Components" - -PageType { - id: root - - BackButtonType { - id: backButton - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin - - onFocusChanged: { - if (this.activeFocus) { - listView.positionViewAtBeginning() - } - } - } - - ListViewType { - id: listView - - anchors.top: backButton.bottom - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.left: parent.left - - header: ColumnLayout { - width: listView.width - - BaseHeaderType { - Layout.fillWidth: true - Layout.topMargin: 8 - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: 32 - - headerText: ApiServicesModel.getSelectedServiceData("name") - descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription") - } - } - - model: inputFields - spacing: 0 - - delegate: ColumnLayout { - width: listView.width - - LabelWithImageType { - Layout.fillWidth: true - Layout.margins: 16 - - imageSource: imagePath - leftText: lText - rightText: rText - - visible: isVisible - } - } - - footer: ColumnLayout { - width: listView.width - - spacing: 0 - - ParagraphTextType { - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - - onLinkActivated: function(link) { - Qt.openUrlExternally(link) - } - textFormat: Text.RichText - text: { - var text = ApiServicesModel.getSelectedServiceData("features") - return text.replace("%1", LanguageModel.getCurrentSiteUrl("free")).replace("/free", "") // todo link should come from gateway - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor - } - } - - ParagraphTextType { - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium" - - horizontalAlignment: Text.AlignHCenter - textFormat: Text.PlainText - color: AmneziaStyle.color.mutedGray - font.pixelSize: 12 - - text: qsTr("Charged to your Apple ID at confirmation. Renews automatically unless auto-renew is turned off at least 24 hours before period end. Manage in Apple ID settings.") - } - - BasicButtonType { - id: continueButton - - Layout.fillWidth: true - Layout.topMargin: 32 - Layout.bottomMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: ApiServicesModel.getSelectedServiceType() === "amnezia-premium" ? qsTr("Subscribe Now") : qsTr("Connect") - - clickedFunc: function() { - PageController.showBusyIndicator(true) - var result = ApiConfigsController.importService() - PageController.showBusyIndicator(false) - - if (!result) { - var endpoint = ApiServicesModel.getStoreEndpoint() - Qt.openUrlExternally(endpoint) - PageController.closePage() - PageController.closePage() - } - } - } - - ParagraphTextType { - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.bottomMargin: 32 - - visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium" - - horizontalAlignment: Text.AlignHCenter - textFormat: Text.RichText - color: AmneziaStyle.color.mutedGray - font.pixelSize: 12 - - text: { - var termsUrl = "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/" - var privacyUrl = LanguageModel.getCurrentSiteUrl("policy") - return qsTr("By continuing, you agree to the Terms of Use and Privacy Policy").arg(termsUrl).arg(privacyUrl) - } - - onLinkActivated: function(link) { - Qt.openUrlExternally(link) - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor - } - } - } - } - - property list inputFields: [ - region, - price, - timeLimit, - speed, - features - ] - - QtObject { - id: region - - readonly property string imagePath: "qrc:/images/controls/map-pin.svg" - readonly property string lText: qsTr("For the region") - readonly property string rText: ApiServicesModel.getSelectedServiceData("region") - property bool isVisible: true - } - - QtObject { - id: price - - readonly property string imagePath: "qrc:/images/controls/tag.svg" - readonly property string lText: qsTr("Price") - readonly property string rText: ApiServicesModel.getSelectedServiceData("price") - property bool isVisible: true - } - - QtObject { - id: timeLimit - - readonly property string imagePath: "qrc:/images/controls/history.svg" - readonly property string lText: qsTr("Work period") - readonly property string rText: ApiServicesModel.getSelectedServiceData("timeLimit") - property bool isVisible: rText !== "" - } - - QtObject { - id: speed - - readonly property string imagePath: "qrc:/images/controls/gauge.svg" - readonly property string lText: qsTr("Speed") - readonly property string rText: ApiServicesModel.getSelectedServiceData("speed") - property bool isVisible: true - } - - QtObject { - id: features - - readonly property string imagePath: "qrc:/images/controls/info.svg" - readonly property string lText: qsTr("Features") - readonly property string rText: "" - property bool isVisible: true - } -} +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + + onFocusChanged: { + if (this.activeFocus) { + listView.positionViewAtBeginning() + } + } + } + + ListViewType { + id: listView + + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + header: ColumnLayout { + width: listView.width + + BaseHeaderType { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 32 + + headerText: ApiServicesModel.getSelectedServiceData("name") + descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription") + } + } + + model: inputFields + spacing: 0 + + delegate: ColumnLayout { + width: listView.width + + LabelWithImageType { + Layout.fillWidth: true + Layout.margins: 16 + + imageSource: imagePath + leftText: lText + rightText: rText + + visible: isVisible + } + } + + footer: ColumnLayout { + width: listView.width + + spacing: 0 + + ParagraphTextType { + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + onLinkActivated: function(link) { + Qt.openUrlExternally(link) + } + textFormat: Text.RichText + text: { + var text = ApiServicesModel.getSelectedServiceData("features") + return text.replace("%1", LanguageModel.getCurrentSiteUrl("free")).replace("/free", "") // todo link should come from gateway + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium" + + horizontalAlignment: Text.AlignHCenter + textFormat: Text.PlainText + color: AmneziaStyle.color.mutedGray + font.pixelSize: 12 + + text: qsTr("Charged to your Apple ID at confirmation. Renews automatically unless auto-renew is turned off at least 24 hours before period end. Manage in Apple ID settings.") + } + + BasicButtonType { + id: continueButton + + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.bottomMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: ApiServicesModel.getSelectedServiceType() === "amnezia-premium" ? qsTr("Subscribe Now") : (ApiServicesModel.getSelectedServiceType() === "amnezia-trial" ? qsTr("Try Trial") : qsTr("Connect")) + + clickedFunc: function() { + PageController.showBusyIndicator(true) + var result = ApiConfigsController.importService() + PageController.showBusyIndicator(false) + + if (!result) { + var endpoint = ApiServicesModel.getStoreEndpoint() + Qt.openUrlExternally(endpoint) + PageController.closePage() + PageController.closePage() + } + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.bottomMargin: 32 + + visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium" + + horizontalAlignment: Text.AlignHCenter + textFormat: Text.RichText + color: AmneziaStyle.color.mutedGray + font.pixelSize: 12 + + text: { + var termsUrl = "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/" + var privacyUrl = LanguageModel.getCurrentSiteUrl("policy") + return qsTr("By continuing, you agree to the Terms of Use and Privacy Policy").arg(termsUrl).arg(privacyUrl) + } + + onLinkActivated: function(link) { + Qt.openUrlExternally(link) + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + } + } + } + } + + property list inputFields: [ + region, + price, + timeLimit, + speed, + features + ] + + QtObject { + id: region + + readonly property string imagePath: "qrc:/images/controls/map-pin.svg" + readonly property string lText: qsTr("For the region") + readonly property string rText: ApiServicesModel.getSelectedServiceData("region") + property bool isVisible: true + } + + QtObject { + id: price + + readonly property string imagePath: "qrc:/images/controls/tag.svg" + readonly property string lText: qsTr("Price") + readonly property string rText: ApiServicesModel.getSelectedServiceData("price") + property bool isVisible: true + } + + QtObject { + id: timeLimit + + readonly property string imagePath: "qrc:/images/controls/history.svg" + readonly property string lText: qsTr("Work period") + readonly property string rText: ApiServicesModel.getSelectedServiceData("timeLimit") + property bool isVisible: rText !== "" + } + + QtObject { + id: speed + + readonly property string imagePath: "qrc:/images/controls/gauge.svg" + readonly property string lText: qsTr("Speed") + readonly property string rText: ApiServicesModel.getSelectedServiceData("speed") + property bool isVisible: true + } + + QtObject { + id: features + + readonly property string imagePath: "qrc:/images/controls/info.svg" + readonly property string lText: qsTr("Features") + readonly property string rText: "" + property bool isVisible: true + } +}