BankEditView.swift (9765B)
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 /// This view shows the currency name in an exchange section 13 /// currency 14 struct BankEditView: View { 15 private let symLog = SymLogV(0) 16 let stack: CallStack 17 let accountID: String? 18 19 @EnvironmentObject private var model: WalletModel 20 @EnvironmentObject private var controller: Controller 21 @AppStorage("minimalistic") var minimalistic: Bool = false 22 @AppStorage("demoHints") var demoHints: Bool = true 23 // @AppStorage("fakeNoFees") var fakeNoFees: Bool = true TODO: use this flag! 24 @AppStorage("ownerName") var ownerName: String = EMPTYSTRING 25 26 @State private var shouldReloadBalances: Int = 0 27 @State private var currencyInfo: CurrencyInfo = CurrencyInfo.zero(UNKNOWN) 28 @State private var didDelete: Bool = false 29 @State private var disabled: Bool = false 30 @State private var showAlert: Bool = false 31 32 @State private var myAccount: String? = nil 33 @State private var accountHolder: String = EMPTYSTRING 34 @State private var iban: String = EMPTYSTRING 35 @State private var xTaler: String = EMPTYSTRING 36 @State private var accountLabel: String = EMPTYSTRING 37 @State private var paytoType: PaytoType = .iban 38 @State private var selected = 0 39 @State private var kycCompleted = false 40 41 @FocusState private var focus:FocusedField? 42 43 enum FocusedField: Hashable { 44 case accountLabel, accountHolder, iban, xTaler 45 } 46 47 // @MainActor 48 // private func validateIban() async { 49 // if (try? await model.validateIban(iban)) == true { 50 // let payto = "payto://iban/\(iban)?receiver-name=\(accountHolder)" 51 // paytoUri = payto.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! 52 // } else { 53 // paytoUri = nil 54 // } 55 // } 56 57 @MainActor 58 private func viewDidLoad() async { 59 if let accountID { 60 if let account = try? await model.getBankAccountById(accountID) { 61 let payTo = PayTo(account.paytoUri) 62 iban = payTo.iban ?? EMPTYSTRING 63 xTaler = payTo.xTaler ?? EMPTYSTRING 64 if iban.count < 1 && xTaler.count > 1 { 65 paytoType = .xTalerBank 66 } 67 accountLabel = account.label ?? EMPTYSTRING 68 kycCompleted = account.kycCompleted 69 accountHolder = payTo.receiver ?? ownerName 70 } 71 } else { 72 73 } 74 } 75 76 @MainActor 77 private func updateAccount() async { 78 let payto = "payto://iban/\(iban)?receiver-name=\(accountHolder)" // TODO: convert BBAN to IBAN 79 let paytoUri = payto.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! 80 81 symLog.log(paytoUri) 82 if let account = try? await model.addBankAccount(paytoUri, 83 label: accountLabel, 84 replace: myAccount ?? accountID 85 ) { 86 symLog.log(account) 87 myAccount = account 88 } else { 89 symLog.log("error addBankAccount") 90 } 91 } 92 93 @MainActor 94 private func deleteAccount() { 95 disabled = true // don't try this more than once 96 Task { // runs on MainActor 97 if let id = myAccount ?? accountID { 98 if let _ = try? await model.forgetBankAccount(id) { 99 symLog.log("forgot \(id)") 100 didDelete = true // change button text 101 // NotificationCenter.default.post(name: .ExchangeDeleted, object: nil, userInfo: nil) 102 // NotificationCenter.default.post(name: .BalanceChange, object: nil, userInfo: nil) 103 dismissTop(stack.push()) 104 } else { 105 showAlert = true 106 disabled = false 107 } 108 } 109 } 110 } 111 112 var body: some View { 113 #if PRINT_CHANGES 114 let _ = Self._printChanges() 115 // let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear 116 #endif 117 let titleStr = String(localized: "Done", comment: "Done button") 118 let a11yLabelStr = String(localized: "Done", comment: "a11y for the Done button") 119 let doneButton = DoneButton(titleStr: titleStr, accessibilityLabelStr: a11yLabelStr) { 120 dismissTop(stack.push()) 121 } 122 123 let methods = [PaytoType.iban, PaytoType.xTalerBank] 124 List { 125 if kycCompleted // || true 126 { Section { 127 Text("If you change the account holder name or the IBAN, you may have to perform the legitimization procedure again.") // TODO: BBAN 128 .talerFont(.body) 129 } 130 } 131 Section { 132 let labelTitle = String(localized: "Label") 133 let labelColon = String("\(labelTitle):") 134 if !minimalistic { 135 Text(labelColon) 136 .talerFont(.picker) 137 .accessibilityHidden(true) 138 .padding(.bottom, -12) 139 } 140 TextField(minimalistic ? labelTitle : EMPTYSTRING, text: $accountLabel) 141 .accessibilityLabel(labelColon) 142 .focused($focus, equals: .accountLabel) 143 .talerFont(.title3) 144 .foregroundColor(WalletColors().fieldForeground) // text color 145 .background(WalletColors().fieldBackground) 146 .textFieldStyle(.roundedBorder) 147 .padding(.bottom) 148 149 let holderTitle = String(localized: "Account holder") 150 let holderColon = String("\(holderTitle):") 151 if !minimalistic { 152 Text(holderColon) 153 .talerFont(.picker) 154 .accessibilityHidden(true) 155 .padding(.bottom, -12) 156 } 157 TextField(minimalistic ? holderTitle : EMPTYSTRING, text: $accountHolder) 158 .accessibilityLabel(holderColon) 159 .focused($focus, equals: .accountHolder) 160 .talerFont(.title3) 161 .foregroundColor(WalletColors().fieldForeground) // text color 162 .background(WalletColors().fieldBackground) 163 .textFieldStyle(.roundedBorder) 164 .padding(.bottom) 165 166 let paytoStr = paytoType.rawValue.uppercased() 167 let paytoColon = String("\(paytoStr):") 168 if let accountID { 169 // we are editing an existing bank account 170 Text(paytoColon) 171 .talerFont(.picker) 172 .accessibilityHidden(true) 173 .padding(.bottom, -12) 174 } else { 175 // this is a NEW bank account - choose a payment method 176 Picker(EMPTYSTRING, selection: $selected) { 177 ForEach(0..<methods.count, id: \.self) { index in 178 let method = methods[index] 179 Text(method.rawValue.uppercased()) 180 .tag(index) 181 } 182 } 183 .pickerStyle(.segmented) 184 .onChange(of: selected) { newValue in 185 paytoType = methods[newValue] 186 } 187 } 188 if paytoType == .iban { 189 TextField(paytoStr, text: $iban) // TODO: BBAN 190 .accessibilityLabel(paytoColon) 191 .focused($focus, equals: .iban) 192 .talerFont(.title3) 193 .foregroundColor(WalletColors().fieldForeground) // text color 194 .background(WalletColors().fieldBackground) 195 .textFieldStyle(.roundedBorder) 196 .padding(.bottom) 197 } else if paytoType == .xTalerBank { 198 TextField(paytoStr, text: $xTaler) 199 .accessibilityLabel(paytoColon) 200 .focused($focus, equals: .xTaler) 201 .talerFont(.title3) 202 .foregroundColor(WalletColors().fieldForeground) // text color 203 .background(WalletColors().fieldBackground) 204 .textFieldStyle(.roundedBorder) 205 .padding(.bottom) 206 } else { 207 Text("unknown payment method") 208 .talerFont(.title3) 209 .padding(.bottom) 210 } 211 212 let buttonTitle = String(localized: "Account.Delete", defaultValue: "Forget bank account", comment: "Action button") 213 let warningText1 = String(localized: "Are you sure you want to forget this bank account?") 214 WarningButton(warningText: warningText1, 215 buttonTitle: buttonTitle, 216 buttonIcon: "trash", 217 role: .destructive, // TODO: WalletColors().errorColor 218 disabled: $disabled, 219 action: deleteAccount) 220 .padding(.top) 221 }.listRowSeparator(.hidden) 222 } // List 223 .navigationBarItems(trailing: doneButton) 224 .onChange(of: focus) { [focus] newState in 225 switch focus { 226 case .none: 227 break 228 case .some(_): Task { 229 await updateAccount() 230 } 231 } 232 } 233 .task { await viewDidLoad() } 234 .onDisappear() { 235 disabled = false 236 } 237 } 238 }