db65db6232
Android UI Tests / ui-tests (push) Has been cancelled
Localize all user-facing strings across iOS (SwiftUI), shared Kotlin, and Android Compose into en/es/fr/de/pt/it/ja/ko/nl/zh: - iOS String Catalogs: main + widget Localizable.xcstrings, InfoPlist.xcstrings (permissions), plural variations, ~200 new keys translated - Shared Kotlin ClientStrings table + Android composeResources/values-* (884 keys ×10), routed Api/ViewModel/util error & UI strings through localization - Backend-localized lookups/suggestions consumed via display names - Widget extension catalog; theme names, home-profile fallbacks, validation, network errors, accessibility labels all localized Add re-runnable verification gates: - scripts/i18n_audit.py — enumerate every literal, partition to GAP=0 - scripts/i18n_coverage.py — all 10 locales translated, format-specifier parity Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
64 lines
2.0 KiB
Swift
64 lines
2.0 KiB
Swift
import Foundation
|
|
import StoreKit
|
|
|
|
/// Shared purchase/restore logic used by FeatureComparisonView, UpgradeFeatureView, and UpgradePromptView.
|
|
/// Each view owns an instance via @StateObject and binds its published properties to the UI.
|
|
@MainActor
|
|
final class SubscriptionPurchaseHelper: ObservableObject {
|
|
@Published var isProcessing = false
|
|
@Published var errorMessage: String?
|
|
@Published var showSuccessAlert = false
|
|
@Published var selectedProduct: Product?
|
|
|
|
private var storeKit: StoreKitManager { StoreKitManager.shared }
|
|
|
|
func handlePurchase(_ product: Product) {
|
|
selectedProduct = product
|
|
isProcessing = true
|
|
errorMessage = nil
|
|
|
|
Task {
|
|
do {
|
|
let transaction = try await storeKit.purchase(product)
|
|
|
|
await MainActor.run {
|
|
isProcessing = false
|
|
|
|
if transaction != nil {
|
|
// Check if backend verification failed (purchase is valid but pending server confirmation)
|
|
if let backendError = storeKit.purchaseError {
|
|
errorMessage = backendError
|
|
} else {
|
|
showSuccessAlert = true
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
await MainActor.run {
|
|
isProcessing = false
|
|
errorMessage = String(format: String(localized: "Purchase failed: %@"), error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleRestore() {
|
|
isProcessing = true
|
|
errorMessage = nil
|
|
|
|
Task {
|
|
await storeKit.restorePurchases()
|
|
|
|
await MainActor.run {
|
|
isProcessing = false
|
|
|
|
if !storeKit.purchasedProductIDs.isEmpty {
|
|
showSuccessAlert = true
|
|
} else {
|
|
errorMessage = String(localized: "No purchases found to restore")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|