damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 1a131cd179e7f119bbbe74f79566b866610cbe74
parent ab529c43eb296437d96e70844451e03e1bd0f14b
Author: Daniel D’Aquino <daniel@daquino.me>
Date:   Wed, 14 Feb 2024 21:31:36 +0000

refactor: re-order items in DamusPurpleView

This is a purely non-functional refactor of DamusPurpleView consisting
only of code mark section comments, and re-ordering for better
organization and to facilitate further refactoring.

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mdamus/Views/Purple/DamusPurpleView.swift | 478+++++++++++++++++++++++++++++++++++++++++--------------------------------------
1 file changed, 246 insertions(+), 232 deletions(-)

diff --git a/damus/Views/Purple/DamusPurpleView.swift b/damus/Views/Purple/DamusPurpleView.swift @@ -10,6 +10,8 @@ import StoreKit fileprivate let damus_products = ["purpleyearly","purple"] +// MARK: - Helper structures + enum ProductState { case loading case loaded([Product]) @@ -48,6 +50,8 @@ struct PurchasedProduct { let product: Product } +// MARK: - Main view + struct DamusPurpleView: View { let damus_state: DamusState let keypair: Keypair @@ -68,6 +72,8 @@ struct DamusPurpleView: View { self.keypair = damus_state.keypair } + // MARK: - Top level view + var body: some View { NavigationView { ZStack { @@ -115,238 +121,7 @@ struct DamusPurpleView: View { .manageSubscriptionsSheet(isPresented: $show_manage_subscriptions) } - func load_account() async { - do { - if let account = try await damus_state.purple.fetch_account(pubkey: damus_state.keypair.pubkey) { - self.my_account_info_state = .loaded(account: account) - return - } - self.my_account_info_state = .no_account - return - } - catch { - self.my_account_info_state = .error(message: NSLocalizedString("There was an error loading your account. Please try again later. If problem persists, please contact us at support@damus.io", comment: "Error label when Purple account information fails to load")) - } - } - - func handle_transactions(products: [Product]) async { - for await update in StoreKit.Transaction.updates { - switch update { - case .verified(let tx): - let prod = products.filter({ prod in tx.productID == prod.id }).first - - if let prod, - let expiration = tx.expirationDate, - Date.now < expiration - { - self.purchased = PurchasedProduct(tx: tx, product: prod) - break - } - case .unverified: - continue - } - } - } - - func load_products() async { - do { - let products = try await Product.products(for: damus_products) - self.products = .loaded(products) - await handle_transactions(products: products) - - print("loaded products", products) - } catch { - self.products = .failed - print("Failed to fetch products: \(error.localizedDescription)") - } - } - - func IconOnBox(_ name: String) -> some View { - ZStack { - RoundedRectangle(cornerRadius: 20.0) - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 20.0)) - .frame(width: 80, height: 80) - .overlay( - RoundedRectangle(cornerRadius: 20) - .stroke(LinearGradient( - colors: [DamusColors.pink, .white.opacity(0), .white.opacity(0.5), .white.opacity(0)], - startPoint: .topLeading, - endPoint: .bottomTrailing), lineWidth: 1) - ) - - Image(name) - .resizable() - .frame(width: 50, height: 50) - .foregroundColor(.white) - } - } - - func Icon(_ name: String) -> some View { - Image(name) - .resizable() - .frame(width: 50, height: 50) - .foregroundColor(.white) - } - - func Title(_ txt: String) -> some View { - Text(txt) - .font(.title3) - .bold() - .foregroundColor(.white) - .padding(.bottom, 3) - } - - func Subtitle(_ txt: String) -> some View { - Text(txt) - .foregroundColor(.white.opacity(0.65)) - } - - var ProductLoadError: some View { - Text(NSLocalizedString("Subscription Error", comment: "Ah dang there was an error loading subscription information from the AppStore. Please try again later :(")) - .foregroundColor(.white) - } - - var SaveText: Text { - Text(NSLocalizedString("Save 14%", comment: "Percentage of purchase price the user will save")) - .font(.callout) - .italic() - .foregroundColor(DamusColors.green) - } - - func subscribe(_ product: Product) async throws { - let result = try await product.purchase() - switch result { - case .success(.verified(let tx)): - print("success \(tx.debugDescription)") - show_welcome_sheet = true - case .success(.unverified(let tx, let res)): - print("success unverified \(tx.debugDescription) \(res.localizedDescription)") - show_welcome_sheet = true - case .pending: - break - case .userCancelled: - break - @unknown default: - break - } - - switch result { - case .success: - // TODO (will): why do this here? - //self.damus_state.purple.starred_profiles_cache[keypair.pubkey] = nil - Task { - await self.damus_state.purple.send_receipt() - } - default: - break - } - } - - var product: Product? { - return self.products.products?.filter({ - prod in prod.id == selection.rawValue - }).first - } - - func price_description(product: Product) -> some View { - if product.id == "purpleyearly" { - return ( - AnyView( - HStack(spacing: 10) { - Text(NSLocalizedString("Annually", comment: "Annual renewal of purple subscription")) - Spacer() - Text(verbatim: non_discounted_price(product)).strikethrough().foregroundColor(DamusColors.white.opacity(0.5)) - Text(verbatim: product.displayPrice).fontWeight(.bold) - } - ) - ) - } else { - return ( - AnyView( - HStack(spacing: 10) { - Text(NSLocalizedString("Monthly", comment: "Monthly renewal of purple subscription")) - Spacer() - Text(verbatim: product.displayPrice).fontWeight(.bold) - } - ) - ) - } - } - - 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 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) - } - Button(action: { - show_manage_subscriptions = true - }, label: { - Text(NSLocalizedString("Manage", comment: "Manage the damus subscription")) - }) - .buttonStyle(GradientButtonStyle()) - } - } - - var ProductStateView: some View { - Group { - if damus_state.purple.enable_purple_iap_support { - switch self.products { - case .failed: - ProductLoadError - case .loaded(let products): - if let purchased { - PurchasedView(purchased) - } else { - ProductsView(products) - } - case .loading: - ProgressView() - .progressViewStyle(.circular) - } - } - } - } + // MARK: - Complex subviews var MainContent: some View { VStack { @@ -448,8 +223,247 @@ struct DamusPurpleView: View { .padding([.top], 20) } } + + var ProductStateView: some View { + Group { + if damus_state.purple.enable_purple_iap_support { + switch self.products { + case .failed: + ProductLoadError + 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) + } + Button(action: { + show_manage_subscriptions = true + }, label: { + Text(NSLocalizedString("Manage", comment: "Manage the damus subscription")) + }) + .buttonStyle(GradientButtonStyle()) + } + } + + 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 { + if product.id == "purpleyearly" { + return ( + AnyView( + HStack(spacing: 10) { + Text(NSLocalizedString("Annually", comment: "Annual renewal of purple subscription")) + Spacer() + Text(verbatim: non_discounted_price(product)).strikethrough().foregroundColor(DamusColors.white.opacity(0.5)) + Text(verbatim: product.displayPrice).fontWeight(.bold) + } + ) + ) + } else { + return ( + AnyView( + HStack(spacing: 10) { + Text(NSLocalizedString("Monthly", comment: "Monthly renewal of purple subscription")) + Spacer() + Text(verbatim: product.displayPrice).fontWeight(.bold) + } + ) + ) + } + } + + // MARK: - State management + + func load_account() async { + do { + if let account = try await damus_state.purple.fetch_account(pubkey: damus_state.keypair.pubkey) { + self.my_account_info_state = .loaded(account: account) + return + } + self.my_account_info_state = .no_account + return + } + catch { + self.my_account_info_state = .error(message: NSLocalizedString("There was an error loading your account. Please try again later. If problem persists, please contact us at support@damus.io", comment: "Error label when Purple account information fails to load")) + } + } + + func handle_transactions(products: [Product]) async { + for await update in StoreKit.Transaction.updates { + switch update { + case .verified(let tx): + let prod = products.filter({ prod in tx.productID == prod.id }).first + + if let prod, + let expiration = tx.expirationDate, + Date.now < expiration + { + self.purchased = PurchasedProduct(tx: tx, product: prod) + break + } + case .unverified: + continue + } + } + } + + func load_products() async { + do { + let products = try await Product.products(for: damus_products) + self.products = .loaded(products) + await handle_transactions(products: products) + + print("loaded products", products) + } catch { + self.products = .failed + print("Failed to fetch products: \(error.localizedDescription)") + } + } + + func subscribe(_ product: Product) async throws { + let result = try await product.purchase() + switch result { + case .success(.verified(let tx)): + print("success \(tx.debugDescription)") + show_welcome_sheet = true + case .success(.unverified(let tx, let res)): + print("success unverified \(tx.debugDescription) \(res.localizedDescription)") + show_welcome_sheet = true + case .pending: + break + case .userCancelled: + break + @unknown default: + break + } + + switch result { + case .success: + // TODO (will): why do this here? + //self.damus_state.purple.starred_profiles_cache[keypair.pubkey] = nil + Task { + await self.damus_state.purple.send_receipt() + } + default: + break + } + } + + var product: Product? { + return self.products.products?.filter({ + prod in prod.id == selection.rawValue + }).first + } + + // MARK: - Small helper views + + func IconOnBox(_ name: String) -> some View { + ZStack { + RoundedRectangle(cornerRadius: 20.0) + .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 20.0)) + .frame(width: 80, height: 80) + .overlay( + RoundedRectangle(cornerRadius: 20) + .stroke(LinearGradient( + colors: [DamusColors.pink, .white.opacity(0), .white.opacity(0.5), .white.opacity(0)], + startPoint: .topLeading, + endPoint: .bottomTrailing), lineWidth: 1) + ) + + Image(name) + .resizable() + .frame(width: 50, height: 50) + .foregroundColor(.white) + } + } + + func Icon(_ name: String) -> some View { + Image(name) + .resizable() + .frame(width: 50, height: 50) + .foregroundColor(.white) + } + + func Title(_ txt: String) -> some View { + Text(txt) + .font(.title3) + .bold() + .foregroundColor(.white) + .padding(.bottom, 3) + } + + func Subtitle(_ txt: String) -> some View { + Text(txt) + .foregroundColor(.white.opacity(0.65)) + } + + var ProductLoadError: some View { + Text(NSLocalizedString("Subscription Error", comment: "Ah dang there was an error loading subscription information from the AppStore. Please try again later :(")) + .foregroundColor(.white) + } + + var SaveText: Text { + Text(NSLocalizedString("Save 14%", comment: "Percentage of purchase price the user will save")) + .font(.callout) + .italic() + .foregroundColor(DamusColors.green) + } } +// MARK: - More helper views + struct DamusPurpleLogoView: View { var body: some View { HStack(spacing: 20) {