From ecb0348b908fc4d11144dfc65a55db152db94d3e Mon Sep 17 00:00:00 2001 From: Davide Date: Mon, 28 Oct 2024 19:55:42 +0100 Subject: [PATCH] Move providers paywall to picker (#764) Paywall on module creation suggests that OpenVPN modules are a paid feature. --- .../Sources/AppUI/IAP/IAPManager.swift | 12 ++++---- .../Sources/AppUI/L10n/SwiftGen+Strings.swift | 4 +++ .../Resources/en.lproj/Localizable.strings | 10 +++++-- .../AppUI/Views/Modules/OpenVPNView.swift | 5 ++++ .../Views/Profile/ProfileCoordinator.swift | 2 +- .../Provider/ProviderContentModifier.swift | 29 +++++++++++++++++-- .../Provider/VPNProviderContentModifier.swift | 5 ++++ 7 files changed, 55 insertions(+), 12 deletions(-) diff --git a/Passepartout/Library/Sources/AppUI/IAP/IAPManager.swift b/Passepartout/Library/Sources/AppUI/IAP/IAPManager.swift index c8bbbcc2..4aa2ce93 100644 --- a/Passepartout/Library/Sources/AppUI/IAP/IAPManager.swift +++ b/Passepartout/Library/Sources/AppUI/IAP/IAPManager.swift @@ -151,12 +151,12 @@ extension IAPManager { features.allSatisfy(eligibleFeatures.contains) } -// public func isEligible(forProvider providerName: ProviderName) -> Bool { -// guard providerName != .oeck else { -// return true -// } -// return isEligible(forFeature: providerName.product) -// } + public func isEligible(forProvider providerId: ProviderID) -> Bool { + if providerId == .oeck { + return true + } + return isEligible(for: .providers) + } public func isEligibleForFeedback() -> Bool { #if os(tvOS) diff --git a/Passepartout/Library/Sources/AppUI/L10n/SwiftGen+Strings.swift b/Passepartout/Library/Sources/AppUI/L10n/SwiftGen+Strings.swift index 27ba028e..ed47c9c6 100644 --- a/Passepartout/Library/Sources/AppUI/L10n/SwiftGen+Strings.swift +++ b/Passepartout/Library/Sources/AppUI/L10n/SwiftGen+Strings.swift @@ -473,6 +473,10 @@ public enum Strings { /// Loading... public static let loading = Strings.tr("Localizable", "providers.last_updated.loading", fallback: "Loading...") } + public enum Picker { + /// Add more providers + public static let purchase = Strings.tr("Localizable", "providers.picker.purchase", fallback: "Add more providers") + } public enum Vpn { /// No servers public static let noServers = Strings.tr("Localizable", "providers.vpn.no_servers", fallback: "No servers") diff --git a/Passepartout/Library/Sources/AppUI/Resources/en.lproj/Localizable.strings b/Passepartout/Library/Sources/AppUI/Resources/en.lproj/Localizable.strings index 67f4e6a8..70bac877 100644 --- a/Passepartout/Library/Sources/AppUI/Resources/en.lproj/Localizable.strings +++ b/Passepartout/Library/Sources/AppUI/Resources/en.lproj/Localizable.strings @@ -175,7 +175,6 @@ "modules.general.sections.storage.footer" = "Profiles are stored to iCloud encrypted."; "modules.general.rows.icloud_sharing" = "Shared on iCloud"; -"modules.general.rows.icloud_sharing.purchase" = "Share on iCloud"; "modules.dns.servers.add" = "Add address"; "modules.dns.search_domains.add" = "Add domain"; @@ -187,7 +186,6 @@ "modules.ip.routes.excluded" = "Excluded routes"; "modules.ip.routes.add_family" = "Add %@"; -"modules.on_demand.purchase" = "Add on-demand rules"; "modules.on_demand.policy" = "Policy"; "modules.on_demand.policy.footer" = "Activate the VPN %@."; "modules.on_demand.policy.footer.any" = "in any network"; @@ -213,7 +211,6 @@ "modules.openvpn.randomize_endpoint" = "Randomize endpoint"; "modules.openvpn.randomize_hostname" = "Randomize hostname"; "modules.openvpn.credentials.interactive" = "Interactive"; -"modules.openvpn.credentials.interactive.purchase" = "Log in interactively"; "modules.openvpn.credentials.interactive.footer" = "On-demand will be disabled."; "modules.openvpn.credentials.otp_method.approach.append" = "The OTP will be appended to the password."; "modules.openvpn.credentials.otp_method.approach.encode" = "The OTP will be encoded in Base64 with the password."; @@ -242,6 +239,13 @@ "ui.connection_status.on_demand_suffix" = " (on-demand)"; "ui.profile_context.connect_to" = "Connect to..."; +// MARK: - Paywalls + +"modules.general.rows.icloud_sharing.purchase" = "Share on iCloud"; +"modules.on_demand.purchase" = "Add on-demand rules"; +"modules.openvpn.credentials.interactive.purchase" = "Log in interactively"; +"providers.picker.purchase" = "Add more providers"; + // MARK: - Alerts "alerts.iap.restricted.title" = "Restricted"; diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift index d5e2826d..98a02228 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift @@ -38,6 +38,9 @@ struct OpenVPNView: View, ModuleDraftEditing { private let isServerPushed: Bool + @State + private var paywallReason: PaywallReason? + init(serverConfiguration: OpenVPN.Configuration) { let module = OpenVPNModule.Builder(configurationBuilder: serverConfiguration.builder()) let editor = ProfileEditor(modules: [module]) @@ -56,6 +59,7 @@ struct OpenVPNView: View, ModuleDraftEditing { var body: some View { contentView .moduleView(editor: editor, draft: draft.wrappedValue, withName: !isServerPushed) + .modifier(PaywallModifier(reason: $paywallReason)) .navigationDestination(for: Subroute.self, destination: destination) } } @@ -82,6 +86,7 @@ private extension OpenVPNView { providerId: providerId, selectedEntity: providerEntity, isRequired: true, + paywallReason: $paywallReason, entityDestination: Subroute.providerServer, providerRows: { moduleGroup(for: providerAccountRows) diff --git a/Passepartout/Library/Sources/AppUI/Views/Profile/ProfileCoordinator.swift b/Passepartout/Library/Sources/AppUI/Views/Profile/ProfileCoordinator.swift index ecb33b04..c4f47657 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Profile/ProfileCoordinator.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Profile/ProfileCoordinator.swift @@ -113,7 +113,7 @@ private extension ProfileCoordinator { paywallReason = iapManager.paywallReason(forFeature: .routing) case .openVPN, .wireGuard: - paywallReason = iapManager.paywallReason(forFeature: .providers) + break case .onDemand: break diff --git a/Passepartout/Library/Sources/AppUI/Views/Provider/ProviderContentModifier.swift b/Passepartout/Library/Sources/AppUI/Views/Provider/ProviderContentModifier.swift index 2a502952..b39d1ef6 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Provider/ProviderContentModifier.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Provider/ProviderContentModifier.swift @@ -31,6 +31,9 @@ struct ProviderContentModifier: ViewModifier where Entity: @EnvironmentObject private var providerManager: ProviderManager + @EnvironmentObject + private var iapManager: IAPManager + let apis: [APIMapper] @Binding @@ -40,6 +43,9 @@ struct ProviderContentModifier: ViewModifier where Entity: let isRequired: Bool + @Binding + var paywallReason: PaywallReason? + @ViewBuilder let providerRows: ProviderRows @@ -71,9 +77,14 @@ struct ProviderContentModifier: ViewModifier where Entity: private extension ProviderContentModifier { #if os(iOS) + @ViewBuilder var providerView: some View { Group { providerPicker + purchaseButton + } + .themeSection() + Group { if providerId != nil { providerRows refreshButton { @@ -90,9 +101,13 @@ private extension ProviderContentModifier { .themeSection(footer: lastUpdatedString) } #else + @ViewBuilder var providerView: some View { - Group { + Section { providerPicker + purchaseButton + } + Section { if providerId != nil { providerRows HStack { @@ -119,6 +134,15 @@ private extension ProviderContentModifier { ) } + @ViewBuilder + var purchaseButton: some View { + if let reason = iapManager.paywallReason(forFeature: .providers) { + Button(Strings.Providers.Picker.purchase) { + paywallReason = reason + } + } + } + func refreshButton