From 8005b304a7e42b16869ccaf961ad124f277066de Mon Sep 17 00:00:00 2001 From: Cassio Rossi Date: Sat, 2 May 2026 19:44:33 -0300 Subject: [PATCH] fix(settings): fix InApp purchase not updating removeAds environment Replace broken 100ms polling loop with proper withObservationTracking + continuation pattern that suspends until InAppManager.status mutates. Make process(purchased:) async to eliminate fire-and-forget Task race condition where DB save never completed before status reset. Add defensive .onChange(of: isValidSubscription) in SubscriptionView to ensure environment updates even if ModelContext.didSave is missed. Co-Authored-By: Claude Opus 4.6 --- .../ViewModels/SubscriptionViewModel.swift | 36 +++++++++---------- .../Supplementals/SubscriptionView.swift | 5 +++ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/MacMagazine/Features/SettingsLibrary/Sources/SettingsLibrary/ViewModels/SubscriptionViewModel.swift b/MacMagazine/Features/SettingsLibrary/Sources/SettingsLibrary/ViewModels/SubscriptionViewModel.swift index cb0c7d35..dca33a6e 100644 --- a/MacMagazine/Features/SettingsLibrary/Sources/SettingsLibrary/ViewModels/SubscriptionViewModel.swift +++ b/MacMagazine/Features/SettingsLibrary/Sources/SettingsLibrary/ViewModels/SubscriptionViewModel.swift @@ -39,18 +39,17 @@ final class SubscriptionViewModel { func setupListeners() { observationTask = Task { @MainActor [weak self] in - guard let self else { return } while !Task.isCancelled { - let currentStatus = withObservationTracking { - self.inAppLibrary.status - } onChange: { - // This closure is called when status changes + guard let self else { return } + await withCheckedContinuation { continuation in + withObservationTracking { + _ = self.inAppLibrary.status + } onChange: { + continuation.resume() + } } - - self.process(purchased: currentStatus) - - // Small delay to prevent tight loop - try? await Task.sleep(for: .milliseconds(100)) + guard !Task.isCancelled else { return } + await self.process(purchased: self.inAppLibrary.status) } } } @@ -112,19 +111,16 @@ extension SubscriptionViewModel { } private extension SubscriptionViewModel { - func process(purchased: InAppStatus) { + func process(purchased: InAppStatus) async { switch purchased { case let .purchased(identifier): if let transaction = status.product(using: identifier) { - Task { - analytics?.track( - .purchaseCompleted(productId: identifier, - revenue: transaction.price) - ) - - await change(expirationDate: transaction.expirationDate) - isValidSubscription = storage?.settings?.subscription.isValidSubscription ?? false - } + analytics?.track( + .purchaseCompleted(productId: identifier, + revenue: transaction.price) + ) + await change(expirationDate: transaction.expirationDate) + isValidSubscription = storage?.settings?.subscription.isValidSubscription ?? false } case .cancelled: diff --git a/MacMagazine/Features/SettingsLibrary/Sources/SettingsLibrary/Views/Supplementals/SubscriptionView.swift b/MacMagazine/Features/SettingsLibrary/Sources/SettingsLibrary/Views/Supplementals/SubscriptionView.swift index d5e69c02..969a3bbd 100644 --- a/MacMagazine/Features/SettingsLibrary/Sources/SettingsLibrary/Views/Supplementals/SubscriptionView.swift +++ b/MacMagazine/Features/SettingsLibrary/Sources/SettingsLibrary/Views/Supplementals/SubscriptionView.swift @@ -54,6 +54,11 @@ struct SubscriptionView: View { viewModel.isPatrao = value } } + .onChange(of: viewModel.isValidSubscription) { _, value in + if value { + settingsViewModel.removeAds = settingsViewModel.storage.settings?.subscription.removeAds ?? false + } + } } }