Files
damus/damus/Views/Purple/Detail/IAPProductStateView.swift
Daniel D’Aquino d694c26b83 iap: move StoreKit logic out of DamusPurpleView and into a new PurpleStoreKitManager
This commit moves most of StoreKit-specific logic that was embedded into
DamusPurpleView and places it into a new PurpleStoreKitManager struct,
to make code more reusable and readable by separating view concerns from
StoreKit-specific concerns.

Most of the code here should be in feature parity with the previous
behavior. However, a few logical improvements were made alongside this
refactoring:

- Improved StoreKit transaction update monitoring logic: Previously the
  view would stop listening for purchase updates after the first update.
  However, I made the program continuously listen for purchase updates,
  as recommended by Apple's documentation
  (https://developer.apple.com/documentation/storekit/transaction/3851206-updates)

- Improved/simplified logic around getting extra information from the
  products: Information and the handling of product information was
  spread in a few separate places. I incorporated those bits of
  information into central and uniform interfaces on DamusPurpleType, to
  simplify logic and future changes.

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-02-19 10:38:43 -08:00

129 lines
4.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// PurchasedProductView.swift
// damus
//
// Created by Daniel DAquino on 2024-02-09.
//
import SwiftUI
import StoreKit
// MARK: - IAPProductStateView
extension DamusPurpleView {
typealias PurchasedProduct = DamusPurple.StoreKitManager.PurchasedProduct
struct IAPProductStateView: View {
let products: ProductState
let purchased: PurchasedProduct?
let subscribe: (Product) async throws -> Void
var body: some View {
switch self.products {
case .failed:
PurpleViewPrimitives.ProductLoadErrorView()
case .loaded(let products):
if let purchased {
PurchasedView(purchased)
} else {
ProductsView(products)
}
case .loading:
ProgressView()
.progressViewStyle(.circular)
}
}
func PurchasedView(_ purchased: PurchasedProduct) -> some View {
VStack(spacing: 10) {
Text(NSLocalizedString("Purchased!", comment: "User purchased a subscription"))
.font(.title2)
.foregroundColor(.white)
price_description(product: purchased.product)
.foregroundColor(.white)
.opacity(0.65)
.frame(width: 200)
Text(NSLocalizedString("Purchased on", comment: "Indicating when the user purchased the subscription"))
.font(.title2)
.foregroundColor(.white)
Text(format_date(date: purchased.tx.purchaseDate))
.foregroundColor(.white)
.opacity(0.65)
if let expiry = purchased.tx.expirationDate {
Text(NSLocalizedString("Renews on", comment: "Indicating when the subscription will renew"))
.font(.title2)
.foregroundColor(.white)
Text(format_date(date: expiry))
.foregroundColor(.white)
.opacity(0.65)
}
}
}
func ProductsView(_ products: [Product]) -> some View {
VStack(spacing: 10) {
Text(NSLocalizedString("Save 20% off on an annual subscription", comment: "Savings for purchasing an annual subscription"))
.font(.callout.bold())
.foregroundColor(.white)
ForEach(products) { product in
Button(action: {
Task { @MainActor in
do {
try await subscribe(product)
} catch {
print(error.localizedDescription)
}
}
}, label: {
price_description(product: product)
})
.buttonStyle(GradientButtonStyle())
}
}
.padding(.horizontal, 20)
}
func price_description(product: Product) -> some View {
let purple_type = DamusPurple.StoreKitManager.DamusPurpleType(rawValue: product.id)
return (
HStack(spacing: 10) {
Text(purple_type?.label() ?? product.displayName)
Spacer()
if let non_discounted_price = purple_type?.non_discounted_price(product: product) {
Text(verbatim: non_discounted_price)
.strikethrough()
.foregroundColor(DamusColors.white.opacity(0.5))
}
Text(verbatim: product.displayPrice)
.fontWeight(.bold)
}
)
}
}
}
// MARK: - Helper structures
extension DamusPurpleView {
enum ProductState {
case loading
case loaded([Product])
case failed
var products: [Product]? {
switch self {
case .loading:
return nil
case .loaded(let ps):
return ps
case .failed:
return nil
}
}
}
}
#Preview {
DamusPurpleView.IAPProductStateView(products: .loaded([]), purchased: nil, subscribe: {_ in })
}