Bläddra i källkod

Decouple production paywall catalog from Products.storekit.

Source product IDs and plan ordering from bundled paywall config, keep Products.storekit for local StoreKit testing only, and remove stale StoreManager warnings touched by this refactor.

Co-authored-by: Cursor <cursoragent@cursor.com>
Hussain Afzal 2 veckor sedan
förälder
incheckning
2c28a8c7ef

+ 0 - 2
smart_printer.xcodeproj/project.pbxproj

@@ -7,7 +7,6 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
-		F87934A62FFBD2470043D1E6 /* Products.storekit in Resources */ = {isa = PBXBuildFile; fileRef = F87934A52FFBD2470043D1E6 /* Products.storekit */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
@@ -115,7 +114,6 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				F87934A62FFBD2470043D1E6 /* Products.storekit in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 6 - 8
smart_printer/AppStoreConfig.swift

@@ -1,7 +1,7 @@
 import Foundation
 
 /// Production App Store identifiers for Printer App - All Printers.
-/// Product IDs are sourced from bundled `Products.storekit`; marketing copy stays in `paywall.json`.
+/// Product IDs are sourced from bundled `paywall.json`.
 enum AppStoreConfig {
     static let displayName = "Printer App - All Printers"
     static let proDisplayName = "\(displayName) Pro"
@@ -24,13 +24,11 @@ enum AppStoreConfig {
     }
 
     enum ProductID {
-        static let monthly = StoreKitCatalog.shared.productID(for: .monthly)
-            ?? PaywallConfig.loadBundled().plans.monthly.productID
-        static let yearly = StoreKitCatalog.shared.productID(for: .yearly)
-            ?? PaywallConfig.loadBundled().plans.yearly.productID
-        static let lifetime = StoreKitCatalog.shared.productID(for: .lifetime)
-            ?? PaywallConfig.loadBundled().plans.lifetime.productID
+        private static let paywallConfig = PaywallConfig.loadBundled()
+        static let monthly = paywallConfig.plans.monthly.productID
+        static let yearly = paywallConfig.plans.yearly.productID
+        static let lifetime = paywallConfig.plans.lifetime.productID
 
-        static let all: Set<String> = StoreKitCatalog.allProductIDs
+        static let all: Set<String> = [monthly, yearly, lifetime]
     }
 }

+ 3 - 3
smart_printer/Managers/StoreManager.swift

@@ -159,7 +159,7 @@ final class StoreManager {
             object: nil,
             queue: .main
         ) { [weak self] _ in
-            Task { @MainActor in
+            Task { @MainActor [weak self] in
                 self?.refreshEntitlementsOnAppActive()
             }
         }
@@ -253,7 +253,7 @@ final class StoreManager {
                 }
 
                 let trustedPlan = planGrantingPremium(from: transaction)
-                if let trustedPlan {
+                if trustedPlan != nil {
                     applyPremiumAccess(from: transaction)
                 }
 
@@ -697,7 +697,7 @@ final class StoreManager {
             }
 
             let trustedPlan = planGrantingPremium(from: transaction)
-            if let trustedPlan {
+            if trustedPlan != nil {
                 applyPremiumAccess(from: transaction)
             }
 

+ 6 - 123
smart_printer/Services/StoreKitCatalog.swift

@@ -28,11 +28,11 @@ struct StoreKitCatalogSnapshot: Sendable {
     private let entriesByProductID: [String: StoreKitCatalogEntry]
 
     init() {
-        let loadedEntries = Self.loadEntriesFromBundle() ?? Self.fallbackEntries()
-        entries = loadedEntries
-        orderedPlans = loadedEntries.map { PaywallPlan(productID: $0.productID) }
-        allProductIDs = Set(loadedEntries.map(\.productID))
-        entriesByProductID = Dictionary(uniqueKeysWithValues: loadedEntries.map { ($0.productID, $0) })
+        let configuredEntries = Self.entriesFromPaywallConfig()
+        entries = configuredEntries
+        orderedPlans = configuredEntries.map { PaywallPlan(productID: $0.productID) }
+        allProductIDs = Set(configuredEntries.map(\.productID))
+        entriesByProductID = Dictionary(uniqueKeysWithValues: configuredEntries.map { ($0.productID, $0) })
     }
 
     func contains(productID: String) -> Bool {
@@ -91,53 +91,7 @@ struct StoreKitCatalogSnapshot: Sendable {
         orderedPlans.filter { kind(for: $0.productID) != .lifetime }
     }
 
-    private static func loadEntriesFromBundle() -> [StoreKitCatalogEntry]? {
-        guard let url = Bundle.main.url(forResource: "Products", withExtension: "storekit"),
-              let data = try? Data(contentsOf: url),
-              let file = try? JSONDecoder().decode(StoreKitConfigurationFile.self, from: data) else {
-            return nil
-        }
-
-        var entries: [StoreKitCatalogEntry] = []
-
-        for product in file.products {
-            entries.append(
-                StoreKitCatalogEntry(
-                    productID: product.productID,
-                    displayName: product.preferredDisplayName,
-                    kind: kind(forProductID: product.productID, type: product.type),
-                    sortOrder: 1_000
-                )
-            )
-        }
-
-        for group in file.subscriptionGroups {
-            for subscription in group.subscriptions {
-                entries.append(
-                    StoreKitCatalogEntry(
-                        productID: subscription.productID,
-                        displayName: subscription.preferredDisplayName,
-                        kind: kind(
-                            forProductID: subscription.productID,
-                            subscriptionPeriod: subscription.recurringSubscriptionPeriod
-                        ),
-                        sortOrder: sortOrder(for: subscription.recurringSubscriptionPeriod)
-                    )
-                )
-            }
-        }
-
-        guard !entries.isEmpty else { return nil }
-
-        return entries.sorted { lhs, rhs in
-            if lhs.sortOrder == rhs.sortOrder {
-                return lhs.productID < rhs.productID
-            }
-            return lhs.sortOrder < rhs.sortOrder
-        }
-    }
-
-    private static func fallbackEntries() -> [StoreKitCatalogEntry] {
+    private static func entriesFromPaywallConfig() -> [StoreKitCatalogEntry] {
         let config = PaywallConfig.loadBundled()
         return [
             StoreKitCatalogEntry(
@@ -160,75 +114,4 @@ struct StoreKitCatalogSnapshot: Sendable {
             ),
         ]
     }
-
-    private static func kind(
-        forProductID productID: String,
-        type: String? = nil,
-        subscriptionPeriod: String? = nil
-    ) -> PaywallPlanKind {
-        if let suffixKind = PaywallPlanKind.allCases.first(where: { productID.hasSuffix(".\($0.rawValue)") }) {
-            return suffixKind
-        }
-        if type == "NonConsumable" {
-            return .lifetime
-        }
-        switch subscriptionPeriod {
-        case "P1M": return .monthly
-        case "P1Y": return .yearly
-        default: return .monthly
-        }
-    }
-
-    private static func sortOrder(for subscriptionPeriod: String?) -> Int {
-        switch subscriptionPeriod {
-        case "P1M": return 20
-        case "P1Y": return 30
-        default: return 40
-        }
-    }
-}
-
-private struct StoreKitConfigurationFile: Decodable {
-    struct Product: Decodable {
-        struct Localization: Decodable {
-            let displayName: String
-            let locale: String
-        }
-
-        let productID: String
-        let type: String
-        let localizations: [Localization]?
-    }
-
-    struct SubscriptionGroup: Decodable {
-        struct Subscription: Decodable {
-            struct Localization: Decodable {
-                let displayName: String
-                let locale: String
-            }
-
-            let productID: String
-            let recurringSubscriptionPeriod: String?
-            let localizations: [Localization]?
-        }
-
-        let subscriptions: [Subscription]
-    }
-
-    let products: [Product]
-    let subscriptionGroups: [SubscriptionGroup]
-}
-
-private extension StoreKitConfigurationFile.Product {
-    var preferredDisplayName: String? {
-        localizations?.first(where: { $0.locale.hasPrefix("en") })?.displayName
-            ?? localizations?.first?.displayName
-    }
-}
-
-private extension StoreKitConfigurationFile.SubscriptionGroup.Subscription {
-    var preferredDisplayName: String? {
-        localizations?.first(where: { $0.locale.hasPrefix("en") })?.displayName
-            ?? localizations?.first?.displayName
-    }
 }