|
@@ -18,7 +18,9 @@ final class PremiumStore: ObservableObject {
|
|
|
@Published private(set) var isLoadingProducts = false
|
|
@Published private(set) var isLoadingProducts = false
|
|
|
@Published private(set) var loadError: String?
|
|
@Published private(set) var loadError: String?
|
|
|
@Published private(set) var purchaseInProgress = false
|
|
@Published private(set) var purchaseInProgress = false
|
|
|
|
|
+ @Published private(set) var restoreInProgress = false
|
|
|
@Published var purchaseError: String?
|
|
@Published var purchaseError: String?
|
|
|
|
|
+ @Published var restoreMessage: String?
|
|
|
|
|
|
|
|
private var transactionListener: Task<Void, Never>?
|
|
private var transactionListener: Task<Void, Never>?
|
|
|
|
|
|
|
@@ -40,6 +42,10 @@ final class PremiumStore: ObservableObject {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func loadProducts() async {
|
|
func loadProducts() async {
|
|
|
|
|
+ await loadProducts(attempt: 1)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func loadProducts(attempt: Int) async {
|
|
|
isLoadingProducts = true
|
|
isLoadingProducts = true
|
|
|
loadError = nil
|
|
loadError = nil
|
|
|
defer { isLoadingProducts = false }
|
|
defer { isLoadingProducts = false }
|
|
@@ -47,7 +53,8 @@ final class PremiumStore: ObservableObject {
|
|
|
do {
|
|
do {
|
|
|
let ids = PremiumProductID.allCases.map(\.rawValue)
|
|
let ids = PremiumProductID.allCases.map(\.rawValue)
|
|
|
let loaded = try await Product.products(for: ids)
|
|
let loaded = try await Product.products(for: ids)
|
|
|
- products = loaded.sorted { $0.id < $1.id }
|
|
|
|
|
|
|
+ let order = Dictionary(uniqueKeysWithValues: ids.enumerated().map { ($1, $0) })
|
|
|
|
|
+ products = loaded.sorted { (order[$0.id] ?? Int.max) < (order[$1.id] ?? Int.max) }
|
|
|
if loaded.isEmpty {
|
|
if loaded.isEmpty {
|
|
|
loadError = """
|
|
loadError = """
|
|
|
No products returned for IDs: \(ids.joined(separator: ", ")).
|
|
No products returned for IDs: \(ids.joined(separator: ", ")).
|
|
@@ -60,9 +67,18 @@ final class PremiumStore: ObservableObject {
|
|
|
loadError = "Store returned partial products. Missing IDs: \(missing.joined(separator: ", "))"
|
|
loadError = "Store returned partial products. Missing IDs: \(missing.joined(separator: ", "))"
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ if products.isEmpty && attempt == 1 {
|
|
|
|
|
+ try? await Task.sleep(nanoseconds: 800_000_000)
|
|
|
|
|
+ await loadProducts(attempt: 2)
|
|
|
|
|
+ }
|
|
|
} catch {
|
|
} catch {
|
|
|
let ids = PremiumProductID.allCases.map(\.rawValue).joined(separator: ", ")
|
|
let ids = PremiumProductID.allCases.map(\.rawValue).joined(separator: ", ")
|
|
|
loadError = "StoreKit error: \(error.localizedDescription). Requested IDs: \(ids)"
|
|
loadError = "StoreKit error: \(error.localizedDescription). Requested IDs: \(ids)"
|
|
|
|
|
+ if attempt == 1 {
|
|
|
|
|
+ try? await Task.sleep(nanoseconds: 800_000_000)
|
|
|
|
|
+ await loadProducts(attempt: 2)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -103,12 +119,21 @@ final class PremiumStore: ObservableObject {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func restorePurchases() async {
|
|
func restorePurchases() async {
|
|
|
|
|
+ restoreInProgress = true
|
|
|
|
|
+ restoreMessage = nil
|
|
|
loadError = nil
|
|
loadError = nil
|
|
|
|
|
+ defer { restoreInProgress = false }
|
|
|
do {
|
|
do {
|
|
|
try await AppStore.sync()
|
|
try await AppStore.sync()
|
|
|
await refreshEntitlements()
|
|
await refreshEntitlements()
|
|
|
|
|
+ if isPremiumUnlocked {
|
|
|
|
|
+ restoreMessage = "Purchases restored successfully."
|
|
|
|
|
+ } else {
|
|
|
|
|
+ restoreMessage = "No active purchases were found for this Apple ID."
|
|
|
|
|
+ }
|
|
|
} catch {
|
|
} catch {
|
|
|
loadError = error.localizedDescription
|
|
loadError = error.localizedDescription
|
|
|
|
|
+ restoreMessage = nil
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|