ManualDetailsV.swift (9964B)
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 ManualDetailsV: View { 13 let stack: CallStack 14 let common: TransactionCommon 15 let details: WithdrawalDetails 16 17 @EnvironmentObject private var model: WalletModel 18 #if DEBUG 19 @AppStorage("developerMode") var developerMode: Bool = true 20 #else 21 @AppStorage("developerMode") var developerMode: Bool = false 22 #endif 23 @AppStorage("minimalistic") var minimalistic: Bool = false 24 @State private var accountID = 0 25 @State private var listID = UUID() 26 27 func redraw(_ newAccount: Int) -> Void { 28 if newAccount != accountID { 29 accountID = newAccount 30 withAnimation { listID = UUID() } 31 } 32 } 33 func validDetails(_ details: [ExchangeAccountDetails]) -> [ExchangeAccountDetails] { 34 details.filter { detail in 35 detail.status.lowercased() == "ok" 36 } 37 } 38 39 @ViewBuilder func qrCodesRow(_ transferOptions: [TransferOption], 40 receiverStr: String, 41 amountStr: String, 42 messageStr: String?) -> some View { 43 let qrCodesForPayto = QRcodesForPayto(stack: stack.push(), 44 transferOptions: transferOptions, 45 receiverStr: receiverStr, 46 amountStr: amountStr, 47 messageStr: messageStr) 48 NavigationLink(destination: qrCodesForPayto) { 49 Text(minimalistic ? "QR" 50 : "Wire transfer QR codes") 51 .talerFont(.title3) 52 } 53 } 54 var body: some View { 55 if let accountDetails = details.exchangeCreditAccountDetails { 56 let validDetails = validDetails(accountDetails) 57 let validCount = validDetails.count 58 if validCount > 0 { 59 let account = validDetails[accountID] 60 if let amount = account.transferAmount { 61 let specs = account.currencySpecification 62 let amountStr = common.amountRaw.formatted(specs: specs, isNegative: false) 63 let amountValue = common.amountRaw.valueStr 64 let obtainStr = common.amountEffective.formatted(specs: specs, isNegative: false) 65 // let _ = print(amountStr, " | ", obtainStr) 66 if !minimalistic { 67 Text("The payment service is waiting for your wire-transfer.") 68 .bold() 69 .multilineTextAlignment(.leading) 70 .listRowSeparator(.hidden) 71 } 72 if validCount > 1 { 73 if validCount > 3 { // too many for SegmentControl 74 AccountPicker(title: String(localized: "Bank"), 75 value: $accountID, 76 accountDetails: validDetails, 77 action: redraw) 78 .listRowSeparator(.hidden) 79 .pickerStyle(.menu) 80 } else { 81 SegmentControl(value: $accountID, accountDetails: validDetails, action: redraw) 82 .listRowSeparator(.hidden) 83 } 84 } else if let bankName = account.bankLabel { 85 Text(bankName + ": " + amountStr.0) 86 .accessibilityLabel(bankName + ": " + amountStr.1) 87 // } else { 88 // Text(amountStr) 89 } 90 let payto = PayTo(account.paytoUri) 91 if let receiverStr = payto.receiver { 92 let wireDetails = ManualDetailsWireV(stack: stack.push(), 93 reservePub: details.reservePub, 94 receiverStr: receiverStr, 95 receiverZip: payto.postalCode, 96 receiverTown: payto.town, 97 iban: payto.iban, 98 cyclos: payto.cyclos ?? EMPTYSTRING, 99 xTaler: payto.xTaler ?? EMPTYSTRING, 100 amountValue: amountValue, 101 amountStr: amountStr, 102 obtainStr: obtainStr, 103 debitIBAN: nil, // only for deposit auth 104 account: account) 105 Group { 106 NavigationLink(destination: wireDetails) { 107 Text(minimalistic ? "Instructions" 108 : "Wire transfer instructions") 109 .talerFont(.title3) 110 } 111 112 if let transferOptions = account.transferOptions { 113 let countA = transferOptions.count 114 let countB = transferOptions.first?.qrCodes?.count ?? 0 115 if countA > 1 || countB > 1 { 116 qrCodesRow(transferOptions, 117 receiverStr: receiverStr, 118 amountStr: amountStr.0, 119 messageStr: payto.messageStr) 120 } else if countA > 0 { // show the single QR directly 121 // Text("If your banking software runs on another device, you can scan this QR code:") 122 // .listRowSeparator(.hidden) 123 if let qrCodeSpecs = transferOptions.first?.qrCodes { 124 if let qrCode = qrCodeSpecs.first { 125 let message = payto.messageStr ?? EMPTYSTRING 126 let textToShare = String(receiverStr + "\n" + amountStr.0 + "\n" + message) 127 QRcodeCopyShare(spec: qrCode, 128 textToShare: textToShare) 129 .listRowSeparator(.automatic) 130 } 131 } 132 } 133 } 134 #if DEBUG 135 if developerMode { 136 if let iban = payto.iban { 137 Text(minimalistic ? "**Alternative:** Use this PayTo-Link:" 138 : "**Alternative:** If your bank already supports PayTo, you can use this PayTo-Link instead:") 139 .multilineTextAlignment(.leading) 140 .padding(.top) 141 .listRowSeparator(.hidden) 142 let title = String(localized: "Share the PayTo URL", comment: "a11y") 143 let minTitle = String(localized: "Share PayTo", comment: "mini") 144 let textToShare = String("\(payto)\n\nIBAN: \(iban)\nReceiver: \(receiverStr)\nAmount: \(amountStr.1)\nSubject: \(details.reservePub)") 145 let _ = print(textToShare) 146 ShareButton(textToShare: textToShare, title: minimalistic ? minTitle : title) 147 .frame(maxWidth: .infinity, alignment: .center) 148 .accessibilityLabel(Text(title)) 149 .disabled(false) 150 .listRowSeparator(.hidden) 151 } 152 } 153 #endif 154 }.id(listID) 155 .talerFont(.body) 156 } else { 157 // TODO: Error No payto URL 158 } 159 } else { 160 // TODO: Error No amount 161 } 162 } else { 163 // TODO: Error none of the details is valid 164 } 165 } else { 166 // TODO: Error No exchangeCreditAccountDetails 167 } 168 } 169 } 170 // MARK: - 171 #if DEBUG 172 struct ManualDetails_Previews: PreviewProvider { 173 static var previews: some View { 174 let common = TransactionCommon(type: .withdrawal, 175 transactionId: "someTxID", 176 timestamp: Timestamp(from: 1_666_666_000_000), 177 scopes: [], 178 txState: TransactionState(major: .done), 179 txActions: [], 180 amountRaw: Amount(currency: LONGCURRENCY, cent: 220), 181 amountEffective: Amount(currency: LONGCURRENCY, cent: 110)) 182 let payto = "payto://iban/SANDBOXX/DE159593?receiver-name=Exchange+Company" 183 let details = WithdrawalDetails(type: .manual, 184 reservePub: "ReSeRvEpUbLiC_KeY_FoR_WiThDrAwAl", 185 reserveIsReady: false, 186 confirmed: false) 187 List { 188 ManualDetailsV(stack: CallStack("Preview"), common: common, details: details) 189 } 190 } 191 } 192 #endif