diff --git a/client/core/api/apiDefs.h b/client/core/api/apiDefs.h index 385ad9e29..ebcdced3b 100644 --- a/client/core/api/apiDefs.h +++ b/client/core/api/apiDefs.h @@ -61,6 +61,8 @@ namespace apiDefs constexpr QLatin1String endDate("end_date"); constexpr QLatin1String issuedConfigs("issued_configs"); constexpr QLatin1String subscriptionDescription("subscription_description"); + constexpr QLatin1String termsOfUseUrl("terms_of_use_url"); + constexpr QLatin1String privacyPolicyUrl("privacy_policy_url"); constexpr QLatin1String supportInfo("support_info"); constexpr QLatin1String email("email"); diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp index 22544f88e..d51103755 100644 --- a/client/core/api/apiUtils.cpp +++ b/client/core/api/apiUtils.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace { @@ -10,6 +11,18 @@ namespace constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?"); + QDateTime subscriptionEndUtcFromString(const QString &subscriptionEndDate) + { + if (subscriptionEndDate.isEmpty()) { + return {}; + } + QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs).toUTC(); + if (!endDate.isValid()) { + endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODate).toUTC(); + } + return endDate; + } + QString apiErrorMessageFromJson(const QJsonObject &jsonObj) { const QJsonValue value = jsonObj.value(QStringLiteral("message")); @@ -35,11 +48,11 @@ bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate) if (subscriptionEndDate.isEmpty()) { return false; } - const QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODate).toUTC(); + const QDateTime endDate = subscriptionEndUtcFromString(subscriptionEndDate); if (!endDate.isValid()) { return false; } - return endDate < QDateTime::currentDateTimeUtc(); + return endDate <= QDateTime::currentDateTimeUtc(); } bool apiUtils::isSubscriptionExpiringSoon(const QString &subscriptionEndDate, int withinDays) @@ -47,12 +60,12 @@ bool apiUtils::isSubscriptionExpiringSoon(const QString &subscriptionEndDate, in if (subscriptionEndDate.isEmpty()) { return false; } - const QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODate).toUTC(); + const QDateTime endDate = subscriptionEndUtcFromString(subscriptionEndDate); if (!endDate.isValid()) { return false; } const QDateTime nowUtc = QDateTime::currentDateTimeUtc(); - if (endDate < nowUtc) { + if (endDate <= nowUtc) { return false; } return endDate <= nowUtc.addDays(withinDays); diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 1263b89c9..f36659058 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -457,7 +457,7 @@ bool ApiConfigsController::importService() if (m_apiServicesModel->getSelectedServiceType() == serviceType::amneziaPremium) { if (isIosOrMacOsNe) { - return importSerivceFromAppStore(); + return importServiceFromAppStore(); } } else if (m_apiServicesModel->getSelectedServiceType() == serviceType::amneziaFree) { importFreeFromGateway(); @@ -466,7 +466,7 @@ bool ApiConfigsController::importService() return false; } -bool ApiConfigsController::importSerivceFromAppStore() +bool ApiConfigsController::importServiceFromAppStore() { #if defined(Q_OS_IOS) || defined(MACOS_NE) bool purchaseOk = false; @@ -533,7 +533,7 @@ bool ApiConfigsController::importSerivceFromAppStore() return true; } -bool ApiConfigsController::restoreSerivceFromAppStore() +bool ApiConfigsController::restoreServiceFromAppStore() { #if defined(Q_OS_IOS) || defined(MACOS_NE) const QString premiumServiceType = QStringLiteral("amnezia-premium"); diff --git a/client/ui/controllers/api/apiConfigsController.h b/client/ui/controllers/api/apiConfigsController.h index 318c33b19..390494f78 100644 --- a/client/ui/controllers/api/apiConfigsController.h +++ b/client/ui/controllers/api/apiConfigsController.h @@ -31,8 +31,8 @@ public slots: bool fillAvailableServices(); bool importService(); - bool importSerivceFromAppStore(); - bool restoreSerivceFromAppStore(); + bool importServiceFromAppStore(); + bool restoreServiceFromAppStore(); bool importFreeFromGateway(); bool importTrialFromGateway(const QString &email); bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, diff --git a/client/ui/models/api/apiServicesModel.cpp b/client/ui/models/api/apiServicesModel.cpp index 57367dc1e..6d16bdabe 100644 --- a/client/ui/models/api/apiServicesModel.cpp +++ b/client/ui/models/api/apiServicesModel.cpp @@ -127,6 +127,12 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const case EndDateRole: { return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy"); } + case TermsOfUseUrlRole: { + return apiServiceData.serviceInfo.termsOfUseUrl; + } + case PrivacyPolicyUrlRole: { + return apiServiceData.serviceInfo.privacyPolicyUrl; + } case OrderRole: { if (serviceType == serviceType::amneziaPremium) { return 0; @@ -260,6 +266,8 @@ QHash ApiServicesModel::roleNames() const roles[FeaturesRole] = "features"; roles[PriceRole] = "price"; roles[EndDateRole] = "endDate"; + roles[TermsOfUseUrlRole] = "termsOfUseUrl"; + roles[PrivacyPolicyUrlRole] = "privacyPolicyUrl"; roles[OrderRole] = "order"; return roles; @@ -285,6 +293,8 @@ ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJs serviceData.serviceInfo.cardDescription = serviceDescription.value(configKey::cardDescription).toString(); serviceData.serviceInfo.description = serviceDescription.value(configKey::description).toString(); serviceData.serviceInfo.features = serviceDescription.value(configKey::features).toString(); + serviceData.serviceInfo.termsOfUseUrl = serviceDescription.value(apiDefs::key::termsOfUseUrl).toString(); + serviceData.serviceInfo.privacyPolicyUrl = serviceDescription.value(apiDefs::key::privacyPolicyUrl).toString(); serviceData.subscriptionPlansJson = serviceDescription.value(configKey::subscriptionPlans).toArray(); serviceData.benefits = serviceDescription.value(configKey::benefits).toArray(); diff --git a/client/ui/models/api/apiServicesModel.h b/client/ui/models/api/apiServicesModel.h index 7d61fbf14..b85f1e931 100644 --- a/client/ui/models/api/apiServicesModel.h +++ b/client/ui/models/api/apiServicesModel.h @@ -23,6 +23,9 @@ public: QString features; QString cardDescription; + QString termsOfUseUrl; + QString privacyPolicyUrl; + QJsonObject object; }; @@ -60,6 +63,8 @@ public: FeaturesRole, PriceRole, EndDateRole, + TermsOfUseUrlRole, + PrivacyPolicyUrlRole, OrderRole }; diff --git a/client/ui/qml/Modules/Style/AmneziaStyle.qml b/client/ui/qml/Modules/Style/AmneziaStyle.qml index d6b3f9081..6c81dc9af 100644 --- a/client/ui/qml/Modules/Style/AmneziaStyle.qml +++ b/client/ui/qml/Modules/Style/AmneziaStyle.qml @@ -12,7 +12,7 @@ QtObject { readonly property color slateGray: '#2C2D30' readonly property color onyxBlack: '#1C1D21' readonly property color midnightBlack: '#0E0E11' - readonly property color goldenApricot: '#FBB26A' + readonly property color goldenApricot: goldenApricotString readonly property color benefitsPanelBackground: '#1C1C1E' readonly property color softViolet: '#A87BE2' readonly property color burntOrange: '#A85809' @@ -21,6 +21,8 @@ QtObject { readonly property color deepBrown: '#402102' readonly property color vibrantRed: '#EB5757' readonly property color darkCharcoal: '#261E1A' + readonly property color pearlGray: '#EAEAEC' + readonly property color sheerWhite: Qt.rgba(1, 1, 1, 0.12) readonly property color translucentWhite: Qt.rgba(1, 1, 1, 0.08) readonly property color barelyTranslucentWhite: Qt.rgba(1, 1, 1, 0.05) @@ -28,9 +30,10 @@ QtObject { readonly property color softGoldenApricot: Qt.rgba(251/255, 178/255, 106/255, 0.3) readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8) readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65) - readonly property color pearlGray: '#EAEAEC' readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26) readonly property color translucentSlateGray: Qt.rgba(85/255, 86/255, 92/255, 0.13) readonly property color translucentOnyxBlack: Qt.rgba(28/255, 29/255, 33/255, 0.13) + + readonly property string goldenApricotString: '#FBB26A' } } diff --git a/client/ui/qml/Pages2/PageSetupWizardApiFreeInfo.qml b/client/ui/qml/Pages2/PageSetupWizardApiFreeInfo.qml index b6292c6a0..c778dd933 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiFreeInfo.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiFreeInfo.qml @@ -124,10 +124,10 @@ PageType { font.pixelSize: 12 text: { - var termsUrl = LanguageModel.getCurrentSiteUrl() - var privacyUrl = LanguageModel.getCurrentSiteUrl("policy") return qsTr("By continuing, you agree to the Terms of Use and Privacy Policy") - .arg(termsUrl).arg(privacyUrl).arg("#FBB26A") + .arg(String(ApiServicesModel.getSelectedServiceData("termsOfUseUrl"))) + .arg(String(ApiServicesModel.getSelectedServiceData("privacyPolicyUrl"))) + .arg(AmneziaStyle.color.goldenApricotString) } onLinkActivated: function(link) { @@ -158,7 +158,7 @@ PageType { 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).arg("#FBB26A") + .arg(termsUrl).arg(privacyUrl).arg(AmneziaStyle.color.goldenApricotString) } onLinkActivated: function(link) { diff --git a/client/ui/qml/Pages2/PageSetupWizardApiPremiumInfo.qml b/client/ui/qml/Pages2/PageSetupWizardApiPremiumInfo.qml index 165a67b5b..42ed36292 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiPremiumInfo.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiPremiumInfo.qml @@ -114,50 +114,47 @@ PageType { benefitsModel: ApiBenefitsModel } - ParagraphTextType { + ColumnLayout { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 - Layout.bottomMargin: 16 + Layout.bottomMargin: 24 + visible: Qt.platform.os === "ios" || IsMacOsNeBuild + spacing: 16 - visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) + ParagraphTextType { + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + textFormat: Text.PlainText + color: AmneziaStyle.color.mutedGray + font.pixelSize: 12 - 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.") - } - - ParagraphTextType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.bottomMargin: 16 - - visible: !(Qt.platform.os === "ios" || IsMacOsNeBuild) - - horizontalAlignment: Text.AlignHCenter - textFormat: Text.RichText - color: AmneziaStyle.color.mutedGray - font.pixelSize: 12 - - text: { - var termsUrl = LanguageModel.getCurrentSiteUrl() - var privacyUrl = LanguageModel.getCurrentSiteUrl("policy") - return qsTr("By continuing, you agree to the Terms of Use and Privacy Policy") - .arg(termsUrl).arg(privacyUrl).arg("#FBB26A") + 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.") } - onLinkActivated: function(link) { - Qt.openUrlExternally(link) - } + ParagraphTextType { + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + textFormat: Text.RichText + color: AmneziaStyle.color.mutedGray + font.pixelSize: 12 - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + 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).arg(AmneziaStyle.color.goldenApricotString) + } + + onLinkActivated: function(link) { + Qt.openUrlExternally(link) + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + } } } @@ -167,7 +164,7 @@ PageType { Layout.rightMargin: 16 Layout.bottomMargin: 24 - visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) + visible: !(Qt.platform.os === "ios" || IsMacOsNeBuild) horizontalAlignment: Text.AlignHCenter textFormat: Text.RichText @@ -175,10 +172,10 @@ PageType { 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).arg("#FBB26A") + .arg(String(ApiServicesModel.getSelectedServiceData("termsOfUseUrl"))) + .arg(String(ApiServicesModel.getSelectedServiceData("privacyPolicyUrl"))) + .arg(AmneziaStyle.color.goldenApricotString) } onLinkActivated: function(link) { diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 160177b6c..175ac377f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -361,7 +361,7 @@ PageType { property bool isVisible: Qt.platform.os === "ios" || IsMacOsNeBuild property var handler: function() { PageController.showBusyIndicator(true) - ApiConfigsController.restoreSerivceFromAppStore() + ApiConfigsController.restoreServiceFromAppStore() PageController.showBusyIndicator(false) } }