Asyncify DonationSettingsViewController.loadState

This commit is contained in:
Max Radermacher 2025-03-27 13:16:02 -05:00 committed by GitHub
parent efacbf8fac
commit dc40f5019f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 72 additions and 81 deletions

View File

@ -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)
}
}

View File

@ -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<Void> {
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<State> {
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<State> in
subscriptionLevelsPromise.then { subscriptionLevels -> Promise<State> in
currentSubscriptionPromise.then { currentSubscription -> Guarantee<State> 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<State> in
Guarantee.value(result)
}.recover { _ in
Guarantee.value(result)
}
} else {
return Guarantee.value(result)
}
}
}.recover { error -> Guarantee<State> 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)
}
}
}

View File

@ -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() }
}
}
}

View File

@ -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
}