|
|
@@ -117,14 +117,18 @@ enum StoreError: LocalizedError {
|
|
|
final class StoreManager {
|
|
|
static let shared = StoreManager()
|
|
|
|
|
|
+ private static let cachedPremiumKey = "StoreManager.cachedIsPremium"
|
|
|
+
|
|
|
private(set) var products: [Product] = []
|
|
|
- private(set) var isPremium = false
|
|
|
+ private(set) var isPremium = UserDefaults.standard.bool(forKey: cachedPremiumKey)
|
|
|
var isPro: Bool { isPremium }
|
|
|
private(set) var isLoadingProducts = false
|
|
|
private(set) var isPurchasing = false
|
|
|
+ private(set) var isResolvingEntitlements = true
|
|
|
private(set) var introOfferEligibleByProductID: [String: Bool] = [:]
|
|
|
|
|
|
private var transactionListener: Task<Void, Never>?
|
|
|
+ private var initialEntitlementTask: Task<Void, Never>?
|
|
|
private var hasStarted = false
|
|
|
|
|
|
private init() {}
|
|
|
@@ -139,12 +143,20 @@ final class StoreManager {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- Task {
|
|
|
- await loadProducts()
|
|
|
- await refreshPremiumStatus()
|
|
|
+ initialEntitlementTask = Task { [weak self] in
|
|
|
+ guard let self else { return }
|
|
|
+ await self.refreshEntitlementsOnly()
|
|
|
+ self.isResolvingEntitlements = false
|
|
|
+ NotificationCenter.default.post(name: .entitlementsDidResolve, object: nil)
|
|
|
+ await self.loadProducts()
|
|
|
+ await self.refreshPremiumStatus()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ func ensureEntitlementsResolved() async {
|
|
|
+ await initialEntitlementTask?.value
|
|
|
+ }
|
|
|
+
|
|
|
func product(for plan: PaywallPlan) -> Product? {
|
|
|
products.first { $0.id == plan.productID }
|
|
|
}
|
|
|
@@ -271,11 +283,19 @@ final class StoreManager {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ func refreshEntitlementsOnly() async {
|
|
|
+ applyPremiumStatus(await hasActivePremiumAccess())
|
|
|
+ }
|
|
|
+
|
|
|
func refreshPremiumStatus() async {
|
|
|
await refreshIntroOfferEligibility()
|
|
|
- let hasPremium = await hasActivePremiumAccess()
|
|
|
+ applyPremiumStatus(await hasActivePremiumAccess())
|
|
|
+ }
|
|
|
+
|
|
|
+ private func applyPremiumStatus(_ hasPremium: Bool) {
|
|
|
let didChange = hasPremium != isPremium
|
|
|
isPremium = hasPremium
|
|
|
+ UserDefaults.standard.set(hasPremium, forKey: Self.cachedPremiumKey)
|
|
|
|
|
|
if didChange {
|
|
|
NotificationCenter.default.post(name: .premiumStatusDidChange, object: nil)
|
|
|
@@ -382,6 +402,7 @@ final class StoreManager {
|
|
|
|
|
|
extension Notification.Name {
|
|
|
static let premiumStatusDidChange = Notification.Name("premiumStatusDidChange")
|
|
|
+ static let entitlementsDidResolve = Notification.Name("entitlementsDidResolve")
|
|
|
static let storeProductsDidUpdate = Notification.Name("storeProductsDidUpdate")
|
|
|
static let storeStateDidChange = Notification.Name("storeStateDidChange")
|
|
|
}
|
|
|
@@ -398,6 +419,17 @@ enum PremiumAccess {
|
|
|
@discardableResult
|
|
|
static func require(from window: NSWindow?, returnToHomeOnDismiss: Bool = false) -> Bool {
|
|
|
guard !isGranted else { return true }
|
|
|
+
|
|
|
+ if StoreManager.shared.isResolvingEntitlements {
|
|
|
+ Task { @MainActor in
|
|
|
+ await StoreManager.shared.ensureEntitlementsResolved()
|
|
|
+ guard !isGranted else { return }
|
|
|
+ guard let viewController = mainViewController(from: window) else { return }
|
|
|
+ viewController.presentPaywall(returnToHomeOnDismiss: returnToHomeOnDismiss)
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
guard let viewController = mainViewController(from: window) else { return false }
|
|
|
viewController.presentPaywall(returnToHomeOnDismiss: returnToHomeOnDismiss)
|
|
|
return false
|
|
|
@@ -1604,10 +1636,10 @@ final class PaywallOverlayView: NSView, AppearanceRefreshable {
|
|
|
}
|
|
|
alphaValue = 0
|
|
|
Task { @MainActor in
|
|
|
+ await StoreManager.shared.ensureEntitlementsResolved()
|
|
|
if StoreManager.shared.products.isEmpty {
|
|
|
await StoreManager.shared.loadProducts()
|
|
|
}
|
|
|
- await StoreManager.shared.refreshPremiumStatus()
|
|
|
await PaywallConfigService.shared.refreshFromRemote()
|
|
|
paywallView.refreshStoreState()
|
|
|
alphaValue = 1
|