DepositAmountAction.swift (7756B)
1 /* 2 * This file is part of GNU Taler, ©2022-26 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Marc Stibane 7 */ 8 import SwiftUI 9 import taler_swift 10 import SymLog 11 12 // Called from DepositSelectV -> BankSectionView 13 struct DepositAmountAction: View { 14 private let symLog = SymLogV(0) 15 let stack: CallStack 16 // when Action is tapped while in currency TransactionList… 17 let selectedBalance: Balance? // …then use THIS balance, otherwise show picker 18 @Binding var amountLastUsed: Amount 19 let paytoUri: String? 20 let label: String? 21 22 @EnvironmentObject private var controller: Controller 23 @EnvironmentObject private var model: WalletModel 24 25 @State private var balanceIndex = 0 26 @State private var balance: Balance? = nil // nil only when balances == [] 27 @State private var currencyInfo: CurrencyInfo = CurrencyInfo.zero(UNKNOWN) 28 @State private var amountToTransfer = Amount.zero(currency: EMPTYSTRING) // Update currency when used 29 @State private var amountAvailable = Amount.zero(currency: EMPTYSTRING) // GetMaxPeerPushAmount 30 @State private var depositStarted = false 31 @State private var txId: String? = nil 32 @State private var talerTX = TalerTransaction(dummyCurrency: DEMOCURRENCY) 33 34 @MainActor 35 private func viewDidLoad() async { 36 let balances = controller.balances 37 if let selectedBalance { 38 let disableDeposits = selectedBalance.disableDirectDeposits ?? false 39 if disableDeposits { 40 // find another balance 41 balance = Balance.firstwithDeposit(balances) 42 } else { 43 balance = selectedBalance 44 } 45 } else { 46 balance = Balance.firstwithDeposit(balances) 47 } 48 // selectedBalance is a snapshot taken when the transaction list appeared, so its 49 // amounts may already be stale - match on the scope instead of the whole value, 50 // otherwise the picker would show balances[0] while we deposit from another scope. 51 if let balance, 52 let index = balances.firstIndex(where: { $0.scopeInfo == balance.scopeInfo }) { 53 balanceIndex = index 54 self.balance = balances[index] // refresh the snapshot 55 } else { 56 balanceIndex = 0 57 balance = balances.isEmpty ? nil : balances[0] 58 } 59 } 60 61 @MainActor 62 private func newBalance() async { 63 // runs whenever the user changes the exchange via ScopePicker, or on new currencyInfo 64 symLog.log("❗️ task \(balanceIndex)") 65 if let balance { 66 let scope = balance.scopeInfo 67 amountToTransfer.setCurrency(scope.currency) 68 currencyInfo = controller.info(for: scope, controller.currencyTicker) 69 do { 70 amountAvailable = try await model.getMaxDepositAmount(scope) 71 } catch { 72 // TODO: Error 73 amountAvailable = balance.available 74 } 75 } 76 } 77 78 @MainActor 79 private func startDeposit(_ scope: ScopeInfo) { 80 if let paytoUri { 81 depositStarted = true // don't run twice 82 Task { 83 symLog.log("Deposit") 84 if let result = try? await model.createDepositGroup(paytoUri, 85 scope: scope, 86 amount: amountToTransfer) { 87 symLog.log(result.transactionId) 88 txId = result.transactionId 89 } else { 90 depositStarted = false 91 } 92 } 93 } 94 } 95 96 var body: some View { 97 #if PRINT_CHANGES 98 let _ = Self._printChanges() 99 #endif 100 if let txId { 101 TransactionSummaryList(stack: stack.push(), 102 transactionId: txId, 103 talerTX: $talerTX, 104 navTitle: nil, 105 hasDone: true, // conclude payment 106 showDone: .prominent, // TODO: isKYC ? .bordered : .prominent 107 url: nil, 108 withActions: false) 109 } else if depositStarted { 110 let message = String(localized: "Depositing...", comment: "loading") 111 LoadingView(stack: stack.push(), scopeInfo: nil, message: message) 112 .navigationBarBackButtonHidden(true) 113 .interactiveDismissDisabled() // can only use "Abort" button to dismiss 114 .safeAreaInset(edge: .bottom) { 115 let buttonTitle = String(localized: "Abort", comment: "deposit") 116 Button(buttonTitle) { dismissTop(stack.push()) } 117 .buttonStyle(TalerButtonStyle(type: .prominent)) 118 .padding(.horizontal) 119 } 120 } else { 121 let count = controller.balances.count 122 let _ = symLog.log("count = \(count)") 123 let currencySymbol = currencyInfo.symbol 124 let navA11y = DepositAmountView.navTitle(currencyInfo.name, true) // always include currency for a11y 125 let navTitle = DepositAmountView.navTitle(currencySymbol, currencyInfo.hasSymbol) 126 let scrollView = ScrollView { 127 if let label { 128 Text("to \(label)") 129 .talerFont(.picker) 130 .frame(maxWidth: .infinity, alignment: .leading) 131 .padding(.horizontal) 132 } 133 if count > 1 { 134 ScopePicker(stack: stack.push(), 135 value: $balanceIndex, 136 onlyNonZero: true) // can only send what exists 137 { index in 138 balanceIndex = index 139 balance = controller.balances[index] 140 } 141 .padding(.horizontal) 142 .padding(.bottom, 4) 143 } // TODO: else show static text? 144 DepositAmountView(stack: stack.push(), 145 balance: $balance, 146 balanceIndex: $balanceIndex, 147 amountLastUsed: $amountLastUsed, 148 amountToTransfer: $amountToTransfer, 149 amountAvailable: amountAvailable, 150 depositStarted: depositStarted 151 ) { 152 if let balance { 153 startDeposit(balance.scopeInfo) 154 } 155 } 156 .onAppear { 157 DebugViewC.shared.setViewID(VIEW_DEPOSIT, stack: stack.push()) 158 symLog.log("❗️ onAppear") 159 } 160 .onDisappear { 161 symLog.log("❗️ onDisappear") 162 } 163 } // ScrollView 164 .navigationTitle(navTitle) 165 .frame(maxWidth: .infinity, alignment: .leading) 166 .background(FullBackground()) 167 // .ignoresSafeArea(.keyboard, edges: .bottom) 168 .task { 169 setVoice(to: nil) 170 await viewDidLoad() 171 } 172 .task(id: balanceIndex + (1000 * controller.currencyTicker)) { await newBalance() } 173 174 if #available(iOS 16.4, *) { 175 scrollView.toolbar(.hidden, for: .tabBar) 176 .scrollBounceBehavior(.basedOnSize) 177 } else { 178 scrollView 179 } 180 } // else 181 } 182 }