PayTemplateScan.swift (12004B)
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 // Will be called either by the user scanning a <pay-template> QR code or tapping the provided link, 13 // both from the shop's website - or even from a printed QR code. 14 // We check whether amount and/or summary is editable, and finally go to PaymentView 15 struct PayTemplateScan: View { 16 private let symLog = SymLogV(0) 17 let stack: CallStack 18 19 // the scanned URL 20 let url: URL 21 22 @EnvironmentObject private var controller: Controller 23 @EnvironmentObject private var model: WalletModel 24 @AppStorage("minimalistic") var minimalistic: Bool = false 25 @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic 26 27 let navTitle = String(localized: "Custom Amount", comment:"pay merchant") 28 29 // @State private var insufficient = false 30 // @State private var preparePayResult: PreparePayResult? = nil 31 @State private var templateContract: TemplateContractDetails? = nil 32 @State private var amountIsEditable = false 33 @State private var amountToTransfer = Amount.zero(currency: EMPTYSTRING) // Update currency when used 34 @State private var amountShortcut = Amount.zero(currency: EMPTYSTRING) // Update currency when used 35 @State private var amountLastUsed = Amount.zero(currency: EMPTYSTRING) // Update currency when used 36 @State private var amountAvailable = Amount.zero(currency: EMPTYSTRING) // TODO: set correct available amount (like in SendAmountV) 37 @State private var shortcutSelected = false 38 @State private var buttonSelected1 = false 39 @State private var buttonSelected2 = false 40 @State private var summaryIsEditable = false 41 @State private var isPaivana = false 42 @State private var summary: String = EMPTYSTRING // templateParam 43 @State private var scope: ScopeInfo? = nil 44 /// survives PaymentScan being popped and pushed again, so that we don't 45 /// instantiate the template (= create another order) a second time 46 @State private var prepared: PreparedTemplate? = nil 47 48 // @State private var feeAmount: Amount? = nil 49 // @State private var feeStr: String = EMPTYSTRING 50 51 private func shortcutAction(_ shortcut: Amount) { 52 amountShortcut = shortcut 53 shortcutSelected = true 54 } 55 private func buttonAction1() { 56 buttonSelected1 = true 57 } 58 private func buttonAction2() { 59 buttonSelected2 = true 60 } 61 62 private func computeFeePayTemplate(_ amount: Amount) async -> ComputeFeeResult? { 63 return nil 64 } 65 66 @MainActor 67 private func viewDidLoad() async { 68 if let response = try? await model.checkPayForTemplate(url.trimmedString) { 69 let details = response.templateDetails 70 let defaults = details.editableDefaults // might be nil, or its fields might be nil 71 // TODO: let the user choose a currency from supportedCurrencies[] 72 let supportedCurrencies = response.supportedCurrencies 73 74 /// checkPayForTemplate does not provide fees (yet) 75 let contract = details.templateContract // specifies fixed amount/summary 76 if let amount = contract.amount { 77 controller.updateAmount(amount, forSaved: url) 78 } 79 if contract.templateType == .paivana { 80 // TODO: ??? 81 isPaivana = true 82 } else { 83 amountIsEditable = contract.amount == nil 84 summaryIsEditable = contract.summary == nil 85 } 86 87 let prepAmount = contract.amount ?? defaults?.amount // might be nil 88 var prepCurrency: String 89 if let scope = contract.scopeInfo { 90 if let balance = controller.balance(for: scope) { 91 amountAvailable = balance.available 92 } 93 prepCurrency = scope.currency 94 } else { 95 prepCurrency = contract.currency ?? defaults?.currency ?? supportedCurrencies.first ?? UNKNOWN 96 } 97 let zeroAmount = Amount(currency: prepCurrency, cent: 0) 98 let prepSummary = contract.summary ?? defaults?.summary // might be nil 99 // symLog.log("LoadingView.task preparePayForTemplate") 100 /// preparePayForTemplate will make a network call to the merchant and create a TX 101 /// -> we only want to do this after the user entered amount and subject - but before confirmation of course 102 // if let result = await preparePayForTemplate(model: model, 103 // url: url, 104 // amount: amountIsEditable ? prepAmount ?? zeroAmount 105 // : nil, 106 // summary: summaryIsEditable ? prepSummary ?? SPACE 107 // : nil, 108 // announce: announce) 109 // { symLog.log("preparePayForTemplate finished") 110 amountToTransfer = prepAmount ?? zeroAmount 111 summary = prepSummary ?? EMPTYSTRING 112 templateContract = contract 113 // insufficient = result.insufficient 114 // feeAmount = result.feeAmount 115 // feeStr = result.feeStr 116 // preparePayResult = result.ppCheck 117 // } else { 118 // symLog.log("preparePayForTemplateM failed") 119 // } 120 } 121 } 122 var body: some View { 123 if let templateContract { // preparePayResult 124 // let currency = templateContract.currency ?? templateContract.amount?.currencyStr ?? UNKNOWN 125 let a11yLabel = String(localized: "Amount to pay:", comment: "a11y, no abbreviations") 126 let amountLabel = minimalistic ? String(localized: "Amount:") 127 : a11yLabel 128 // final destination with amountToTransfer, after user input of amount 129 let finalDestinationI = PaymentScan(stack: stack.push(), 130 url: url, 131 template: true, 132 amountToTransfer: $amountToTransfer, 133 summary: $summary, 134 amountIsEditable: amountIsEditable, 135 summaryIsEditable: summaryIsEditable, 136 prepared: $prepared) 137 138 // final destination with amountShortcut, when user tapped a shortcut 139 let finalDestinationS = PaymentScan(stack: stack.push(), 140 url: url, 141 template: true, 142 amountToTransfer: $amountShortcut, 143 summary: $summary, 144 amountIsEditable: amountIsEditable, 145 summaryIsEditable: summaryIsEditable, 146 prepared: $prepared) 147 148 // destination to subject input 149 let inputDestination = SubjectInputV(stack: stack.push(), 150 url: url, 151 amountAvailable: nil, 152 amountToTransfer: $amountToTransfer, 153 amountLabel: amountLabel, 154 summary: $summary, 155 // insufficient: $insufficient, 156 // feeAmount: $feeAmount, 157 feeIsNotZero: true, // TODO: feeIsNotZero() 158 targetView: finalDestinationI) 159 160 // destination to subject input, when user tapped an amount shortcut 161 let shortcutDestination = SubjectInputV(stack: stack.push(), 162 url: url, 163 amountAvailable: nil, 164 amountToTransfer: $amountShortcut, 165 amountLabel: amountLabel, 166 summary: $summary, 167 // insufficient: $insufficient, 168 // feeAmount: $feeAmount, 169 feeIsNotZero: true, // TODO: feeIsNotZero() 170 targetView: finalDestinationS) 171 Group { 172 if amountIsEditable { // template contract amount is not fixed => let the user input an amount first 173 let amountInput = AmountInputV(stack: stack.push(), 174 scope: scope, 175 amountAvailable: $amountAvailable, 176 amountLabel: amountLabel, 177 a11yLabel: a11yLabel, 178 amountToTransfer: $amountToTransfer, 179 amountLastUsed: amountLastUsed, 180 wireFee: nil, 181 summary: $summary, 182 shortcutAction: shortcutAction, 183 buttonAction: buttonAction1, 184 isIncoming: false, 185 computeFee: computeFeePayTemplate) 186 ScrollView { 187 if summaryIsEditable { // after amount input, 188 amountInput 189 .background( NavLink($shortcutSelected) { shortcutDestination } ) 190 .background( NavLink($buttonSelected1) { inputDestination} ) 191 192 } else { 193 amountInput 194 .background( NavLink($shortcutSelected) { finalDestinationS } ) 195 .background( NavLink($buttonSelected1) { finalDestinationI } ) 196 } 197 } // amountInput 198 } else if summaryIsEditable { // template contract summary is not fixed => let the user input a summary 199 ScrollView { 200 inputDestination 201 .background( NavLink($buttonSelected2) { finalDestinationI } ) 202 } // inputDestination 203 } else { // both template contract amount and summary are fixed => directly show the payment 204 // Attention: contains a List, thus mustn't be included in a ScrollView 205 PaymentScan(stack: stack.push(), 206 url: url, 207 template: true, 208 amountToTransfer: $amountToTransfer, 209 summary: $summary, 210 amountIsEditable: amountIsEditable, 211 summaryIsEditable: summaryIsEditable, 212 prepared: $prepared) 213 } 214 } .navigationTitle(navTitle) 215 // .ignoresSafeArea(.keyboard, edges: .bottom) 216 .frame(maxWidth: .infinity, alignment: .leading) 217 .background(FullBackground()) 218 .onAppear() { 219 symLog.log("onAppear") 220 DebugViewC.shared.setSheetID(SHEET_PAY_TEMPLATE, stack: stack.push()) 221 } 222 } else { 223 LoadingView(stack: stack.push(), scopeInfo: nil, message: url.host) 224 .task { await viewDidLoad() } 225 } 226 } 227 }