Merge branch 'enable-public-beta'

This commit is contained in:
Davide De Rosa 2021-01-08 15:08:45 +01:00
commit f183cda657
10 changed files with 73 additions and 63 deletions

View File

@ -105,12 +105,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
fatalError("No window.rootViewController?") fatalError("No window.rootViewController?")
} }
let topmost = root.presentedViewController ?? root let topmost = root.presentedViewController ?? root
if TransientStore.shared.service.hasReachedMaximumNumberOfHosts {
guard ProductManager.shared.isEligible(forFeature: .unlimitedHosts) else {
topmost.presentPurchaseScreen(forProduct: .unlimitedHosts)
return false
}
}
return tryParseURL(url, passphrase: nil, target: topmost) return tryParseURL(url, passphrase: nil, target: topmost)
} }
@ -168,7 +162,7 @@ extension UISplitViewController {
extension AppDelegate { extension AppDelegate {
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard ProductManager.shared.isEligible(forFeature: .siriShortcuts) else { guard (try? ProductManager.shared.isEligible(forFeature: .siriShortcuts)) ?? false else {
return false return false
} }
guard let interaction = userActivity.interaction else { guard let interaction = userActivity.interaction else {

View File

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Drop hosts restriction in free version ("Unlimited hosts").
## 1.14.0 (2021-01-07) ## 1.14.0 (2021-01-07)
### Added ### Added

View File

@ -32,13 +32,10 @@ extension AppConstants {
} }
struct InApp { struct InApp {
// static var isBetaFullVersion: Bool { static var isBetaFullVersion: Bool {
// return ProcessInfo.processInfo.environment["FULL_VERSION"] != nil return ProcessInfo.processInfo.environment["FULL_VERSION"] != nil
// } }
static let isBetaFullVersion = true
static let lastFullVersionBuild = 2016 static let lastFullVersionBuild = 2016
static let limitedNumberOfHosts = 2
} }
} }

View File

@ -75,6 +75,12 @@ extension UIViewController {
present(nav, animated: true, completion: nil) present(nav, animated: true, completion: nil)
} }
func presentBetaFeatureUnavailable(_ title: String) {
let alert = UIAlertController.asAlert(title, "The requested feature is unavailable in beta.")
alert.addCancelAction("OK")
present(alert, animated: true, completion: nil)
}
} }
func visitURL(_ url: URL) { func visitURL(_ url: URL) {

View File

@ -46,7 +46,7 @@ extension ProductManager {
// review features and potentially revert them if they were used (Siri is handled in AppDelegate) // review features and potentially revert them if they were used (Siri is handled in AppDelegate)
log.debug("Checking 'Trusted networks'") log.debug("Checking 'Trusted networks'")
if !isEligible(forFeature: .trustedNetworks) { if !((try? isEligible(forFeature: .trustedNetworks)) ?? false) {
// reset trusted networks for ALL profiles (must load first) // reset trusted networks for ALL profiles (must load first)
for key in service.allProfileKeys() { for key in service.allProfileKeys() {
@ -71,24 +71,12 @@ extension ProductManager {
} }
} }
log.debug("Checking 'Unlimited hosts'")
if !isEligible(forFeature: .unlimitedHosts) {
let ids = service.hostIds()
if ids.count > AppConstants.InApp.limitedNumberOfHosts {
for id in ids {
service.removeProfile(ProfileKey(.host, id))
}
log.debug("\tRefunded")
anyRefund = true
}
}
log.debug("Checking providers") log.debug("Checking providers")
for name in service.providerNames() { for name in service.providerNames() {
guard let metadata = InfrastructureFactory.shared.metadata(forName: name) else { guard let metadata = InfrastructureFactory.shared.metadata(forName: name) else {
continue continue
} }
if !isEligible(forProvider: metadata) { if !((try? isEligible(forProvider: metadata)) ?? false) {
service.removeProfile(ProfileKey(name)) service.removeProfile(ProfileKey(name))
log.debug("\tRefunded provider: \(name)") log.debug("\tRefunded provider: \(name)")
anyRefund = true anyRefund = true
@ -108,10 +96,3 @@ extension ProductManager {
NotificationCenter.default.post(name: ProductManager.didReviewPurchases, object: nil) NotificationCenter.default.post(name: ProductManager.didReviewPurchases, object: nil)
} }
} }
extension ConnectionService {
var hasReachedMaximumNumberOfHosts: Bool {
let numberOfHosts = hostIds().count
return numberOfHosts >= AppConstants.InApp.limitedNumberOfHosts
}
}

View File

@ -222,12 +222,6 @@ class OrganizerViewController: UITableViewController, StrongTableHost {
} }
private func addNewHost() { private func addNewHost() {
if TransientStore.shared.service.hasReachedMaximumNumberOfHosts {
guard ProductManager.shared.isEligible(forFeature: .unlimitedHosts) else {
presentPurchaseScreen(forProduct: .unlimitedHosts)
return
}
}
let picker = UIDocumentPickerViewController(documentTypes: AppConstants.URLs.filetypes, in: .import) let picker = UIDocumentPickerViewController(documentTypes: AppConstants.URLs.filetypes, in: .import)
picker.allowsMultipleSelection = false picker.allowsMultipleSelection = false
picker.delegate = self picker.delegate = self
@ -243,18 +237,17 @@ class OrganizerViewController: UITableViewController, StrongTableHost {
} }
private func importNewHost() { private func importNewHost() {
if TransientStore.shared.service.hasReachedMaximumNumberOfHosts {
guard ProductManager.shared.isEligible(forFeature: .unlimitedHosts) else {
presentPurchaseScreen(forProduct: .unlimitedHosts)
return
}
}
perform(segue: StoryboardSegue.Organizer.showImportedHostsSegueIdentifier) perform(segue: StoryboardSegue.Organizer.showImportedHostsSegueIdentifier)
} }
private func addShortcuts() { private func addShortcuts() {
guard ProductManager.shared.isEligible(forFeature: .siriShortcuts) else { do {
presentPurchaseScreen(forProduct: .siriShortcuts) guard try ProductManager.shared.isEligible(forFeature: .siriShortcuts) else {
presentPurchaseScreen(forProduct: .siriShortcuts)
return
}
} catch {
presentBetaFeatureUnavailable("Siri")
return return
} }
perform(segue: StoryboardSegue.Organizer.siriShortcutsSegueIdentifier) perform(segue: StoryboardSegue.Organizer.siriShortcutsSegueIdentifier)

View File

@ -70,11 +70,16 @@ class WizardProviderViewController: UITableViewController, StrongTableHost {
private func tryNext(withMetadata metadata: Infrastructure.Metadata, purchaseIfNecessary: Bool) { private func tryNext(withMetadata metadata: Infrastructure.Metadata, purchaseIfNecessary: Bool) {
selectedMetadata = metadata selectedMetadata = metadata
guard ProductManager.shared.isEligible(forProvider: metadata) else { do {
guard purchaseIfNecessary else { guard try ProductManager.shared.isEligible(forProvider: metadata) else {
guard purchaseIfNecessary else {
return
}
presentPurchaseScreen(forProduct: metadata.product, delegate: self)
return return
} }
presentPurchaseScreen(forProduct: metadata.product, delegate: self) } catch {
presentBetaFeatureUnavailable("Providers")
return return
} }

View File

@ -377,11 +377,19 @@ class ServiceViewController: UIViewController, StrongTableHost {
} }
private func trustMobileNetwork(cell: ToggleTableViewCell) { private func trustMobileNetwork(cell: ToggleTableViewCell) {
guard ProductManager.shared.isEligible(forFeature: .trustedNetworks) else { do {
guard try ProductManager.shared.isEligible(forFeature: .trustedNetworks) else {
delay {
cell.setOn(false, animated: true)
}
presentPurchaseScreen(forProduct: .trustedNetworks)
return
}
} catch {
delay { delay {
cell.setOn(false, animated: true) cell.setOn(false, animated: true)
} }
presentPurchaseScreen(forProduct: .trustedNetworks) presentBetaFeatureUnavailable("Trusted networks")
return return
} }
@ -394,8 +402,13 @@ class ServiceViewController: UIViewController, StrongTableHost {
} }
private func trustCurrentWiFi() { private func trustCurrentWiFi() {
guard ProductManager.shared.isEligible(forFeature: .trustedNetworks) else { do {
presentPurchaseScreen(forProduct: .trustedNetworks) guard try ProductManager.shared.isEligible(forFeature: .trustedNetworks) else {
presentPurchaseScreen(forProduct: .trustedNetworks)
return
}
} catch {
presentBetaFeatureUnavailable("Trusted networks")
return return
} }
@ -447,14 +460,22 @@ class ServiceViewController: UIViewController, StrongTableHost {
} }
private func toggleTrustWiFi(cell: ToggleTableViewCell, at row: Int) { private func toggleTrustWiFi(cell: ToggleTableViewCell, at row: Int) {
guard ProductManager.shared.isEligible(forFeature: .trustedNetworks) else { do {
guard try ProductManager.shared.isEligible(forFeature: .trustedNetworks) else {
delay {
cell.setOn(false, animated: true)
}
presentPurchaseScreen(forProduct: .trustedNetworks)
return
}
} catch {
delay { delay {
cell.setOn(false, animated: true) cell.setOn(false, animated: true)
} }
presentPurchaseScreen(forProduct: .trustedNetworks) presentBetaFeatureUnavailable("Trusted networks")
return return
} }
if cell.isOn { if cell.isOn {
trustedNetworks.enableWifi(at: row) trustedNetworks.enableWifi(at: row)
} else { } else {

View File

@ -71,8 +71,6 @@ public struct Product: RawRepresentable, Equatable, Hashable {
// MARK: Features // MARK: Features
#if os(iOS) #if os(iOS)
public static let unlimitedHosts = Product(featureId: "unlimited_hosts")
public static let trustedNetworks = Product(featureId: "trusted_networks") public static let trustedNetworks = Product(featureId: "trusted_networks")
public static let siriShortcuts = Product(featureId: "siri") public static let siriShortcuts = Product(featureId: "siri")
@ -82,7 +80,6 @@ public struct Product: RawRepresentable, Equatable, Hashable {
#if os(iOS) #if os(iOS)
public static let allFeatures: [Product] = [ public static let allFeatures: [Product] = [
.unlimitedHosts,
.trustedNetworks, .trustedNetworks,
.siriShortcuts, .siriShortcuts,
.fullVersion .fullVersion

View File

@ -32,6 +32,10 @@ import TunnelKit
private let log = SwiftyBeaver.self private let log = SwiftyBeaver.self
public enum ProductError: Error {
case beta
}
public class ProductManager: NSObject { public class ProductManager: NSObject {
public struct Configuration { public struct Configuration {
public let isBetaFullVersion: Bool public let isBetaFullVersion: Bool
@ -155,11 +159,17 @@ public class ProductManager: NSObject {
return purchasedFeatures.contains(.fullVersion) return purchasedFeatures.contains(.fullVersion)
} }
public func isEligible(forFeature feature: Product) -> Bool { public func isEligible(forFeature feature: Product) throws -> Bool {
guard !isBeta else {
throw ProductError.beta
}
return isFullVersion() || purchasedFeatures.contains(feature) return isFullVersion() || purchasedFeatures.contains(feature)
} }
public func isEligible(forProvider metadata: Infrastructure.Metadata) -> Bool { public func isEligible(forProvider metadata: Infrastructure.Metadata) throws -> Bool {
guard !isBeta else {
throw ProductError.beta
}
return isFullVersion() || purchasedFeatures.contains(metadata.product) return isFullVersion() || purchasedFeatures.contains(metadata.product)
} }