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.
This commit is contained in:
Davide De Rosa 2022-04-26 20:40:51 +02:00
parent ed81e374aa
commit 99e48119f7
6 changed files with 77 additions and 36 deletions

View File

@ -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 = "<group>"; };
0E065F102813269500062CAF /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
0E0BD27227B2EA2C00583AC5 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
0E0BD27527B2EB2200583AC5 /* DonateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonateView.swift; sourceTree = "<group>"; };
@ -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 */,

View File

@ -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()

View File

@ -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)

View File

@ -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 <http://www.gnu.org/licenses/>.
//
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)
}
}

View File

@ -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 {

View File

@ -51,7 +51,7 @@ struct PaywallView: View {
var body: some View {
Group {
if productManager.cfg.appType == .beta {
if productManager.appType == .beta {
BetaView()
} else {
PurchaseView(