// // DamusPurpleView.swift // damus // // Created by William Casarin on 2023-03-21. // import SwiftUI import StoreKit // MARK: - Helper structures enum AccountInfoState { case loading case loaded(account: DamusPurple.Account) case no_account case error(message: String) } // MARK: - Main view struct DamusPurpleView: View, DamusPurpleStoreKitManagerDelegate { let damus_state: DamusState let keypair: Keypair @State var my_account_info_state: AccountInfoState = .loading @State var products: ProductState @State var purchased: PurchasedProduct? = nil @State var selection: DamusPurple.StoreKitManager.DamusPurpleType = .yearly @State var show_welcome_sheet: Bool = false @State var account_uuid: UUID? = nil @State var iap_error: String? = nil // TODO: Display this error to the user in some way. @State private var shouldDismissView = false @Environment(\.dismiss) var dismiss init(damus_state: DamusState) { self._products = State(wrappedValue: .loading) self.damus_state = damus_state self.keypair = damus_state.keypair } // MARK: - Top level view var body: some View { NavigationView { PurpleBackdrop { ScrollView { MainContent .padding(.top, 75) } } .navigationBarHidden(true) .navigationBarTitleDisplayMode(.inline) .navigationBarBackButtonHidden(true) .navigationBarItems(leading: BackNav()) } .onReceive(handle_notify(.switched_timeline)) { _ in dismiss() } .onAppear { notify(.display_tabbar(false)) Task { await self.load_account() // Assign this view as the delegate for the storekit manager to receive purchase updates damus_state.purple.storekit_manager.delegate = self // Fetch the account UUID to use for IAP purchases and to check if an IAP purchase is associated with the account self.account_uuid = try await damus_state.purple.get_maybe_cached_uuid_for_account() } } .onDisappear { notify(.display_tabbar(true)) } .onReceive(handle_notify(.purple_account_update), perform: { account in self.my_account_info_state = .loaded(account: account) }) .task { await load_products() } .ignoresSafeArea(.all) .sheet(isPresented: $show_welcome_sheet, onDismiss: { shouldDismissView = true }, content: { DamusPurpleNewUserOnboardingView(damus_state: damus_state) }) } // MARK: - Complex subviews var MainContent: some View { VStack { DamusPurpleView.LogoView() switch my_account_info_state { case .loading: ProgressView() .progressViewStyle(.circular) case .loaded(let account): Group { DamusPurpleAccountView(damus_state: damus_state, account: account) ProductStateView(account: account) } case .no_account: MarketingContent case .error(let message): Text(message) .foregroundStyle(.red) .multilineTextAlignment(.center) .padding() } Spacer() } } var MarketingContent: some View { VStack { DamusPurpleView.MarketingContentView(purple: damus_state.purple) VStack(alignment: .center) { ProductStateView(account: nil) } .padding([.top], 20) } } func ProductStateView(account: DamusPurple.Account?) -> some View { Group { if damus_state.purple.enable_purple_iap_support { if account?.active == true && purchased == nil { // Account active + no IAP purchases = Bought through Lightning. // Instruct the user to manage billing on the website ManageOnWebsiteNote } else { // If account is no longer active or was purchased via IAP, then show IAP purchase/manage options if let account_uuid { DamusPurpleView.IAPProductStateView(products: products, purchased: purchased, account_uuid: account_uuid, subscribe: subscribe) if let iap_error { Text("There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: \(iap_error)", comment: "In-app purchase error message for the user") .foregroundStyle(.red) .multilineTextAlignment(.center) .padding(.horizontal) } } else { ProgressView() .progressViewStyle(.circular) } } } else { ManageOnWebsiteNote } } } var ManageOnWebsiteNote: some View { Text("Visit the Damus website on a web browser to manage billing", comment: "Instruction on how to manage billing externally") .font(.caption) .foregroundColor(.white.opacity(0.6)) .multilineTextAlignment(.center) } // 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 load_products() async { do { let products = try await self.damus_state.purple.storekit_manager.get_products() self.products = .loaded(products) print("loaded products", products) } catch { self.products = .failed print("Failed to fetch products: \(error.localizedDescription)") } } // For DamusPurple.StoreKitManager.Delegate conformance. This gets called by the StoreKitManager when a new product was purchased func product_was_purchased(product: DamusPurple.StoreKitManager.PurchasedProduct) { self.purchased = product } func subscribe(_ product: Product) async throws { do { try await self.damus_state.purple.make_iap_purchase(product: product) show_welcome_sheet = true } catch(let error) { self.iap_error = error.localizedDescription } } } struct DamusPurpleView_Previews: PreviewProvider { static var previews: some View { /* DamusPurpleView(products: [ DamusProduct(name: "Yearly", id: "purpleyearly", price: Decimal(69.99)), DamusProduct(name: "Monthly", id: "purple", price: Decimal(6.99)), ]) */ DamusPurpleView(damus_state: test_damus_state) } }