Move providers paywall to picker (#764)

Paywall on module creation suggests that OpenVPN modules are a paid
feature.
This commit is contained in:
Davide 2024-10-28 19:55:42 +01:00 committed by GitHub
parent 639dee55ee
commit ecb0348b90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 55 additions and 12 deletions

View File

@ -151,12 +151,12 @@ extension IAPManager {
features.allSatisfy(eligibleFeatures.contains) features.allSatisfy(eligibleFeatures.contains)
} }
// public func isEligible(forProvider providerName: ProviderName) -> Bool { public func isEligible(forProvider providerId: ProviderID) -> Bool {
// guard providerName != .oeck else { if providerId == .oeck {
// return true return true
// } }
// return isEligible(forFeature: providerName.product) return isEligible(for: .providers)
// } }
public func isEligibleForFeedback() -> Bool { public func isEligibleForFeedback() -> Bool {
#if os(tvOS) #if os(tvOS)

View File

@ -473,6 +473,10 @@ public enum Strings {
/// Loading... /// Loading...
public static let loading = Strings.tr("Localizable", "providers.last_updated.loading", fallback: "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 { public enum Vpn {
/// No servers /// No servers
public static let noServers = Strings.tr("Localizable", "providers.vpn.no_servers", fallback: "No servers") public static let noServers = Strings.tr("Localizable", "providers.vpn.no_servers", fallback: "No servers")

View File

@ -175,7 +175,6 @@
"modules.general.sections.storage.footer" = "Profiles are stored to iCloud encrypted."; "modules.general.sections.storage.footer" = "Profiles are stored to iCloud encrypted.";
"modules.general.rows.icloud_sharing" = "Shared on iCloud"; "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.servers.add" = "Add address";
"modules.dns.search_domains.add" = "Add domain"; "modules.dns.search_domains.add" = "Add domain";
@ -187,7 +186,6 @@
"modules.ip.routes.excluded" = "Excluded routes"; "modules.ip.routes.excluded" = "Excluded routes";
"modules.ip.routes.add_family" = "Add %@"; "modules.ip.routes.add_family" = "Add %@";
"modules.on_demand.purchase" = "Add on-demand rules";
"modules.on_demand.policy" = "Policy"; "modules.on_demand.policy" = "Policy";
"modules.on_demand.policy.footer" = "Activate the VPN %@."; "modules.on_demand.policy.footer" = "Activate the VPN %@.";
"modules.on_demand.policy.footer.any" = "in any network"; "modules.on_demand.policy.footer.any" = "in any network";
@ -213,7 +211,6 @@
"modules.openvpn.randomize_endpoint" = "Randomize endpoint"; "modules.openvpn.randomize_endpoint" = "Randomize endpoint";
"modules.openvpn.randomize_hostname" = "Randomize hostname"; "modules.openvpn.randomize_hostname" = "Randomize hostname";
"modules.openvpn.credentials.interactive" = "Interactive"; "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.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.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."; "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.connection_status.on_demand_suffix" = " (on-demand)";
"ui.profile_context.connect_to" = "Connect to..."; "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 // MARK: - Alerts
"alerts.iap.restricted.title" = "Restricted"; "alerts.iap.restricted.title" = "Restricted";

View File

@ -38,6 +38,9 @@ struct OpenVPNView: View, ModuleDraftEditing {
private let isServerPushed: Bool private let isServerPushed: Bool
@State
private var paywallReason: PaywallReason?
init(serverConfiguration: OpenVPN.Configuration) { init(serverConfiguration: OpenVPN.Configuration) {
let module = OpenVPNModule.Builder(configurationBuilder: serverConfiguration.builder()) let module = OpenVPNModule.Builder(configurationBuilder: serverConfiguration.builder())
let editor = ProfileEditor(modules: [module]) let editor = ProfileEditor(modules: [module])
@ -56,6 +59,7 @@ struct OpenVPNView: View, ModuleDraftEditing {
var body: some View { var body: some View {
contentView contentView
.moduleView(editor: editor, draft: draft.wrappedValue, withName: !isServerPushed) .moduleView(editor: editor, draft: draft.wrappedValue, withName: !isServerPushed)
.modifier(PaywallModifier(reason: $paywallReason))
.navigationDestination(for: Subroute.self, destination: destination) .navigationDestination(for: Subroute.self, destination: destination)
} }
} }
@ -82,6 +86,7 @@ private extension OpenVPNView {
providerId: providerId, providerId: providerId,
selectedEntity: providerEntity, selectedEntity: providerEntity,
isRequired: true, isRequired: true,
paywallReason: $paywallReason,
entityDestination: Subroute.providerServer, entityDestination: Subroute.providerServer,
providerRows: { providerRows: {
moduleGroup(for: providerAccountRows) moduleGroup(for: providerAccountRows)

View File

@ -113,7 +113,7 @@ private extension ProfileCoordinator {
paywallReason = iapManager.paywallReason(forFeature: .routing) paywallReason = iapManager.paywallReason(forFeature: .routing)
case .openVPN, .wireGuard: case .openVPN, .wireGuard:
paywallReason = iapManager.paywallReason(forFeature: .providers) break
case .onDemand: case .onDemand:
break break

View File

@ -31,6 +31,9 @@ struct ProviderContentModifier<Entity, ProviderRows>: ViewModifier where Entity:
@EnvironmentObject @EnvironmentObject
private var providerManager: ProviderManager private var providerManager: ProviderManager
@EnvironmentObject
private var iapManager: IAPManager
let apis: [APIMapper] let apis: [APIMapper]
@Binding @Binding
@ -40,6 +43,9 @@ struct ProviderContentModifier<Entity, ProviderRows>: ViewModifier where Entity:
let isRequired: Bool let isRequired: Bool
@Binding
var paywallReason: PaywallReason?
@ViewBuilder @ViewBuilder
let providerRows: ProviderRows let providerRows: ProviderRows
@ -71,9 +77,14 @@ struct ProviderContentModifier<Entity, ProviderRows>: ViewModifier where Entity:
private extension ProviderContentModifier { private extension ProviderContentModifier {
#if os(iOS) #if os(iOS)
@ViewBuilder
var providerView: some View { var providerView: some View {
Group { Group {
providerPicker providerPicker
purchaseButton
}
.themeSection()
Group {
if providerId != nil { if providerId != nil {
providerRows providerRows
refreshButton { refreshButton {
@ -90,9 +101,13 @@ private extension ProviderContentModifier {
.themeSection(footer: lastUpdatedString) .themeSection(footer: lastUpdatedString)
} }
#else #else
@ViewBuilder
var providerView: some View { var providerView: some View {
Group { Section {
providerPicker providerPicker
purchaseButton
}
Section {
if providerId != nil { if providerId != nil {
providerRows providerRows
HStack { 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<Label>(label: () -> Label) -> some View where Label: View { func refreshButton<Label>(label: () -> Label) -> some View where Label: View {
Button(action: onRefreshInfrastructure, label: label) Button(action: onRefreshInfrastructure, label: label)
} }
@ -127,7 +151,7 @@ private extension ProviderContentModifier {
providerManager providerManager
.providers .providers
.filter { .filter {
$0.supports(Entity.Configuration.self) iapManager.isEligible(forProvider: $0.id) && $0.supports(Entity.Configuration.self)
} }
} }
@ -198,6 +222,7 @@ private extension ProviderContentModifier {
providerId: .constant(.hideme), providerId: .constant(.hideme),
entityType: VPNEntity<OpenVPN.Configuration>.self, entityType: VPNEntity<OpenVPN.Configuration>.self,
isRequired: false, isRequired: false,
paywallReason: .constant(nil),
providerRows: {}, providerRows: {},
onSelectProvider: { _, _, _ in } onSelectProvider: { _, _, _ in }
)) ))

View File

@ -40,6 +40,9 @@ struct VPNProviderContentModifier<Configuration, Destination, ProviderRows>: Vie
let isRequired: Bool let isRequired: Bool
@Binding
var paywallReason: PaywallReason?
let entityDestination: Destination let entityDestination: Destination
@ViewBuilder @ViewBuilder
@ -53,6 +56,7 @@ struct VPNProviderContentModifier<Configuration, Destination, ProviderRows>: Vie
providerId: $providerId, providerId: $providerId,
entityType: VPNEntity<Configuration>.self, entityType: VPNEntity<Configuration>.self,
isRequired: isRequired, isRequired: isRequired,
paywallReason: $paywallReason,
providerRows: { providerRows: {
providerEntityRow providerEntityRow
providerRows providerRows
@ -96,6 +100,7 @@ private extension VPNProviderContentModifier {
providerId: .constant(.hideme), providerId: .constant(.hideme),
selectedEntity: .constant(nil as VPNEntity<OpenVPN.Configuration>?), selectedEntity: .constant(nil as VPNEntity<OpenVPN.Configuration>?),
isRequired: false, isRequired: false,
paywallReason: .constant(nil),
entityDestination: "Destination", entityDestination: "Destination",
providerRows: { providerRows: {
Text("Other") Text("Other")