mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-20 02:00:55 +07:00
feat: new services description (#2412)
* feat: iap for apple now use storekit2 * fix: fixed error 101 on connection event * feat: enhance StoreKit2Helper to handle entitlements and improve restore service from App Store functionality * chore: add isInAppPurchase and isTestPurchase in primary config * refactor: use end_date from primary config for renew ui * fix: hide renew button for free * fix: hide renew button for appstore purchases * feat: add new premium info page * feat: add new free info page * chore: minor fixes * refactor: move plan and benefits into separate models * fix: fixed expired status when configs without an end date * feat: add trial api support * chore: add api message parsing for 422 error * feat: move privacy policy and term of use to gateway * feat: add iap support for new premium info page * chore: minor fixes * chore: minor fix * chore: minor fixes * feat: additional parsing for storekit subscription plans * chore: minor codestyle fixes * chore: simplify benefits * chore: hide extend buttons on external premium * feat: add trial error processing * fix: remove wrong check from tiral handler * chore: cleanup --------- Co-authored-by: spectrum <yyy@amnezia.org>
This commit is contained in:
@@ -20,9 +20,14 @@ PageType {
|
||||
property var processedServer
|
||||
property bool subscriptionExpired: false
|
||||
property bool subscriptionExpiringSoon: false
|
||||
property bool isSubscriptionRenewalAvailable: false
|
||||
property bool isInAppPurchase: false
|
||||
|
||||
function updateSubscriptionState() {
|
||||
root.subscriptionExpired = ServersModel.getProcessedServerData("isSubscriptionExpired")
|
||||
root.subscriptionExpiringSoon = ServersModel.getProcessedServerData("isSubscriptionExpiringSoon")
|
||||
root.isSubscriptionRenewalAvailable = ApiAccountInfoModel.data("isSubscriptionRenewalAvailable")
|
||||
root.isInAppPurchase = ApiAccountInfoModel.data("isInAppPurchase")
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
@@ -38,6 +43,14 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ApiAccountInfoModel
|
||||
|
||||
function onModelReset() {
|
||||
root.updateSubscriptionState()
|
||||
}
|
||||
}
|
||||
|
||||
SortFilterProxyModel {
|
||||
id: proxyServersModel
|
||||
objectName: "proxyServersModel"
|
||||
@@ -87,7 +100,7 @@ PageType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 4
|
||||
Layout.bottomMargin: root.subscriptionExpired || root.subscriptionExpiringSoon ? 0 : 4
|
||||
|
||||
actionButtonImage: "qrc:/images/controls/settings.svg"
|
||||
|
||||
@@ -105,26 +118,27 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
CaptionTextType {
|
||||
ParagraphTextType {
|
||||
visible: root.subscriptionExpired || root.subscriptionExpiringSoon
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 4
|
||||
Layout.topMargin: 12
|
||||
|
||||
text: root.subscriptionExpired ? qsTr("Subscription expired") : qsTr("Subscription expiring soon")
|
||||
color: root.subscriptionExpired ? AmneziaStyle.color.vibrantRed : AmneziaStyle.color.goldenApricot
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
visible: root.subscriptionExpired || root.subscriptionExpiringSoon
|
||||
visible: (root.subscriptionExpired || root.subscriptionExpiringSoon)
|
||||
&& root.isSubscriptionRenewalAvailable && !root.isInAppPurchase
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
Layout.bottomMargin: 4
|
||||
Layout.topMargin: 28
|
||||
Layout.bottomMargin: 0
|
||||
|
||||
defaultColor: AmneziaStyle.color.paleGray
|
||||
hoveredColor: AmneziaStyle.color.lightGray
|
||||
@@ -138,11 +152,11 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
CaptionTextType {
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: (root.subscriptionExpired || root.subscriptionExpiringSoon) ? 8 : 4
|
||||
Layout.topMargin: (root.subscriptionExpired || root.subscriptionExpiringSoon) ? 12 : 4
|
||||
Layout.bottomMargin: 8
|
||||
|
||||
text: qsTr("Location for connection")
|
||||
|
||||
@@ -2,7 +2,6 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
@@ -55,10 +54,14 @@ PageType {
|
||||
|
||||
property bool isSubscriptionExpired: false
|
||||
property bool isSubscriptionExpiringSoon: false
|
||||
property bool isSubscriptionRenewalAvailable: false
|
||||
property bool isInAppPurchase: false
|
||||
|
||||
function updateSubscriptionState() {
|
||||
root.isSubscriptionExpired = ApiAccountInfoModel.data("isSubscriptionExpired")
|
||||
root.isSubscriptionExpiringSoon = ApiAccountInfoModel.data("isSubscriptionExpiringSoon")
|
||||
root.isSubscriptionRenewalAvailable = ApiAccountInfoModel.data("isSubscriptionRenewalAvailable")
|
||||
root.isInAppPurchase = ApiAccountInfoModel.data("isInAppPurchase")
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
@@ -124,7 +127,7 @@ PageType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 10
|
||||
Layout.bottomMargin: root.isSubscriptionExpired || root.isSubscriptionExpiringSoon ? 0 : 10
|
||||
|
||||
actionButtonImage: "qrc:/images/controls/edit-3.svg"
|
||||
|
||||
@@ -135,13 +138,13 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
ParagraphTextType {
|
||||
visible: root.isSubscriptionExpired || root.isSubscriptionExpiringSoon
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 4
|
||||
Layout.topMargin: 12
|
||||
|
||||
text: root.isSubscriptionExpired
|
||||
? qsTr("Subscription expired")
|
||||
@@ -150,10 +153,6 @@ PageType {
|
||||
color: root.isSubscriptionExpired
|
||||
? AmneziaStyle.color.vibrantRed
|
||||
: AmneziaStyle.color.goldenApricot
|
||||
|
||||
font.pixelSize: 14
|
||||
font.weight: Font.Medium
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
@@ -170,7 +169,8 @@ PageType {
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
visible: root.isSubscriptionExpired || root.isSubscriptionExpiringSoon
|
||||
visible: (root.isSubscriptionExpired || root.isSubscriptionExpiringSoon)
|
||||
&& root.isSubscriptionRenewalAvailable && !root.isInAppPurchase
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
@@ -226,52 +226,33 @@ PageType {
|
||||
|
||||
readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible")
|
||||
|
||||
Item {
|
||||
BasicButtonType {
|
||||
visible: !root.isSubscriptionExpired && !root.isSubscriptionExpiringSoon
|
||||
&& root.isSubscriptionRenewalAvailable && !root.isInAppPurchase
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: renewRow.implicitHeight + 32
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: ApiSettingsController.getRenewalLink()
|
||||
}
|
||||
implicitHeight: 25
|
||||
|
||||
Row {
|
||||
id: renewRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 12
|
||||
defaultColor: AmneziaStyle.color.transparent
|
||||
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||
pressedColor: AmneziaStyle.color.sheerWhite
|
||||
textColor: AmneziaStyle.color.goldenApricot
|
||||
leftImageSource: "qrc:/images/controls/refresh-cw.svg"
|
||||
leftImageColor: AmneziaStyle.color.goldenApricot
|
||||
|
||||
Item {
|
||||
width: renewIcon.implicitWidth
|
||||
height: renewIcon.implicitHeight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("Renew subscription")
|
||||
|
||||
Image {
|
||||
id: renewIcon
|
||||
source: "qrc:/images/controls/refresh-cw.svg"
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: renewIcon
|
||||
source: renewIcon
|
||||
color: AmneziaStyle.color.goldenApricot
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: qsTr("Renew subscription")
|
||||
color: AmneziaStyle.color.goldenApricot
|
||||
font.pixelSize: 18
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
clickedFunc: function() {
|
||||
ApiSettingsController.getRenewalLink()
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {
|
||||
visible: !root.isSubscriptionExpired && !root.isSubscriptionExpiringSoon
|
||||
&& root.isSubscriptionRenewalAvailable && !root.isInAppPurchase
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
import "../Components"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property string freeHeaderName: ""
|
||||
property string freeHeaderDescription: ""
|
||||
|
||||
function syncFromModel() {
|
||||
root.freeHeaderName = String(ApiServicesModel.getSelectedServiceData("name"))
|
||||
root.freeHeaderDescription = String(ApiServicesModel.getSelectedServiceData("serviceDescription"))
|
||||
}
|
||||
|
||||
Component.onCompleted: syncFromModel()
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (activeFocus) {
|
||||
flick.contentY = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: flick
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: continueButton.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
contentHeight: scrollColumn.implicitHeight + 24
|
||||
|
||||
ColumnLayout {
|
||||
id: scrollColumn
|
||||
|
||||
width: flick.width
|
||||
spacing: 0
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
headerText: root.freeHeaderName
|
||||
descriptionText: root.freeHeaderDescription
|
||||
}
|
||||
|
||||
LabelTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 12
|
||||
|
||||
text: qsTr("Free features")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
font.pixelSize: 13
|
||||
}
|
||||
|
||||
BenefitsPanel {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
benefitsModel: ApiBenefitsModel
|
||||
}
|
||||
|
||||
TermsAndPrivacyText {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
visible: !(Qt.platform.os === "ios" || IsMacOsNeBuild)
|
||||
|
||||
termsUrl: String(ApiServicesModel.getSelectedServiceData("termsOfUseUrl"))
|
||||
privacyUrl: String(ApiServicesModel.getSelectedServiceData("privacyPolicyUrl"))
|
||||
}
|
||||
|
||||
TermsAndPrivacyText {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild)
|
||||
|
||||
termsUrl: "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
|
||||
privacyUrl: LanguageModel.getCurrentSiteUrl("policy")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: continueButton
|
||||
|
||||
z: 2
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.bottomMargin: 16 + SettingsController.safeAreaBottomMargin
|
||||
|
||||
text: qsTr("Continue")
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
import "../Components"
|
||||
import PageEnum 1.0
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property int selectedPlanIndex: 0
|
||||
property string premiumHeaderName: ""
|
||||
property string premiumHeaderDescription: ""
|
||||
|
||||
readonly property var currentPlan: ApiSubscriptionPlansModel.planAt(selectedPlanIndex)
|
||||
|
||||
function syncFromModel() {
|
||||
root.selectedPlanIndex = ApiSubscriptionPlansModel.recommendedRowIndex()
|
||||
|
||||
root.premiumHeaderName = String(ApiServicesModel.getSelectedServiceData("name"))
|
||||
root.premiumHeaderDescription = String(ApiServicesModel.getSelectedServiceData("serviceDescription"))
|
||||
}
|
||||
|
||||
Component.onCompleted: syncFromModel()
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (activeFocus) {
|
||||
flick.contentY = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: flick
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: continueButton.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
contentHeight: scrollColumn.implicitHeight + 24
|
||||
|
||||
ColumnLayout {
|
||||
id: scrollColumn
|
||||
|
||||
width: flick.width
|
||||
spacing: 0
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
headerText: root.premiumHeaderName
|
||||
descriptionText: root.premiumHeaderDescription
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ApiSubscriptionPlansModel
|
||||
|
||||
delegate: SubscriptionPlanCard {
|
||||
required property int index
|
||||
required property var model
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: index === ApiSubscriptionPlansModel.rowCount() - 1 ? 24 : 12
|
||||
|
||||
selected: root.selectedPlanIndex === index
|
||||
billingPeriod: String(model.billingPeriod)
|
||||
priceLabel: String(model.priceLabel)
|
||||
subtitle: String(model.subtitle)
|
||||
showRecommendedBadge: !!model.recommended
|
||||
recommendedText: qsTr("Recommended")
|
||||
|
||||
onSelectRequested: root.selectedPlanIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
LabelTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 12
|
||||
|
||||
text: qsTr("Premium features")
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
font.pixelSize: 13
|
||||
}
|
||||
|
||||
BenefitsPanel {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
benefitsModel: ApiBenefitsModel
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 24
|
||||
visible: Qt.platform.os === "ios" || IsMacOsNeBuild
|
||||
spacing: 16
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
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.")
|
||||
}
|
||||
|
||||
TermsAndPrivacyText {
|
||||
termsUrl: "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
|
||||
privacyUrl: LanguageModel.getCurrentSiteUrl("policy")
|
||||
}
|
||||
}
|
||||
|
||||
TermsAndPrivacyText {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
visible: !(Qt.platform.os === "ios" || IsMacOsNeBuild)
|
||||
|
||||
termsUrl: String(ApiServicesModel.getSelectedServiceData("termsOfUseUrl"))
|
||||
privacyUrl: String(ApiServicesModel.getSelectedServiceData("privacyPolicyUrl"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: continueButton
|
||||
|
||||
z: 2
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.bottomMargin: 16 + SettingsController.safeAreaBottomMargin
|
||||
|
||||
text: {
|
||||
var plan = root.currentPlan
|
||||
if (!plan) {
|
||||
return qsTr("Continue")
|
||||
}
|
||||
return qsTr("Subscribe — %1 for %2").arg(String(plan.billingPeriod)).arg(String(plan.priceLabel))
|
||||
}
|
||||
|
||||
clickedFunc: function() {
|
||||
var plan = root.currentPlan
|
||||
if (!plan) {
|
||||
return
|
||||
}
|
||||
if (plan.isTrial) {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardApiTrialEmail)
|
||||
return
|
||||
}
|
||||
if (Qt.platform.os === "ios" || IsMacOsNeBuild) {
|
||||
PageController.showBusyIndicator(true)
|
||||
var storeId = plan.storeProductId !== undefined ? String(plan.storeProductId) : ""
|
||||
ApiConfigsController.importPremiumFromAppStore(storeId)
|
||||
PageController.showBusyIndicator(false)
|
||||
return
|
||||
}
|
||||
if (plan.checkoutUrl) {
|
||||
Qt.openUrlExternally(plan.checkoutUrl)
|
||||
PageController.closePage()
|
||||
PageController.closePage()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
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 <a href=\"%1\" style=\"color: #FBB26A;\">Terms of Use</a> and <a href=\"%2\" style=\"color: #FBB26A;\">Privacy Policy</a>").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<QtObject> 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
|
||||
}
|
||||
}
|
||||
@@ -84,12 +84,19 @@ PageType {
|
||||
bodyText: cardDescription
|
||||
footerText: price
|
||||
|
||||
showRecommendedBadge: showRecommended && isServiceAvailable
|
||||
recommendedText: qsTr("Recommended")
|
||||
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
onClicked: {
|
||||
if (isServiceAvailable) {
|
||||
ApiServicesModel.setServiceIndex(proxyApiServicesModel.mapToSource(index))
|
||||
PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
|
||||
if (ApiServicesModel.getSelectedServiceType() === "amnezia-premium") {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardApiPremiumInfo)
|
||||
} else {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardApiFreeInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
import "../Components"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
property string trialEmailErrorMessage: ""
|
||||
|
||||
Connections {
|
||||
target: ApiConfigsController
|
||||
|
||||
function onTrialEmailError(message) {
|
||||
root.trialEmailErrorMessage = message
|
||||
emailField.errorText = message
|
||||
}
|
||||
}
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (activeFocus) {
|
||||
flick.contentY = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: flick
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: continueButton.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
contentHeight: scrollColumn.implicitHeight + 24
|
||||
|
||||
ColumnLayout {
|
||||
id: scrollColumn
|
||||
|
||||
width: flick.width
|
||||
spacing: 0
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
headerText: qsTr("Create an account")
|
||||
descriptionText: qsTr("To manage your subscription")
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: emailField
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
headerText: qsTr("Email")
|
||||
textField.placeholderText: qsTr("Email")
|
||||
textField.inputMethodHints: Qt.ImhEmailCharactersOnly
|
||||
|
||||
Connections {
|
||||
target: emailField.textField
|
||||
|
||||
function onTextChanged() {
|
||||
if (root.trialEmailErrorMessage !== "") {
|
||||
root.trialEmailErrorMessage = ""
|
||||
emailField.errorText = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
font.pixelSize: 12
|
||||
text: qsTr("We will create an account for your trial subscription and send important subscription updates to this email.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: continueButton
|
||||
|
||||
z: 2
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.bottomMargin: 16 + SettingsController.safeAreaBottomMargin
|
||||
|
||||
text: qsTr("Continue")
|
||||
|
||||
clickedFunc: function() {
|
||||
root.trialEmailErrorMessage = ""
|
||||
emailField.errorText = ""
|
||||
|
||||
var raw = emailField.textField.text.trim()
|
||||
if (raw.length === 0 || raw.indexOf("@") < 0) {
|
||||
PageController.showNotificationMessage(qsTr("Enter a valid email address"))
|
||||
return
|
||||
}
|
||||
PageController.showBusyIndicator(true)
|
||||
var ok = ApiConfigsController.importTrialFromGateway(raw)
|
||||
PageController.showBusyIndicator(false)
|
||||
if (ok) {
|
||||
PageController.closePage()
|
||||
PageController.closePage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,6 +222,9 @@ PageType {
|
||||
headerText: title
|
||||
bodyText: description
|
||||
|
||||
showRecommendedBadge: featuredAmneziaConnection
|
||||
recommendedText: featuredAmneziaConnection ? qsTr("Recommended") : ""
|
||||
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
leftImageSource: imageSource
|
||||
|
||||
@@ -275,8 +278,9 @@ PageType {
|
||||
id: amneziaVpn
|
||||
|
||||
property string title: qsTr("VPN by Amnezia")
|
||||
property string description: qsTr("Connect to classic paid and free VPN services from Amnezia")
|
||||
property string description: qsTr("The easiest way to connect to VPN")
|
||||
property string imageSource: "qrc:/images/controls/amnezia.svg"
|
||||
property bool featuredAmneziaConnection: true
|
||||
property bool isVisible: true
|
||||
property var handler: function() {
|
||||
PageController.showBusyIndicator(true)
|
||||
@@ -291,6 +295,7 @@ PageType {
|
||||
QtObject {
|
||||
id: selfHostVpn
|
||||
|
||||
property bool featuredAmneziaConnection: false
|
||||
property string title: qsTr("Self-hosted VPN")
|
||||
property string description: qsTr("Configure Amnezia VPN on your own server")
|
||||
property string imageSource: "qrc:/images/controls/server.svg"
|
||||
@@ -303,6 +308,7 @@ PageType {
|
||||
QtObject {
|
||||
id: backupRestore
|
||||
|
||||
property bool featuredAmneziaConnection: false
|
||||
property string title: qsTr("Restore from backup")
|
||||
property string description: qsTr("")
|
||||
property string imageSource: "qrc:/images/controls/archive-restore.svg"
|
||||
@@ -321,6 +327,7 @@ PageType {
|
||||
QtObject {
|
||||
id: fileOpen
|
||||
|
||||
property bool featuredAmneziaConnection: false
|
||||
property string title: qsTr("File with connection settings")
|
||||
property string description: qsTr("")
|
||||
property string imageSource: "qrc:/images/controls/folder-search-2.svg"
|
||||
@@ -340,6 +347,7 @@ PageType {
|
||||
QtObject {
|
||||
id: qrScan
|
||||
|
||||
property bool featuredAmneziaConnection: false
|
||||
property string title: qsTr("QR code")
|
||||
property string description: qsTr("")
|
||||
property string imageSource: "qrc:/images/controls/scan-line.svg"
|
||||
@@ -355,13 +363,14 @@ PageType {
|
||||
QtObject {
|
||||
id: restorePurchases
|
||||
|
||||
property bool featuredAmneziaConnection: false
|
||||
property string title: qsTr("Restore purchases")
|
||||
property string description: qsTr("")
|
||||
property string imageSource: "qrc:/images/controls/refresh-cw.svg"
|
||||
property bool isVisible: Qt.platform.os === "ios" || IsMacOsNeBuild
|
||||
property var handler: function() {
|
||||
PageController.showBusyIndicator(true)
|
||||
ApiConfigsController.restoreSerivceFromAppStore()
|
||||
ApiConfigsController.restoreServiceFromAppStore()
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
@@ -369,6 +378,7 @@ PageType {
|
||||
QtObject {
|
||||
id: siteLink
|
||||
|
||||
property bool featuredAmneziaConnection: false
|
||||
property string title: qsTr("I have nothing")
|
||||
property string description: qsTr("")
|
||||
property string imageSource: "qrc:/images/controls/help-circle.svg"
|
||||
|
||||
@@ -225,9 +225,13 @@ PageType {
|
||||
Connections {
|
||||
target: ApiConfigsController
|
||||
|
||||
function onInstallServerFromApiFinished(message) {
|
||||
function onInstallServerFromApiFinished(message, preferredDefaultIndex) {
|
||||
if (!ConnectionController.isConnected) {
|
||||
ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1);
|
||||
if (preferredDefaultIndex !== undefined && preferredDefaultIndex >= 0) {
|
||||
ServersModel.setDefaultServerIndex(preferredDefaultIndex)
|
||||
} else {
|
||||
ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1)
|
||||
}
|
||||
ServersModel.processedIndex = ServersModel.defaultIndex
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user