From dc40f5019fec3bebca0fa3f1b8512176b09d1ede Mon Sep 17 00:00:00 2001 From: Max Radermacher Date: Thu, 27 Mar 2025 13:16:02 -0500 Subject: [PATCH] Asyncify DonationSettingsViewController.loadState --- ...tionSettingsViewController+MySupport.swift | 8 +- .../DonationSettingsViewController.swift | 140 ++++++++---------- .../Donations/DonationViewsUtil+IDEAL.swift | 2 +- .../Donations/DonationViewsUtil.swift | 3 +- 4 files changed, 72 insertions(+), 81 deletions(-) diff --git a/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController+MySupport.swift b/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController+MySupport.swift index 05ef9d40be..f6f3fa439e 100644 --- a/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController+MySupport.swift +++ b/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController+MySupport.swift @@ -567,11 +567,13 @@ extension DonationSettingsViewController { private func showDonateAndCancelSubscriptionAction(title: ShowDonateActionTitle) -> ActionSheetAction { return ActionSheetAction(title: title.localizedTitle) { _ in Task.detached { - if let subscriberId = SSKEnvironment.shared.databaseStorageRef.read(block: { - DonationSubscriptionManager.getSubscriberID(transaction: $0) }) { + let subscriberId = SSKEnvironment.shared.databaseStorageRef.read { tx in + return DonationSubscriptionManager.getSubscriberID(transaction: tx) + } + if let subscriberId { try await DonationSubscriptionManager.cancelSubscription(for: subscriberId) } - await self.loadAndUpdateState().awaitable() + await self.loadAndUpdateState() await self.showDonateViewController(preferredDonateMode: .monthly) } } diff --git a/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController.swift b/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController.swift index 678751f6d1..f9b2806ffc 100644 --- a/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController.swift +++ b/Signal/src/ViewControllers/AppSettings/Donations/DonationSettingsViewController.swift @@ -99,7 +99,9 @@ class DonationSettingsViewController: OWSTableViewController2 { public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - _ = loadAndUpdateState() + Task { + await self.loadAndUpdateState() + } } public override func viewDidAppear(_ animated: Bool) { @@ -129,18 +131,18 @@ class DonationSettingsViewController: OWSTableViewController2 { // MARK: - Data loading - func loadAndUpdateState() -> Guarantee { + func loadAndUpdateState() async { switch state { case .loading: owsFailDebug("Already loading!") - return .value(()) + return case .initializing, .loadFinished: self.state = .loading - return loadState().done { self.state = $0 } + self.state = await self.loadState() } } - private func loadState() -> Guarantee { + private func loadState() async -> State { let idealStore = DependenciesBridge.shared.externalPendingIDEALDonationStore let profileManager = SSKEnvironment.shared.profileManagerRef let ( @@ -167,75 +169,68 @@ class DonationSettingsViewController: OWSTableViewController2 { ) } - let subscriptionLevelsPromise = Promise.wrapAsync { try await DonationViewsUtil.loadSubscriptionLevels(badgeStore: SSKEnvironment.shared.profileManagerRef.badgeStore) } - let currentSubscriptionPromise = Promise.wrapAsync { - try await DonationViewsUtil.loadCurrentSubscription(subscriberID: subscriberID) - } - let profileBadgeLookupPromise = Guarantee.wrapAsync { await self.loadProfileBadgeLookup() } + async let subscriptionLevels = DonationViewsUtil.loadSubscriptionLevels(badgeStore: SSKEnvironment.shared.profileManagerRef.badgeStore) + async let currentSubscription = DonationViewsUtil.loadCurrentSubscription(subscriberID: subscriberID) + async let profileBadgeLookup = loadProfileBadgeLookup() - return profileBadgeLookupPromise.then { profileBadgeLookup -> Guarantee in - subscriptionLevelsPromise.then { subscriptionLevels -> Promise in - currentSubscriptionPromise.then { currentSubscription -> Guarantee in - let result: State = .loadFinished( - subscriptionStatus: { - guard let currentSubscription else { - if let pendingIDEALSubscription { - return .pendingSubscription(pendingIDEALSubscription) - } else { - return .noSubscription - } - } - - return .hasSubscription( - subscription: currentSubscription, - subscriptionLevel: DonationViewsUtil.subscriptionLevelForSubscription( - subscriptionLevels: subscriptionLevels, - subscription: currentSubscription - ), - previouslyHadActiveSubscription: hasEverRedeemedRecurringSubscriptionBadge, - receiptCredentialRequestError: recurringSubscriptionReceiptCredentialRequestError - ) - }(), - oneTimeBoostReceiptCredentialRequestError: oneTimeBoostReceiptCredentialRequestError, - profileBadgeLookup: profileBadgeLookup, - pendingOneTimeDonation: pendingIDEALOneTimeDonation, - hasAnyBadges: hasAnyBadges, - hasAnyDonationReceipts: hasAnyDonationReceipts - ) - if let pendingIDEALSubscription { - // Serialized badges lose their assets, so ensure they've - // been populated before returning. - return Promise.wrapAsync { - try await SSKEnvironment.shared.profileManagerRef.badgeStore.populateAssetsOnBadge(pendingIDEALSubscription.newSubscriptionLevel.badge) - }.then { _ -> Guarantee in - Guarantee.value(result) - }.recover { _ in - Guarantee.value(result) - } - } else { - return Guarantee.value(result) - } - } - }.recover { error -> Guarantee in - Logger.warn("[Donations] \(error)") - owsFailDebugUnlessNetworkFailure(error) - let result: State = .loadFinished( - subscriptionStatus: .loadFailed, - oneTimeBoostReceiptCredentialRequestError: oneTimeBoostReceiptCredentialRequestError, - profileBadgeLookup: profileBadgeLookup, - pendingOneTimeDonation: pendingIDEALOneTimeDonation, - hasAnyBadges: hasAnyBadges, - hasAnyDonationReceipts: hasAnyDonationReceipts + do { + let subscriptionStatus: State.SubscriptionStatus + if let currentSubscription = try await currentSubscription { + subscriptionStatus = .hasSubscription( + subscription: currentSubscription, + subscriptionLevel: DonationViewsUtil.subscriptionLevelForSubscription( + subscriptionLevels: try await subscriptionLevels, + subscription: currentSubscription + ), + previouslyHadActiveSubscription: hasEverRedeemedRecurringSubscriptionBadge, + receiptCredentialRequestError: recurringSubscriptionReceiptCredentialRequestError ) - return Guarantee.value(result) + + } else if let pendingIDEALSubscription { + subscriptionStatus = .pendingSubscription(pendingIDEALSubscription) + } else { + subscriptionStatus = .noSubscription } + + let result: State = .loadFinished( + subscriptionStatus: subscriptionStatus, + oneTimeBoostReceiptCredentialRequestError: oneTimeBoostReceiptCredentialRequestError, + profileBadgeLookup: await profileBadgeLookup, + pendingOneTimeDonation: pendingIDEALOneTimeDonation, + hasAnyBadges: hasAnyBadges, + hasAnyDonationReceipts: hasAnyDonationReceipts + ) + if let pendingIDEALSubscription { + // Serialized badges lose their assets, so ensure they've + // been populated before returning. + try? await SSKEnvironment.shared.profileManagerRef.badgeStore.populateAssetsOnBadge(pendingIDEALSubscription.newSubscriptionLevel.badge) + } + return result + } catch { + Logger.warn("[Donations] \(error)") + owsFailDebugUnlessNetworkFailure(error) + let result: State = .loadFinished( + subscriptionStatus: .loadFailed, + oneTimeBoostReceiptCredentialRequestError: oneTimeBoostReceiptCredentialRequestError, + profileBadgeLookup: await profileBadgeLookup, + pendingOneTimeDonation: pendingIDEALOneTimeDonation, + hasAnyBadges: hasAnyBadges, + hasAnyDonationReceipts: hasAnyDonationReceipts + ) + return result } } private func loadProfileBadgeLookup() async -> ProfileBadgeLookup { - let donationConfiguration: DonationSubscriptionManager.DonationConfiguration do { - donationConfiguration = try await DonationSubscriptionManager.fetchDonationConfiguration() + let donationConfiguration = try await DonationSubscriptionManager.fetchDonationConfiguration() + let result = ProfileBadgeLookup( + boostBadge: donationConfiguration.boost.badge, + giftBadge: donationConfiguration.gift.badge, + subscriptionLevels: donationConfiguration.subscription.levels + ) + await result.attemptToPopulateBadgeAssets(populateAssetsOnBadge: SSKEnvironment.shared.profileManagerRef.badgeStore.populateAssetsOnBadge(_:)) + return result } catch { Logger.warn("[Donations] Failed to fetch donation configuration \(error). Proceeding without it, as it is only cosmetic here.") return ProfileBadgeLookup( @@ -244,13 +239,6 @@ class DonationSettingsViewController: OWSTableViewController2 { subscriptionLevels: [] ) } - let profileBadgeLookup = ProfileBadgeLookup( - boostBadge: donationConfiguration.boost.badge, - giftBadge: donationConfiguration.gift.badge, - subscriptionLevels: donationConfiguration.subscription.levels - ) - await profileBadgeLookup.attemptToPopulateBadgeAssets(populateAssetsOnBadge: SSKEnvironment.shared.profileManagerRef.badgeStore.populateAssetsOnBadge) - return profileBadgeLookup } private func setUpAvatarView() { @@ -682,9 +670,9 @@ class DonationSettingsViewController: OWSTableViewController2 { // Not ideal, because this makes network requests. However, this // should be rare, and doing it this way avoids us needing to add // methods for updating the state outside the normal loading flow. - self.loadAndUpdateState().done(on: DispatchQueue.main) { [weak self] in - guard let self else { return } - self.showDonateViewController(preferredDonateMode: donateMode) + Task { [weak self] in + await self?.loadAndUpdateState() + self?.showDonateViewController(preferredDonateMode: donateMode) } } } diff --git a/Signal/src/ViewControllers/Donations/DonationViewsUtil+IDEAL.swift b/Signal/src/ViewControllers/Donations/DonationViewsUtil+IDEAL.swift index 2f66a6744c..9d1c162fce 100644 --- a/Signal/src/ViewControllers/Donations/DonationViewsUtil+IDEAL.swift +++ b/Signal/src/ViewControllers/Donations/DonationViewsUtil+IDEAL.swift @@ -173,7 +173,7 @@ extension DonationViewsUtil { }.ensure(on: DispatchQueue.main) { // refresh the local state upon completing the donation // to refresh any pending donation messages - _ = donationsVC.loadAndUpdateState() + Task { await donationsVC.loadAndUpdateState() } } } } diff --git a/Signal/src/ViewControllers/Donations/DonationViewsUtil.swift b/Signal/src/ViewControllers/Donations/DonationViewsUtil.swift index 39e9ee06d0..2c6ed3eb66 100644 --- a/Signal/src/ViewControllers/Donations/DonationViewsUtil.swift +++ b/Signal/src/ViewControllers/Donations/DonationViewsUtil.swift @@ -329,12 +329,13 @@ public final class DonationViewsUtil { public static func loadSubscriptionLevels(badgeStore: BadgeStore) async throws -> [DonationSubscriptionLevel] { let levels = try await DonationSubscriptionManager.fetchDonationConfiguration().subscription.levels - await withThrowingTaskGroup(of: Void.self) { taskGroup in + try await withThrowingTaskGroup(of: Void.self) { taskGroup in for level in levels { taskGroup.addTask { try await badgeStore.populateAssetsOnBadge(level.badge) } } + try await taskGroup.waitForAll() } return levels }