From 2f603778d69e64ad5ab5fb6a49b33e43b9f6be13 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Fri, 16 Feb 2024 14:21:06 -0600 Subject: [PATCH] =?UTF-8?q?Supports=20=E2=80=9Ccustom=E2=80=9D=20pledge=20?= =?UTF-8?q?amounts=20for=20Patreon=20apps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AltStore/Components/AppBannerView.swift | 2 +- .../Operations/VerifyAppPledgeOperation.swift | 16 ++++++-- .../AltStore 16.xcdatamodel/contents | 3 +- AltStoreCore/Model/StoreApp.swift | 39 +++++++++++++++---- 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/AltStore/Components/AppBannerView.swift b/AltStore/Components/AppBannerView.swift index 9291996f..d37bbd23 100644 --- a/AltStore/Components/AppBannerView.swift +++ b/AltStore/Components/AppBannerView.swift @@ -292,7 +292,7 @@ extension AppBannerView self.button.accessibilityLabel = String(format: NSLocalizedString("Install %@", comment: ""), app.name) self.button.accessibilityValue = buttonTitle } - else if let amount = storeApp.pledgeAmount, let currencyCode = storeApp.pledgeCurrency, #available(iOS 15, *) + else if let amount = storeApp.pledgeAmount, let currencyCode = storeApp.pledgeCurrency, !storeApp.prefersCustomPledge, #available(iOS 15, *) { let price = amount.formatted(.currency(code: currencyCode).presentation(.narrow).precision(.fractionLength(0...2))) diff --git a/AltStore/Operations/VerifyAppPledgeOperation.swift b/AltStore/Operations/VerifyAppPledgeOperation.swift index 7ec8f674..82b40615 100644 --- a/AltStore/Operations/VerifyAppPledgeOperation.swift +++ b/AltStore/Operations/VerifyAppPledgeOperation.swift @@ -64,12 +64,22 @@ class VerifyAppPledgeOperation: ResultOperation let components = URLComponents(url: patreonURL, resolvingAgainstBaseURL: false) let lastPathComponent = components?.path.components(separatedBy: "/").last - let checkoutURL: URL - let username = lastPathComponent ?? patreonURL.lastPathComponent - if !username.isEmpty, let url = URL(string: "https://www.patreon.com/join/" + username) + + let checkoutURL: URL + if await self.$storeApp.prefersCustomPledge, let customPledgeURL = URL(string: "https://www.patreon.com/checkout/" + username + "?rid=0&custom=1") + { + checkoutURL = customPledgeURL + + let action = await UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) + try await presentingViewController.presentConfirmationAlert(title: NSLocalizedString("Custom Pledge", comment: ""), + message: NSLocalizedString("This app supports custom pledges. Pledge any amount on Patreon to receive access.", comment: ""), + primaryAction: action) + } + else if !username.isEmpty, let url = URL(string: "https://www.patreon.com/join/" + username) { // Prefer /join URL over campaign homepage. + // URL format from https://support.patreon.com/hc/en-us/articles/360044376211-Managing-members-with-custom-pledges checkoutURL = url } else diff --git a/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 16.xcdatamodel/contents b/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 16.xcdatamodel/contents index 6a764404..e02a449e 100644 --- a/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 16.xcdatamodel/contents +++ b/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 16.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -253,6 +253,7 @@ + diff --git a/AltStoreCore/Model/StoreApp.swift b/AltStoreCore/Model/StoreApp.swift index f6f5032a..2f374e79 100644 --- a/AltStoreCore/Model/StoreApp.swift +++ b/AltStoreCore/Model/StoreApp.swift @@ -91,16 +91,36 @@ extension PlatformURL: Comparable { public typealias PlatformURLs = [PlatformURL] -extension StoreApp +private struct PatreonParameters: Decodable { - private struct PatreonParameters: Decodable + struct Pledge: Decodable { - var pledge: Decimal? - var currency: String? - var tiers: Set? - var benefit: String? - var hidden: Bool? + var amount: Decimal + var isCustom: Bool + + init(from decoder: Decoder) throws + { + let container = try decoder.singleValueContainer() + + if let stringValue = try? container.decode(String.self), stringValue == "custom" + { + self.amount = 0 // Use 0 as amount internally to simplify logic. + self.isCustom = true + } + else + { + // Unless the value is "custom", throw error if value is not Decimal. + self.amount = try container.decode(Decimal.self) + self.isCustom = false + } + } } + + var pledge: Pledge? + var currency: String? + var tiers: Set? + var benefit: String? + var hidden: Bool? } @objc(StoreApp) @@ -136,6 +156,7 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable @NSManaged public private(set) var isPledgeRequired: Bool @NSManaged public private(set) var isHiddenWithoutPledge: Bool @NSManaged public private(set) var pledgeCurrency: String? + @NSManaged public private(set) var prefersCustomPledge: Bool @nonobjc public var pledgeAmount: Decimal? { _pledgeAmount as? Decimal } @NSManaged @objc(pledgeAmount) private var _pledgeAmount: NSDecimalNumber? @@ -423,6 +444,7 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable // Must _explicitly_ set to false to ensure it updates cached database value. self.isPledged = false + self.prefersCustomPledge = false if let patreon = try container.decodeIfPresent(PatreonParameters.self, forKey: .patreon) { @@ -431,8 +453,9 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable if let pledge = patreon.pledge { - self._pledgeAmount = pledge as NSDecimalNumber + self._pledgeAmount = pledge.amount as NSDecimalNumber self.pledgeCurrency = patreon.currency ?? "USD" // Only set pledge currency if explicitly given pledge. + self.prefersCustomPledge = pledge.isCustom } else if patreon.pledge == nil && patreon.tiers == nil && patreon.benefit == nil {