From 99e48119f7ca5c8559b60f5a58bb836fbeb0b1ca Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Tue, 26 Apr 2022 20:40:51 +0200 Subject: [PATCH] Encapsulate calculation of former products Use an interface (BuildProducts) that makes understandable and easily extensibile how some in-app products are inferred by build number. --- Passepartout.xcodeproj/project.pbxproj | 4 ++ Passepartout/App/Constants/AppContext.swift | 7 ++-- .../App/Constants/Constants+Extensions.swift | 18 ++++++-- Passepartout/App/InApp/BuildProducts.swift | 42 +++++++++++++++++++ Passepartout/App/InApp/ProductManager.swift | 40 ++++++------------ Passepartout/App/Views/PaywallView.swift | 2 +- 6 files changed, 77 insertions(+), 36 deletions(-) create mode 100644 Passepartout/App/InApp/BuildProducts.swift diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj index dd0f5b32..5e20df5b 100644 --- a/Passepartout.xcodeproj/project.pbxproj +++ b/Passepartout.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0E0392772818732D00827C10 /* BuildProducts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0392762818732D00827C10 /* BuildProducts.swift */; }; 0E065F112813269500062CAF /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E065F102813269500062CAF /* WelcomeView.swift */; }; 0E0BD27327B2EA2C00583AC5 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27227B2EA2C00583AC5 /* MainView.swift */; }; 0E0BD27627B2EB2200583AC5 /* DonateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27527B2EB2200583AC5 /* DonateView.swift */; }; @@ -188,6 +189,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0E0392762818732D00827C10 /* BuildProducts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildProducts.swift; sourceTree = ""; }; 0E065F102813269500062CAF /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; 0E0BD27227B2EA2C00583AC5 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; 0E0BD27527B2EB2200583AC5 /* DonateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonateView.swift; sourceTree = ""; }; @@ -506,6 +508,7 @@ 0E92781227E7CD530057BB81 /* InApp */ = { isa = PBXGroup; children = ( + 0E0392762818732D00827C10 /* BuildProducts.swift */, 0EB17EA327D2263700D473B5 /* LocalProduct.swift */, 0E53249627D26B51002565C3 /* ProductManager.swift */, ); @@ -904,6 +907,7 @@ 0E35C09A280E95BB0071FA35 /* ProviderProfileAvailability.swift in Sources */, 0E5349C827C176D100C71BB3 /* EndpointView+WireGuard.swift in Sources */, 0EBC076027EC587900208AD9 /* SwiftGen+Strings.swift in Sources */, + 0E0392772818732D00827C10 /* BuildProducts.swift in Sources */, 0E5683B927C2825D00EAF1CD /* DiagnosticsView.swift in Sources */, 0E71ACFD27C1321A00F85C4B /* ActivityView.swift in Sources */, 0E44689627B051C300A14CE4 /* ProfileView.swift in Sources */, diff --git a/Passepartout/App/Constants/AppContext.swift b/Passepartout/App/Constants/AppContext.swift index dbd019ea..eb16fe15 100644 --- a/Passepartout/App/Constants/AppContext.swift +++ b/Passepartout/App/Constants/AppContext.swift @@ -112,11 +112,10 @@ class AppContext { // app - productManager = ProductManager(.init( + productManager = ProductManager( appType: Constants.InApp.appType, - lastFullVersionBuild: Constants.InApp.lastFullVersionBuild, - lastNetworkSettingsBuild: Constants.InApp.lastNetworkSettingsBuild - )) + buildProducts: Constants.InApp.buildProducts + ) intentsManager = IntentsManager() reviewer = Reviewer() diff --git a/Passepartout/App/Constants/Constants+Extensions.swift b/Passepartout/App/Constants/Constants+Extensions.swift index 7174ce52..53b55442 100644 --- a/Passepartout/App/Constants/Constants+Extensions.swift +++ b/Passepartout/App/Constants/Constants+Extensions.swift @@ -71,12 +71,22 @@ extension Constants { } #if targetEnvironment(macCatalyst) - static let lastFullVersionBuild: (Int, LocalProduct) = (0, .fullVersion_macOS) + static let buildProducts = BuildProducts { + if $0 <= 3000 { + return [.networkSettings] + } + return [] + } #else - static let lastFullVersionBuild: (Int, LocalProduct) = (2016, .fullVersion_iOS) + static let buildProducts = BuildProducts { + if $0 <= 2016 { + return [.fullVersion_iOS] + } else if $0 <= 3000 { + return [.networkSettings] + } + return [] + } #endif - - static let lastNetworkSettingsBuild = 2999 private static var isBeta: Bool { #if targetEnvironment(simulator) diff --git a/Passepartout/App/InApp/BuildProducts.swift b/Passepartout/App/InApp/BuildProducts.swift new file mode 100644 index 00000000..13b8295b --- /dev/null +++ b/Passepartout/App/InApp/BuildProducts.swift @@ -0,0 +1,42 @@ +// +// BuildProducts.swift +// Passepartout +// +// Created by Davide De Rosa on 4/26/22. +// Copyright (c) 2022 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +struct BuildProducts { + private let productsAtBuild: (Int) -> [LocalProduct] + + init(productsAtBuild: @escaping (Int) -> [LocalProduct]) { + self.productsAtBuild = productsAtBuild + } + + func products(atBuild build: Int) -> [LocalProduct] { + productsAtBuild(build) + } + + func hasProduct(_ product: LocalProduct, atBuild build: Int) -> Bool { + productsAtBuild(build).contains(product) + } +} diff --git a/Passepartout/App/InApp/ProductManager.swift b/Passepartout/App/InApp/ProductManager.swift index fc302037..b5f8d044 100644 --- a/Passepartout/App/InApp/ProductManager.swift +++ b/Passepartout/App/InApp/ProductManager.swift @@ -43,26 +43,10 @@ class ProductManager: NSObject, ObservableObject { case fullVersion = 2 } - - struct Configuration { - let appType: AppType - - let lastFullVersionBuild: (Int, LocalProduct) - - let lastNetworkSettingsBuild: Int - - init( - appType: AppType, - lastFullVersionBuild: (Int, LocalProduct), - lastNetworkSettingsBuild: Int - ) { - self.appType = appType - self.lastFullVersionBuild = lastFullVersionBuild - self.lastNetworkSettingsBuild = lastNetworkSettingsBuild - } - } - let cfg: Configuration + let appType: AppType + + let buildProducts: BuildProducts @Published private(set) var isRefreshingProducts = false @@ -82,8 +66,10 @@ class ProductManager: NSObject, ObservableObject { private var refreshRequest: SKReceiptRefreshRequest? - init(_ cfg: Configuration) { - self.cfg = cfg + init(appType: AppType, buildProducts: BuildProducts) { + self.appType = appType + self.buildProducts = buildProducts + products = [] inApp = InApp() purchasedAppBuild = nil @@ -195,7 +181,7 @@ class ProductManager: NSObject, ObservableObject { } private func isFullVersion() -> Bool { - if cfg.appType == .fullVersion { + if appType == .fullVersion { return true } if isCurrentPlatformVersion() { @@ -206,7 +192,7 @@ class ProductManager: NSObject, ObservableObject { func isEligible(forFeature feature: LocalProduct) -> Bool { if let purchasedAppBuild = purchasedAppBuild { - if feature == .networkSettings && purchasedAppBuild <= cfg.lastNetworkSettingsBuild { + if feature == .networkSettings && buildProducts.hasProduct(.networkSettings, atBuild: purchasedAppBuild) { return true } } @@ -222,7 +208,7 @@ class ProductManager: NSObject, ObservableObject { } func isEligibleForFeedback() -> Bool { - return cfg.appType == .beta || !purchasedFeatures.isEmpty + return appType == .beta || !purchasedFeatures.isEmpty } func hasPurchased(_ product: LocalProduct) -> Bool { @@ -256,9 +242,9 @@ class ProductManager: NSObject, ObservableObject { if let buildNumber = purchasedAppBuild { pp_log.debug("Original purchased build: \(buildNumber)") - // treat former purchases as full versions - if buildNumber <= cfg.lastFullVersionBuild.0 { - purchasedFeatures.insert(cfg.lastFullVersionBuild.1) + // assume some purchases by build number + buildProducts.products(atBuild: buildNumber).forEach { + purchasedFeatures.insert($0) } } if let iapReceipts = receipt.inAppPurchaseReceipts { diff --git a/Passepartout/App/Views/PaywallView.swift b/Passepartout/App/Views/PaywallView.swift index 0de4c66d..91099dd7 100644 --- a/Passepartout/App/Views/PaywallView.swift +++ b/Passepartout/App/Views/PaywallView.swift @@ -51,7 +51,7 @@ struct PaywallView: View { var body: some View { Group { - if productManager.cfg.appType == .beta { + if productManager.appType == .beta { BetaView() } else { PurchaseView(