Files
damus/damus/Views/Purple/DamusPurpleView.swift
William Casarin c6e37bd864 refactor: introduce PurpleBackdrop
We mainly want to use this to improve previews for now, but might be
useful in the future.

Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-04 14:03:53 +00:00

223 lines
7.8 KiB
Swift

//
// 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(String(format: NSLocalizedString("There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@", comment: "In-app purchase error message for the user"), iap_error))
.foregroundStyle(.red)
.multilineTextAlignment(.center)
.padding(.horizontal)
}
}
else {
ProgressView()
.progressViewStyle(.circular)
}
}
}
else {
ManageOnWebsiteNote
}
}
}
var ManageOnWebsiteNote: some View {
Text(NSLocalizedString("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)
}
}