Files
MobileMusicAssistant/ServicesMAStoreManager.swift
T

118 lines
3.5 KiB
Swift

//
// ServicesMAStoreManager.swift
// Mobile Music Assistant
//
// Created by Sven Hanold on 09.04.26.
//
import StoreKit
import OSLog
import SwiftUI
private let logger = Logger(subsystem: "com.musicassistant.mobile", category: "StoreManager")
enum PurchaseResult: Equatable {
case success(Product)
case cancelled
case failed(String)
}
@Observable @MainActor final class MAStoreManager {
var products: [Product] = []
var purchaseResult: PurchaseResult?
var isPurchasing = false
var loadError: String?
private static let productIDs: Set<String> = [
"donatesong",
"donatealbum",
"donateanthology"
]
private var updateListenerTask: Task<Void, Never>?
init() {
updateListenerTask = listenForTransactions()
}
func loadProducts() async {
loadError = nil
do {
let fetched = try await Product.products(for: Self.productIDs)
products = fetched.sorted { $0.price < $1.price }
if fetched.isEmpty {
loadError = "No products returned. Make sure the StoreKit configuration is active in the scheme (Edit Scheme → Run → Options → StoreKit Configuration)."
logger.warning("Product.products(for:) returned 0 results for IDs: \(Self.productIDs)")
}
} catch {
loadError = error.localizedDescription
logger.error("Failed to load products: \(error.localizedDescription)")
}
}
func purchase(_ product: Product) async {
isPurchasing = true
defer { isPurchasing = false }
do {
let result = try await product.purchase()
switch result {
case .success(let verification):
let transaction = try checkVerified(verification)
await transaction.finish()
purchaseResult = .success(product)
case .userCancelled:
purchaseResult = .cancelled
case .pending:
break
@unknown default:
break
}
} catch {
purchaseResult = .failed(error.localizedDescription)
}
}
private func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
switch result {
case .unverified(_, let error):
throw error
case .verified(let value):
return value
}
}
private func listenForTransactions() -> Task<Void, Never> {
Task(priority: .background) { [weak self] in
for await result in Transaction.updates {
do {
let transaction = try self?.checkVerified(result)
await transaction?.finish()
} catch {
logger.error("Transaction verification failed: \(error.localizedDescription)")
}
}
}
}
// MARK: - Helpers
func iconName(for product: Product) -> String {
switch product.id {
case "donatesong": return "music.note"
case "donatealbum": return "opticaldisc"
case "donateanthology": return "music.note.list"
default: return "heart.fill"
}
}
func tierName(for product: Product) -> LocalizedStringKey {
switch product.id {
case "donatesong": return "Song"
case "donatealbum": return "Album"
case "donateanthology": return "Anthology"
default: return LocalizedStringKey(product.displayName)
}
}
}