DepositAmountView.swift (10058B)
1 /* 2 * This file is part of GNU Taler, ©2022-25 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 DepositAmountV 13 struct DepositAmountView: View { 14 private let symLog = SymLogV(0) 15 let stack: CallStack 16 @Binding var balance: Balance? 17 @Binding var balanceIndex: Int 18 @Binding var amountLastUsed: Amount 19 @Binding var amountToTransfer: Amount 20 let amountAvailable: Amount 21 let paytoUri: String? 22 23 @EnvironmentObject private var controller: Controller 24 @EnvironmentObject private var model: WalletModel 25 @Environment(\.colorScheme) private var colorScheme 26 @Environment(\.colorSchemeContrast) private var colorSchemeContrast 27 @AppStorage("minimalistic") var minimalistic: Bool = false 28 29 @State var checkDepositResult: CheckDepositResponse? = nil 30 @State private var insufficient = false 31 @State private var feeAmount: Amount? = nil 32 @State private var feeStr: String = EMPTYSTRING 33 @State private var depositStarted = false 34 @State private var exchange: Exchange? = nil // wg. noFees 35 36 public static func navTitle(_ currency: String = EMPTYSTRING, 37 _ condition: Bool = false 38 ) -> String { 39 condition ? String(localized: "NavTitle_Deposit_Currency", 40 defaultValue: "Deposit \(currency)", 41 comment: "currencySymbol") 42 : String(localized: "NavTitle_Deposit", 43 defaultValue: "Deposit") 44 } 45 46 private func feeLabel(_ feeString: String) -> String { 47 feeString.count > 0 ? String(localized: "+ \(feeString) fee") 48 : EMPTYSTRING 49 } 50 51 private func feeIsNotZero() -> Bool? { 52 if let hasNoFees = exchange?.noFees { 53 if hasNoFees { 54 return nil // this exchange never has fees 55 } 56 } 57 return checkDepositResult == nil ? false 58 : true // TODO: !(feeAmount?.isZero ?? false) 59 } 60 61 @MainActor 62 private func computeFeeDeposit(_ amount: Amount) async -> ComputeFeeResult? { 63 if amount.isZero { 64 return ComputeFeeResult.zero() 65 } 66 let insufficient = (try? amount > amountAvailable) ?? true 67 if insufficient { 68 return ComputeFeeResult.insufficient() 69 } 70 // private func fee(ppCheck: CheckDepositResponse?) -> Amount? { 71 do { 72 // if let ppCheck { 73 // // Outgoing: fee = effective - raw 74 // feeAmount = try ppCheck.fees.coin + ppCheck.fees.wire + ppCheck.fees.refresh 75 // return feeAmount 76 // } 77 } catch { 78 79 } 80 return nil 81 } 82 83 private func buttonTitle(_ amount: Amount) -> String { 84 if let balance { 85 let amountWithCurrency = amount.formatted(balance.scopeInfo, isNegative: false, useISO: true) 86 return DepositAmountView.navTitle(amountWithCurrency.0, true) 87 } 88 return DepositAmountView.navTitle() // should never happen 89 } 90 91 @MainActor 92 private func startDeposit(_ scope: ScopeInfo) { 93 if let paytoUri { 94 depositStarted = true // don't run twice 95 Task { 96 symLog.log("Deposit") 97 if let result = try? await model.createDepositGroup(paytoUri, 98 scope: scope, 99 amount: amountToTransfer) { 100 symLog.log(result.transactionId) 101 if let txState = result.txState { 102 if txState.isKYC || txState.isKYCauth { 103 symLog.log("Deposit KYC") 104 105 } else { 106 // ViewState2.shared.popToRootView(stack.push()) 107 NotificationCenter.default.post(name: .TransactionDone, object: nil, userInfo: nil) 108 dismissTop(stack.push()) 109 } 110 } 111 } else { 112 depositStarted = false 113 } 114 } 115 } 116 } 117 118 var body: some View { 119 #if PRINT_CHANGES 120 let _ = Self._printChanges() 121 let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear 122 #endif 123 if depositStarted { 124 let message = String(localized: "Depositing...", comment: "loading") 125 LoadingView(stack: stack.push(), scopeInfo: nil, message: message) 126 .navigationBarBackButtonHidden(true) 127 .interactiveDismissDisabled() // can only use "Done" button to dismiss 128 .safeAreaInset(edge: .bottom) { 129 let buttonTitle = String(localized: "Abort", comment: "deposit") 130 Button(buttonTitle) { dismissTop(stack.push()) } 131 .buttonStyle(TalerButtonStyle(type: .prominent)) 132 .padding(.horizontal) 133 } 134 } else { Group { 135 if let balance { 136 let scopeInfo = balance.scopeInfo 137 let availableStr = amountAvailable.formatted(scopeInfo, isNegative: false) 138 139 let currencyInfo = controller.info(for: scopeInfo, controller.currencyTicker) 140 // let amountVoiceOver = amountToTransfer.formatted(scopeInfo, isNegative: false) 141 let insufficientLabel = String(localized: "You don't have enough \(currencyInfo.specs.name).") 142 // let insufficientLabel2 = String(localized: "but you only have \(available) to deposit.") 143 144 let disabled = insufficient || amountToTransfer.isZero 145 146 Text("Available: \(availableStr.0)") 147 .accessibilityLabel(Text("Available: \(availableStr.1)", comment: "a11y")) 148 .talerFont(.title3) 149 .frame(maxWidth: .infinity, alignment: .trailing) 150 .padding(.horizontal) 151 // .padding(.bottom, 2) 152 let a11yLabel = String(localized: "Amount to deposit:", comment: "a11y, no abbreviations") 153 let amountLabel = minimalistic ? String(localized: "Amount:") 154 : a11yLabel 155 CurrencyInputView(scope: scopeInfo, 156 amount: $amountToTransfer, 157 amountLastUsed: amountLastUsed, 158 available: amountAvailable, // enable shortcuts if available >= value 159 title: amountLabel, 160 a11yTitle: a11yLabel, 161 shortcutAction: nil) 162 .padding(.horizontal) 163 164 Text(insufficient ? insufficientLabel 165 : feeLabel(feeStr)) 166 .talerFont(.body) 167 .foregroundColor(insufficient ? WalletColors().attention 168 : (feeAmount?.isZero ?? true) ? WalletColors().secondary(colorScheme, colorSchemeContrast) 169 : WalletColors().negative) 170 .padding(4) 171 let hint = String(localized: "enabled when amount is non-zero, but not higher than your available amount", 172 comment: "a11y") 173 Button(buttonTitle(amountToTransfer)) { startDeposit(balance.scopeInfo) } // TODO: use exchange scope 174 .buttonStyle(TalerButtonStyle(type: .prominent, disabled: disabled || depositStarted)) 175 .padding(.horizontal) 176 .disabled(disabled || depositStarted) 177 .accessibilityHint(disabled ? hint : EMPTYSTRING) 178 } else { // no balance - Yikes 179 Text("No balance. There seems to be a problem with the database...") 180 } 181 } 182 .onAppear { 183 DebugViewC.shared.setViewID(VIEW_DEPOSIT, stack: stack.push()) 184 symLog.log("❗️ onAppear") 185 } 186 .onDisappear { 187 symLog.log("❗️ onDisappear") 188 } 189 190 // .task(id: amountToTransfer.value) { 191 // if let amountAvailable { 192 // do { 193 // insufficient = try amountToTransfer > amountAvailable 194 // } catch { 195 // print("Yikes❗️ insufficient failed❗️") 196 // insufficient = true 197 // } 198 // 199 // if insufficient { 200 // announce("\(amountVoiceOver), \(insufficientLabel2)") 201 // feeStr = EMPTYSTRING 202 // } 203 // } 204 // if !insufficient { 205 // if amountToTransfer.isZero { 206 // feeStr = EMPTYSTRING 207 // checkDepositResult = nil 208 // } else if let paytoUri { 209 // if let ppCheck = try? await model.checkDepositM(paytoUri, amount: amountToTransfer) { 210 // if let feeAmount = fee(ppCheck: ppCheck) { 211 // feeStr = feeAmount.formatted(scopeInfo, isNegative: false) 212 // let feeLabel = feeLabel(feeStr) 213 // announce("\(amountVoiceOver), \(feeLabel)") 214 // } else { 215 // feeStr = EMPTYSTRING 216 // announce(amountVoiceOver) 217 // } 218 // checkDepositResult = ppCheck 219 // } else { 220 // checkDepositResult = nil 221 // } 222 // } 223 // } 224 // } 225 } // else 226 } // body 227 } 228 // MARK: - 229 #if DEBUG 230 #endif