Resolves a race condition in wallet data fetching that caused views to hang on loading placeholders. The issue occurred due to: 1. Multiple state updates triggering view re-renders mid-fetch 2. Refreshable tasks getting cancelled before completion Changes: - Remove premature state reset in refreshWalletInformation() - Atomically update balance and transactions together after fetching - Replace onAppear + manual task cancellation with SwiftUI .task modifier - Simplify refresh flow to use proper async/await without explicit task management This ensures the wallet view completes data loading in a single atomic operation, preventing intermediate loading states from persisting. Closes: https://github.com/damus-io/damus/issues/2999 Changelog-Fixed: Wallet view no longer hangs on loading placeholder Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
151 lines
6.5 KiB
Swift
151 lines
6.5 KiB
Swift
//
|
|
// WalletView.swift
|
|
// damus
|
|
//
|
|
// Created by William Casarin on 2023-05-05.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
let WALLET_WARNING_THRESHOLD: UInt64 = 100000
|
|
|
|
struct WalletView: View {
|
|
let damus_state: DamusState
|
|
@State var show_settings: Bool = false
|
|
@State var show_send_sheet: Bool = false
|
|
@ObservedObject var model: WalletModel
|
|
@ObservedObject var settings: UserSettingsStore
|
|
@State private var showBalance: Bool = false
|
|
|
|
init(damus_state: DamusState, model: WalletModel? = nil) {
|
|
self.damus_state = damus_state
|
|
self._model = ObservedObject(wrappedValue: model ?? damus_state.wallet)
|
|
self._settings = ObservedObject(wrappedValue: damus_state.settings)
|
|
}
|
|
|
|
func MainWalletView(nwc: WalletConnectURL) -> some View {
|
|
ScrollView {
|
|
VStack(spacing: 35) {
|
|
if let balance = model.balance, balance > WALLET_WARNING_THRESHOLD && !settings.dismiss_wallet_high_balance_warning {
|
|
VStack(spacing: 10) {
|
|
HStack {
|
|
Image(systemName: "exclamationmark.circle")
|
|
Text("Safety Reminder", comment: "Heading for a safety reminder that appears when the user has too many funds, recommending them to learn about safeguarding their funds.")
|
|
.font(.title3)
|
|
.bold()
|
|
}
|
|
.foregroundStyle(.damusWarningTertiary)
|
|
|
|
Text("If your wallet balance is getting high, it's important to understand how to keep your funds secure. Please consider learning the best practices to ensure your assets remain safe. [Click here](https://damus.io/docs/wallet/high-balance-safety-reminder/) to learn more.", comment: "Text reminding the user has a high balance, recommending them to learn about self-custody")
|
|
.foregroundStyle(.damusWarningSecondary)
|
|
.accentColor(.damusWarningTertiary)
|
|
.opacity(0.8)
|
|
|
|
Button(action: {
|
|
settings.dismiss_wallet_high_balance_warning = true
|
|
}, label: {
|
|
Text("Dismiss", comment: "Button label to dismiss the safety reminder that the user's wallet has a high balance")
|
|
})
|
|
.bold()
|
|
.foregroundStyle(.damusWarningTertiary)
|
|
}
|
|
.privacySensitive()
|
|
.padding()
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 20)
|
|
.stroke(.damusWarningBorder, lineWidth: 1)
|
|
)
|
|
}
|
|
|
|
VStack(spacing: 5) {
|
|
|
|
BalanceView(balance: model.balance, hide_balance: $settings.hide_wallet_balance)
|
|
|
|
Button(action: {
|
|
show_send_sheet = true
|
|
}) {
|
|
HStack {
|
|
Image(systemName: "paperplane.fill")
|
|
Text("Send", comment: "Button label to send bitcoin payment from wallet")
|
|
.font(.headline)
|
|
}
|
|
.padding(.horizontal, 10)
|
|
}
|
|
.buttonStyle(GradientButtonStyle())
|
|
.padding(.bottom, 20)
|
|
|
|
TransactionsView(damus_state: damus_state, transactions: model.transactions, hide_balance: $settings.hide_wallet_balance)
|
|
}
|
|
}
|
|
.navigationTitle(NSLocalizedString("Wallet", comment: "Navigation title for Wallet view"))
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.padding()
|
|
.padding(.bottom, 50)
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
switch model.connect_state {
|
|
case .new:
|
|
ConnectWalletView(model: model, nav: damus_state.nav, userKeypair: self.damus_state.keypair)
|
|
case .none:
|
|
ConnectWalletView(model: model, nav: damus_state.nav, userKeypair: self.damus_state.keypair)
|
|
case .existing(let nwc):
|
|
MainWalletView(nwc: nwc)
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
Button(
|
|
action: { show_settings = true },
|
|
label: {
|
|
Image("settings")
|
|
.foregroundColor(.gray)
|
|
}
|
|
)
|
|
}
|
|
}
|
|
.task {
|
|
await self.refreshWalletInformation()
|
|
}
|
|
.refreshable {
|
|
await self.refreshWalletInformation()
|
|
}
|
|
.sheet(isPresented: $show_settings, onDismiss: { self.show_settings = false }) {
|
|
ScrollView {
|
|
NWCSettings(damus_state: damus_state, nwc: nwc, model: model, settings: settings)
|
|
.padding(.top, 30)
|
|
}
|
|
.presentationDragIndicator(.visible)
|
|
.presentationDetents([.large])
|
|
}
|
|
.sheet(isPresented: $show_send_sheet) {
|
|
SendPaymentView(damus_state: damus_state, model: model, nwc: nwc)
|
|
.presentationDragIndicator(.visible)
|
|
.presentationDetents([.large])
|
|
}
|
|
}
|
|
}
|
|
|
|
func refreshWalletInformation() async {
|
|
do {
|
|
try await self.model.refreshWalletInformation()
|
|
}
|
|
catch {
|
|
guard let error = error as? ErrorView.UserPresentableErrorProtocol else {
|
|
Log.error("Error while refreshing wallet: %s", for: .nwc, error.localizedDescription)
|
|
return
|
|
}
|
|
present_sheet(.error(error.userPresentableError))
|
|
}
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
let test_wallet_connect_url = WalletConnectURL(pubkey: test_pubkey, relay: .init("wss://relay.damus.io")!, keypair: test_damus_state.keypair.to_full()!, lud16: "jb55@sendsats.com")
|
|
|
|
struct WalletView_Previews: PreviewProvider {
|
|
static let tds = test_damus_state
|
|
static var previews: some View {
|
|
WalletView(damus_state: tds, model: WalletModel(state: .existing(test_wallet_connect_url), settings: tds.settings))
|
|
}
|
|
}
|