Merge branch 'enable-public-beta'
This commit is contained in:
commit
f183cda657
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue