mirror of
git://jb55.com/damus
synced 2024-09-30 00:40:45 +00:00
Merge branch 'iap-improvements'
Pull a few patches from v1.7-rc1 purple: show welcome sheet after ln payment iap: add loading spinner to purchase actions
This commit is contained in:
commit
94f7e4d1e1
@ -29,6 +29,7 @@ enum Sheets: Identifiable {
|
||||
case user_status
|
||||
case onboardingSuggestions
|
||||
case purple(DamusPurpleURL)
|
||||
case purple_onboarding
|
||||
|
||||
static func zap(target: ZapTarget, lnurl: String) -> Sheets {
|
||||
return .zap(ZapSheet(target: target, lnurl: lnurl))
|
||||
@ -50,6 +51,7 @@ enum Sheets: Identifiable {
|
||||
case .filter: return "filter"
|
||||
case .onboardingSuggestions: return "onboarding-suggestions"
|
||||
case .purple(let purple_url): return "purple" + purple_url.url_string()
|
||||
case .purple_onboarding: return "purple_onboarding"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -334,6 +336,8 @@ struct ContentView: View {
|
||||
OnboardingSuggestionsView(model: SuggestedUsersViewModel(damus_state: damus_state!))
|
||||
case .purple(let purple_url):
|
||||
DamusPurpleURLSheetView(damus_state: damus_state!, purple_url: purple_url)
|
||||
case .purple_onboarding:
|
||||
DamusPurpleNewUserOnboardingView(damus_state: damus_state)
|
||||
}
|
||||
}
|
||||
.onOpenURL { url in
|
||||
@ -343,12 +347,26 @@ struct ContentView: View {
|
||||
}
|
||||
|
||||
switch res {
|
||||
case .filter(let filt): self.open_search(filt: filt)
|
||||
case .profile(let pk): self.open_profile(pubkey: pk)
|
||||
case .event(let ev): self.open_event(ev: ev)
|
||||
case .wallet_connect(let nwc): self.open_wallet(nwc: nwc)
|
||||
case .script(let data): self.open_script(data)
|
||||
case .purple(let purple_url): self.active_sheet = .purple(purple_url)
|
||||
case .filter(let filt): self.open_search(filt: filt)
|
||||
case .profile(let pk): self.open_profile(pubkey: pk)
|
||||
case .event(let ev): self.open_event(ev: ev)
|
||||
case .wallet_connect(let nwc): self.open_wallet(nwc: nwc)
|
||||
case .script(let data): self.open_script(data)
|
||||
case .purple(let purple_url):
|
||||
if case let .welcome(checkout_id) = purple_url.variant {
|
||||
// If this is a welcome link, do the following before showing the onboarding screen:
|
||||
// 1. Check if this is legitimate and good to go.
|
||||
// 2. Mark as complete if this is good to go.
|
||||
Task {
|
||||
let is_good_to_go = try? await damus_state.purple.check_and_mark_ln_checkout_is_good_to_go(checkout_id: checkout_id)
|
||||
if is_good_to_go == true {
|
||||
self.active_sheet = .purple(purple_url)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
self.active_sheet = .purple(purple_url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -468,6 +486,21 @@ struct ContentView: View {
|
||||
} else {
|
||||
print("txn: NOSTRDB FAILED TO REOPEN closed:\(damus_state.ndb.is_closed)")
|
||||
}
|
||||
if damus_state.purple.checkout_ids_in_progress.count > 0 {
|
||||
// For extra assurance, run this after one second, to avoid race conditions if the app is also handling a damus purple welcome url.
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
Task {
|
||||
// TODO: Improve UX for renewals (#2013)
|
||||
let freshly_completed_checkout_ids = try? await damus_state.purple.check_status_of_checkouts_in_progress()
|
||||
let there_is_a_completed_checkout: Bool = (freshly_completed_checkout_ids?.count ?? 0) > 0
|
||||
let account_info = try await damus_state.purple.fetch_account(pubkey: self.keypair.pubkey)
|
||||
if there_is_a_completed_checkout == true && account_info?.active == true {
|
||||
// Show welcome sheet
|
||||
self.active_sheet = .purple_onboarding
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase) { (phase: ScenePhase) in
|
||||
guard let damus_state else { return }
|
||||
|
@ -12,6 +12,7 @@ class DamusPurple: StoreObserverDelegate {
|
||||
let settings: UserSettingsStore
|
||||
let keypair: Keypair
|
||||
var storekit_manager: StoreKitManager
|
||||
var checkout_ids_in_progress: Set<String> = []
|
||||
|
||||
@MainActor
|
||||
var account_cache: [Pubkey: Account]
|
||||
@ -243,6 +244,65 @@ class DamusPurple: StoreObserverDelegate {
|
||||
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func fetch_ln_checkout_object(checkout_id: String) async throws -> LNCheckoutInfo? {
|
||||
let url = environment.api_base_url().appendingPathComponent("ln-checkout/\(checkout_id)")
|
||||
|
||||
let (data, response) = try await make_nip98_authenticated_request(
|
||||
method: .get,
|
||||
url: url,
|
||||
payload: nil,
|
||||
payload_type: nil,
|
||||
auth_keypair: self.keypair
|
||||
)
|
||||
|
||||
if let httpResponse = response as? HTTPURLResponse {
|
||||
switch httpResponse.statusCode {
|
||||
case 200:
|
||||
return try JSONDecoder().decode(LNCheckoutInfo.self, from: data)
|
||||
case 404:
|
||||
return nil
|
||||
default:
|
||||
throw PurpleError.http_response_error(status_code: httpResponse.statusCode, response: data)
|
||||
}
|
||||
}
|
||||
throw PurpleError.error_processing_response
|
||||
}
|
||||
|
||||
@MainActor
|
||||
/// This function checks the status of all checkout objects in progress with the server, and it does two things:
|
||||
/// - It returns the ones that were freshly completed
|
||||
/// - It internally marks them as "completed"
|
||||
/// Important note: If you call this function, you must use the result, as those checkouts will not be returned the next time you call this function
|
||||
///
|
||||
/// - Returns: An array of checkout objects that have been successfully completed.
|
||||
func check_status_of_checkouts_in_progress() async throws -> [String] {
|
||||
var freshly_completed_checkouts: [String] = []
|
||||
for checkout_id in self.checkout_ids_in_progress {
|
||||
let checkout_info = try await self.fetch_ln_checkout_object(checkout_id: checkout_id)
|
||||
if checkout_info?.is_all_good() == true {
|
||||
freshly_completed_checkouts.append(checkout_id)
|
||||
}
|
||||
if checkout_info?.completed == true {
|
||||
self.checkout_ids_in_progress.remove(checkout_id)
|
||||
}
|
||||
}
|
||||
return freshly_completed_checkouts
|
||||
}
|
||||
|
||||
@MainActor
|
||||
/// This function checks the status of a specific checkout id with the server
|
||||
/// You should use this result immediately, since it will internally be marked as handled
|
||||
///
|
||||
/// - Returns: true if this checkout is all good to go. false if not. nil if checkout was not found.
|
||||
func check_and_mark_ln_checkout_is_good_to_go(checkout_id: String) async throws -> Bool? {
|
||||
let checkout_info = try await self.fetch_ln_checkout_object(checkout_id: checkout_id)
|
||||
if checkout_info?.completed == true {
|
||||
self.checkout_ids_in_progress.remove(checkout_id) // Remove if from the list of checkouts in progress
|
||||
}
|
||||
return checkout_info?.is_all_good()
|
||||
}
|
||||
|
||||
struct Account {
|
||||
let pubkey: Pubkey
|
||||
let created_at: Date
|
||||
@ -293,6 +353,44 @@ extension DamusPurple {
|
||||
let active: Bool
|
||||
}
|
||||
|
||||
struct LNCheckoutInfo: Codable {
|
||||
// Note: Swift will decode a JSON full of extra fields into a Struct with only a subset of them, but not the other way around
|
||||
// Therefore, to avoid compatibility concerns and complexity, we should only use the fields we need
|
||||
// The ones we do not need yet will be left commented out until we need them.
|
||||
let id: UUID
|
||||
/*
|
||||
let product_template_name: String
|
||||
let verified_pubkey: String?
|
||||
*/
|
||||
let invoice: Invoice?
|
||||
let completed: Bool
|
||||
|
||||
|
||||
struct Invoice: Codable {
|
||||
/*
|
||||
let bolt11: String
|
||||
let label: String
|
||||
let connection_params: ConnectionParams
|
||||
*/
|
||||
let paid: Bool?
|
||||
|
||||
/*
|
||||
struct ConnectionParams: Codable {
|
||||
let nodeid: String
|
||||
let address: String
|
||||
let rune: String
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// Indicates whether this checkout is all good to go.
|
||||
/// The checkout is good to go if it is marked as complete and the invoice has been successfully paid
|
||||
/// - Returns: true if this checkout is all good to go. false otherwise
|
||||
func is_all_good() -> Bool {
|
||||
return self.completed == true && self.invoice?.paid == true
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct AccountUUIDInfo: Codable {
|
||||
let account_uuid: UUID
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ struct DamusPurpleVerifyNpubView: View {
|
||||
Button(action: {
|
||||
Task {
|
||||
try await damus_state.purple.verify_npub_for_checkout(checkout_id: checkout_id)
|
||||
damus_state.purple.checkout_ids_in_progress.insert(checkout_id)
|
||||
verified = true
|
||||
}
|
||||
}, label: {
|
||||
|
@ -21,20 +21,32 @@ extension DamusPurpleView {
|
||||
let subscribe: (Product) async throws -> Void
|
||||
|
||||
@State var show_manage_subscriptions = false
|
||||
@State var subscription_purchase_loading = false
|
||||
|
||||
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:
|
||||
if subscription_purchase_loading {
|
||||
HStack(spacing: 10) {
|
||||
Text(NSLocalizedString("Purchasing", comment: "Loading label indicating the purchase action is in progress"))
|
||||
.foregroundStyle(.white)
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
.tint(.white)
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch self.products {
|
||||
case .failed:
|
||||
PurpleViewPrimitives.ProductLoadErrorView()
|
||||
case .loaded(let products):
|
||||
if let purchased {
|
||||
PurchasedView(purchased)
|
||||
} else {
|
||||
ProductsView(products)
|
||||
}
|
||||
case .loading:
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,7 +119,9 @@ extension DamusPurpleView {
|
||||
Button(action: {
|
||||
Task { @MainActor in
|
||||
do {
|
||||
subscription_purchase_loading = true
|
||||
try await subscribe(product)
|
||||
subscription_purchase_loading = false
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user