ManualDetailsWireV.swift (15675B)
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 OrderedCollections 10 import taler_swift 11 12 struct TransferRestrictionsV: View { 13 let amountStr: (String, String) 14 let obtainStr: (String, String)? 15 let debitIBAN: String? 16 let restrictions: [AccountRestriction]? 17 18 @AppStorage("minimalistic") var minimalistic: Bool = false 19 20 @State private var selectedLanguage = Locale.preferredLanguageCode 21 22 private func transferMini(_ amountS: String) -> String { 23 let amountNBS = amountS.nbs 24 return String(localized: "Transfer \(amountNBS) to the payment service.") 25 } 26 private func transferMaxi(_ amountS: String, _ obtainS: String) -> String { 27 let amountNBS = amountS.nbs 28 let obtainNBS = obtainS.nbs 29 return String(localized: "You need to transfer \(amountNBS) from your regular bank account to the payment service to receive \(obtainNBS) as digital cash in this wallet.") 30 } 31 32 private func authMini(_ amountS: String, _ debitS: String) -> String { 33 let amountNBS = amountS.nbs 34 return String(localized: "Transfer \(amountNBS) from account \(debitS) to verify having control over it.") 35 } 36 private func authMaxi(_ amountS: String, _ debitS: String) -> String { 37 let amountNBS = amountS.nbs 38 return String(localized: "You need to transfer \(amountNBS) to the payment service from your bank account \(debitS) to verify having control over it. Don't use a different bank account, or the verification will fail.") 39 } 40 41 var body: some View { 42 VStack(alignment: .leading) { 43 if let obtainStr { 44 Text(minimalistic ? transferMini(amountStr.0) 45 : transferMaxi(amountStr.0, obtainStr.0)) 46 .accessibilityLabel(minimalistic ? transferMini(amountStr.1) 47 : transferMaxi(amountStr.1, obtainStr.1)) 48 .talerFont(.body) 49 .multilineTextAlignment(.leading) 50 } else if let debitIBAN { 51 Text(minimalistic ? authMini(amountStr.0, debitIBAN) 52 : authMaxi(amountStr.0, debitIBAN)) 53 .accessibilityLabel(minimalistic ? authMini(amountStr.1, debitIBAN) 54 : authMaxi(amountStr.1, debitIBAN)) 55 .talerFont(.body) 56 .multilineTextAlignment(.leading) 57 } 58 if let restrictions { 59 ForEach(restrictions) { restriction in 60 let hintsI18n = restriction.human_hint_i18n 61 if let hintsI18n, !hintsI18n.isEmpty { 62 // let sortedDict = OrderedDictionary(uniqueKeys: hintsI18n.keys, values: hintsI18n.values) 63 // var sorted: OrderedDictionary<String:String> 64 let sortedDict = OrderedDictionary(uncheckedUniqueKeysWithValues: hintsI18n.sorted { $0.key < $1.key }) 65 Picker("Restriction:", selection: $selectedLanguage) { 66 ForEach(sortedDict.keys, id: \.self) { 67 Text(sortedDict[$0] ?? "missing hint") 68 } 69 } 70 .padding(.top) 71 } else if let hint = restriction.human_hint { 72 let mark = Image(systemName: EXCLAMATION) 73 Text("\(mark) \(hint)") // verbatim: doesn't work here, will not show the image. Thus we must set this to "Don't translate" 74 .padding(.top) 75 } 76 } 77 } 78 } 79 } 80 } 81 // MARK: - 82 struct ManualDetailsWireV: View { 83 let stack: CallStack 84 let reservePub: String 85 let receiverStr: String 86 let receiverZip: String? 87 let receiverTown: String? 88 let iban: String? // TODO: BBAN 89 let cyclos: String 90 let xTaler: String 91 let amountValue: String // string representation of the value, formatted as "`integer`.`fraction`" 92 let amountStr: (String, String) 93 let obtainStr: (String, String)? // only for withdrawal 94 let debitIBAN: String? // only for deposit auth 95 let account: ExchangeAccountDetails 96 97 @AppStorage("minimalistic") var minimalistic: Bool = false 98 let navTitle = String(localized: "Wire transfer", comment: "ViewTitle of wire-transfer instructions") 99 100 private func step3(_ amountS: String) -> String { 101 let amountNBS = amountS.nbs 102 let bePatient = String(localized: "Depending on your bank the transfer can take from minutes to two working days, please be patient.") 103 if let debitIBAN { 104 return minimalistic ? String(localized: "Transfer \(amountNBS) from \(debitIBAN).") 105 : String(localized: "Finish the wire transfer of \(amountNBS) in your banking app or website to verify your bank account \(debitIBAN).") + "\n" + bePatient 106 } 107 return minimalistic ? String(localized: "Transfer \(amountNBS).") 108 : String(localized: "Finish the wire transfer of \(amountNBS) in your banking app or website, then this withdrawal will proceed automatically.") + "\n" + bePatient 109 } 110 111 @ViewBuilder func payeeZip() -> some View { 112 HStack { 113 VStack(alignment: .leading) { 114 Text("Zip code:") 115 .talerFont(.subheadline) 116 Text(receiverZip ?? EMPTYSTRING) 117 .monospacedDigit() 118 .padding(.leading) 119 } .frame(maxWidth: .infinity, alignment: .leading) 120 .accessibilityElement(children: .combine) 121 .accessibilityLabel(Text("Zip code", comment: "a11y")) 122 CopyButton(textToCopy: receiverZip ?? EMPTYSTRING, vertical: true) 123 .accessibilityLabel(Text("Copy the zip code", comment: "a11y")) 124 .disabled(false) 125 } .padding(.top, -8) 126 } 127 128 @ViewBuilder func payeeTown() -> some View { 129 HStack { 130 VStack(alignment: .leading) { 131 Text("City:") 132 .talerFont(.subheadline) 133 Text(receiverTown ?? EMPTYSTRING) 134 .monospacedDigit() 135 .padding(.leading) 136 } .frame(maxWidth: .infinity, alignment: .leading) 137 .accessibilityElement(children: .combine) 138 .accessibilityLabel(Text("City", comment: "a11y")) 139 CopyButton(textToCopy: receiverTown ?? EMPTYSTRING, vertical: true) 140 .accessibilityLabel(Text("Copy the city", comment: "a11y")) 141 .disabled(false) 142 } .padding(.top, -8) 143 } 144 145 var body: some View { 146 List { 147 let cryptoString = debitIBAN == nil ? reservePub : "kyc" + reservePub 148 let cryptocode = HStack { 149 Text(cryptoString) 150 .monospacedDigit() 151 .accessibilityLabel(Text("Cryptocode", comment: "a11y")) 152 .frame(maxWidth: .infinity, alignment: .leading) 153 CopyButton(textToCopy: cryptoString, vertical: true) 154 .accessibilityLabel(Text("Copy the cryptocode", comment: "a11y")) 155 .disabled(false) 156 } .padding(.leading) 157 let payeeCode = HStack { 158 VStack(alignment: .leading) { 159 Text("Recipient:") 160 .talerFont(.subheadline) 161 Text(receiverStr) 162 .monospacedDigit() 163 .padding(.leading) 164 } .frame(maxWidth: .infinity, alignment: .leading) 165 .accessibilityElement(children: .combine) 166 .accessibilityLabel(Text("Recipient", comment: "a11y")) 167 CopyButton(textToCopy: receiverStr, vertical: true) 168 // CopyButton(textToCopy: buildReceiver(), vertical: true) 169 .accessibilityLabel(Text("Copy the recipient", comment: "a11y")) 170 .disabled(false) 171 } .padding(.top, -8) 172 let ibanCode = HStack { 173 VStack(alignment: .leading) { 174 Text("IBAN:") // TODO: BBAN 175 .talerFont(.subheadline) 176 Text(iban ?? EMPTYSTRING) 177 .monospacedDigit() 178 .padding(.leading) 179 } .frame(maxWidth: .infinity, alignment: .leading) 180 .accessibilityElement(children: .combine) 181 .accessibilityLabel(Text("IBAN of the recipient", comment: "a11y")) // TODO: BBAN 182 CopyButton(textToCopy: iban ?? EMPTYSTRING, vertical: true) 183 .accessibilityLabel(Text("Copy the IBAN", comment: "a11y")) // TODO: BBAN 184 .disabled(false) 185 } // .padding(.top, -8) 186 let amountCode = HStack { 187 VStack(alignment: .leading) { 188 Text("Amount:") 189 .talerFont(.subheadline) 190 Text(amountStr.0) 191 .accessibilityLabel(amountStr.1) 192 .monospacedDigit() 193 .padding(.leading) 194 } .frame(maxWidth: .infinity, alignment: .leading) 195 .accessibilityElement(children: .combine) 196 .accessibilityLabel(Text("Amount to transfer", comment: "a11y")) 197 CopyButton(textToCopy: amountValue, vertical: true) // only digits + separator, no currency name or symbol 198 .accessibilityLabel(Text("Copy the amount", comment: "a11y")) 199 .disabled(false) 200 } .padding(.top, -8) 201 let xTalerCode = HStack { 202 VStack(alignment: .leading) { 203 Text("Account:") 204 .talerFont(.subheadline) 205 Text(xTaler) 206 .monospacedDigit() 207 .padding(.leading) 208 } .frame(maxWidth: .infinity, alignment: .leading) 209 .accessibilityElement(children: .combine) 210 .accessibilityLabel(Text("account of the recipient", comment: "a11y")) 211 CopyButton(textToCopy: xTaler, vertical: true) 212 .accessibilityLabel(Text("Copy the account", comment: "a11y")) 213 .disabled(false) 214 } .padding(.top, -8) 215 let step1 = Text(minimalistic ? "**Step 1:** Copy+Paste this subject:" 216 : "**Step 1:** Copy this code and paste it into the subject/purpose field in your banking app or bank website:") 217 .talerFont(.body) 218 .multilineTextAlignment(.leading) 219 let warningIcon = Image(systemName: WARNING) 220 let manda1 = String(localized: "This is mandatory, otherwise your money will not arrive in this wallet.") 221 let manda2 = String(localized: "This is mandatory, otherwise the verification will fail.") 222 let mandatory = Text("\(warningIcon) " + (debitIBAN == nil ? manda1 : manda2)) 223 .bold() 224 .talerFont(.body) 225 .multilineTextAlignment(.leading) 226 .listRowSeparator(.hidden) 227 let step2i = Text(minimalistic ? "**Step 2:** Copy+Paste recipient and IBAN:" 228 : "**Step 2:** If you don't already have it in your banking favorites list, then copy and paste recipient and IBAN into the recipient/IBAN fields in your banking app or website (and save it as favorite for the next time):") // TODO: BBAN 229 .talerFont(.body) 230 .multilineTextAlignment(.leading) 231 .padding(.top) 232 let step2x = Text(minimalistic ? "**Step 2:** Copy+Paste recipient and account:" 233 : "**Step 2:** Copy and paste recipient and account into the corresponding fields in your banking app or website:") 234 .talerFont(.body) 235 .multilineTextAlignment(.leading) 236 .padding(.top) 237 let step3A11y = String(localized: "Step 3: \(step3(amountStr.1))", comment: "a11y") 238 let step3Head: LocalizedStringKey = "**Step 3:** \(step3(amountStr.0))" 239 let step3 = Text(step3Head) 240 .accessibilityLabel(step3A11y) 241 .talerFont(.body) 242 .multilineTextAlignment(.leading) 243 244 Group { 245 TransferRestrictionsV(amountStr: amountStr, 246 obtainStr: obtainStr, 247 debitIBAN: debitIBAN, 248 restrictions: account.creditRestrictions) 249 .listRowSeparator(.visible) 250 step1 251 if !minimalistic { 252 mandatory 253 } 254 cryptocode 255 if iban != nil { 256 step2i 257 payeeCode 258 if let receiverZip { 259 if !receiverZip.isEmpty { 260 payeeZip() 261 } 262 } 263 if let receiverTown { 264 if !receiverTown.isEmpty { 265 payeeTown() 266 } 267 } 268 ibanCode 269 } else if cyclos.count > 0 { 270 step2x 271 payeeCode 272 } else { 273 step2x 274 payeeCode 275 xTalerCode 276 } 277 amountCode 278 // .padding(.top) 279 step3 // .padding(.top, 6) 280 }.listRowSeparator(.hidden) 281 } 282 .navigationTitle(navTitle) 283 .onAppear() { 284 // symLog.log("onAppear") 285 DebugViewC.shared.setViewID(VIEW_WITHDRAW_INSTRUCTIONS, stack: stack.push()) 286 } 287 } 288 } 289 290 // MARK: - 291 #if DEBUG 292 //struct ManualDetailsWire_Previews: PreviewProvider { 293 // static var previews: some View { 294 // let common = TransactionCommon(type: .withdrawal, 295 // transactionId: "someTxID", 296 // timestamp: Timestamp(from: 1_666_666_000_000), 297 // txState: TransactionState(major: .done), 298 // txActions: []) 299 // amountEffective: Amount(currency: LONGCURRENCY, cent: 110), 300 // amountRaw: Amount(currency: LONGCURRENCY, cent: 220), 301 // let payto = "payto://iban/SANDBOXX/DE159593?receiver-name=Exchange+Company" 302 // let details = WithdrawalDetails(type: .manual, 303 // reservePub: "ReSeRvEpUbLiC_KeY_FoR_WiThDrAwAl", 304 // reserveIsReady: false, 305 // confirmed: false) 306 // List { 307 // ManualDetailsWireV(stack: CallStack("Preview"), 308 // details: details, 309 // receiverStr: <#T##String#>, 310 // iban: <#T##String?#>, 311 // xTaler: <#T##String#>, 312 // amountStr: <#T##String#>, 313 // obtainStr: <#T##String#>, 314 // account: T##ExchangeAccountDetails) 315 // } 316 // } 317 //} 318 #endif