IAPProductStateView.swift (7800B)
1 // 2 // PurchasedProductView.swift 3 // damus 4 // 5 // Created by Daniel D’Aquino on 2024-02-09. 6 // 7 8 import SwiftUI 9 import StoreKit 10 11 // MARK: - IAPProductStateView 12 13 extension DamusPurpleView { 14 typealias PurchasedProduct = DamusPurple.StoreKitManager.PurchasedProduct 15 static let SHOW_IAP_DEBUG_INFO = false 16 17 struct IAPProductStateView: View { 18 var products: ProductState 19 var purchased: PurchasedProduct? 20 let account_uuid: UUID 21 let subscribe: (Product) async throws -> Void 22 23 @State var show_manage_subscriptions = false 24 @State var subscription_purchase_loading = false 25 26 var body: some View { 27 if subscription_purchase_loading { 28 HStack(spacing: 10) { 29 Text("Purchasing", comment: "Loading label indicating the purchase action is in progress") 30 .foregroundStyle(.white) 31 ProgressView() 32 .progressViewStyle(.circular) 33 .tint(.white) 34 } 35 } 36 else { 37 switch self.products { 38 case .failed: 39 PurpleViewPrimitives.ProductLoadErrorView() 40 case .loaded(let products): 41 if let purchased { 42 PurchasedView(purchased) 43 } else { 44 ProductsView(products) 45 } 46 case .loading: 47 ProgressView() 48 .progressViewStyle(.circular) 49 } 50 } 51 } 52 53 func PurchasedView(_ purchased: PurchasedProduct) -> some View { 54 return Group { 55 if self.account_uuid == purchased.tx.appAccountToken { 56 // If the In-app purchase is registered to this account, show options to manage the subscription 57 PurchasedManageView(purchased) 58 } 59 else { 60 // If the In-app purchase is registered to a different account, we cannot manage the subscription 61 // This seems to be a limitation of StoreKit where we can only have one renewable subscription for a product at a time. 62 // Therefore, instruct the user about this limitation, or to contact us if they believe this is a mistake. 63 PurchasedUnmanageableView(purchased) 64 } 65 } 66 } 67 68 func PurchasedUnmanageableView(_ purchased: PurchasedProduct) -> some View { 69 Text("This device's in-app purchase is registered to a different Nostr account. Unable to manage this Purple account. If you believe this was a mistake, please contact us via support@damus.io.", comment: "Notice label that user cannot manage their In-App purchases") 70 .font(.caption) 71 .foregroundColor(.white.opacity(0.6)) 72 .multilineTextAlignment(.center) 73 .padding(.horizontal) 74 } 75 76 func PurchasedManageView(_ purchased: PurchasedProduct) -> some View { 77 VStack(spacing: 10) { 78 if SHOW_IAP_DEBUG_INFO == true { 79 Text("Purchased!", comment: "User purchased a subscription") 80 .font(.title2) 81 .foregroundColor(.white) 82 price_description(product: purchased.product) 83 .foregroundColor(.white) 84 .opacity(0.65) 85 .frame(width: 200) 86 Text("Purchased on", comment: "Indicating when the user purchased the subscription") 87 .font(.title2) 88 .foregroundColor(.white) 89 Text(format_date(date: purchased.tx.purchaseDate)) 90 .foregroundColor(.white) 91 .opacity(0.65) 92 if let expiry = purchased.tx.expirationDate { 93 Text("Renews on", comment: "Indicating when the subscription will renew") 94 .font(.title2) 95 .foregroundColor(.white) 96 Text(format_date(date: expiry)) 97 .foregroundColor(.white) 98 .opacity(0.65) 99 } 100 } 101 Button(action: { 102 show_manage_subscriptions = true 103 }, label: { 104 Text("Manage", comment: "Manage the damus subscription") 105 .padding(.horizontal, 20) 106 }) 107 .buttonStyle(GradientButtonStyle()) 108 } 109 .manageSubscriptionsSheet(isPresented: $show_manage_subscriptions) 110 .padding() 111 } 112 113 func ProductsView(_ products: [Product]) -> some View { 114 VStack(spacing: 10) { 115 Text("Save 20% off on an annual subscription", comment: "Savings for purchasing an annual subscription") 116 .font(.callout.bold()) 117 .foregroundColor(.white) 118 ForEach(products) { product in 119 Button(action: { 120 Task { @MainActor in 121 do { 122 subscription_purchase_loading = true 123 try await subscribe(product) 124 subscription_purchase_loading = false 125 } catch { 126 print(error.localizedDescription) 127 } 128 } 129 }, label: { 130 price_description(product: product) 131 }) 132 .buttonStyle(GradientButtonStyle()) 133 } 134 135 Text("By subscribing to Damus Purple, you are accepting our [privacy policy](https://damus.io/privacy-policy.txt) and Apple's Standard [EULA](https://www.apple.com/legal/internet-services/itunes/dev/stdeula/)", comment: "Text explaining the terms and conditions of subscribing to Damus Purple. EULA stands for End User License Agreement.") 136 .foregroundColor(.white.opacity(0.6)) 137 .font(.caption) 138 .padding() 139 140 } 141 .padding() 142 } 143 144 func price_description(product: Product) -> some View { 145 let purple_type = DamusPurple.StoreKitManager.DamusPurpleType(rawValue: product.id) 146 return ( 147 HStack(spacing: 10) { 148 Text(purple_type?.label() ?? product.displayName) 149 Spacer() 150 if let non_discounted_price = purple_type?.non_discounted_price(product: product) { 151 Text(non_discounted_price) 152 .strikethrough() 153 .foregroundColor(DamusColors.white.opacity(0.5)) 154 } 155 Text(product.displayPrice) 156 .fontWeight(.bold) 157 } 158 ) 159 } 160 } 161 } 162 163 // MARK: - Helper structures 164 165 extension DamusPurpleView { 166 enum ProductState { 167 case loading 168 case loaded([Product]) 169 case failed 170 171 var products: [Product]? { 172 switch self { 173 case .loading: 174 return nil 175 case .loaded(let ps): 176 return ps 177 case .failed: 178 return nil 179 } 180 } 181 } 182 } 183 184 #Preview { 185 PurpleBackdrop { 186 DamusPurpleView.IAPProductStateView(products: .loaded([]), purchased: nil, account_uuid: UUID(), subscribe: {_ in }) 187 } 188 }