PayTemplateScan.swift (11700B)
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 prepCurrency = contract.currency ?? defaults?.currency ?? supportedCurrencies.first ?? UNKNOWN 88 let zeroAmount = Amount(currency: prepCurrency, cent: 0) 89 let prepAmount = contract.amount ?? defaults?.amount // might be nil 90 let prepSummary = contract.summary ?? defaults?.summary // might be nil 91 // symLog.log("LoadingView.task preparePayForTemplate") 92 /// preparePayForTemplate will make a network call to the merchant and create a TX 93 /// -> we only want to do this after the user entered amount and subject - but before confirmation of course 94 // if let result = await preparePayForTemplate(model: model, 95 // url: url, 96 // amount: amountIsEditable ? prepAmount ?? zeroAmount 97 // : nil, 98 // summary: summaryIsEditable ? prepSummary ?? SPACE 99 // : nil, 100 // announce: announce) 101 // { symLog.log("preparePayForTemplate finished") 102 amountToTransfer = prepAmount ?? zeroAmount 103 summary = prepSummary ?? EMPTYSTRING 104 templateContract = contract 105 // insufficient = result.insufficient 106 // feeAmount = result.feeAmount 107 // feeStr = result.feeStr 108 // preparePayResult = result.ppCheck 109 // } else { 110 // symLog.log("preparePayForTemplateM failed") 111 // } 112 } 113 114 } 115 var body: some View { 116 if let templateContract { // preparePayResult 117 // let currency = templateContract.currency ?? templateContract.amount?.currencyStr ?? UNKNOWN 118 let a11yLabel = String(localized: "Amount to pay:", comment: "a11y, no abbreviations") 119 let amountLabel = minimalistic ? String(localized: "Amount:") 120 : a11yLabel 121 // final destination with amountToTransfer, after user input of amount 122 let finalDestinationI = PaymentScan(stack: stack.push(), 123 url: url, 124 template: true, 125 amountToTransfer: $amountToTransfer, 126 summary: $summary, 127 amountIsEditable: amountIsEditable, 128 summaryIsEditable: summaryIsEditable, 129 prepared: $prepared) 130 131 // final destination with amountShortcut, when user tapped a shortcut 132 let finalDestinationS = PaymentScan(stack: stack.push(), 133 url: url, 134 template: true, 135 amountToTransfer: $amountShortcut, 136 summary: $summary, 137 amountIsEditable: amountIsEditable, 138 summaryIsEditable: summaryIsEditable, 139 prepared: $prepared) 140 141 // destination to subject input 142 let inputDestination = SubjectInputV(stack: stack.push(), 143 url: url, 144 amountAvailable: nil, 145 amountToTransfer: $amountToTransfer, 146 amountLabel: amountLabel, 147 summary: $summary, 148 // insufficient: $insufficient, 149 // feeAmount: $feeAmount, 150 feeIsNotZero: true, // TODO: feeIsNotZero() 151 targetView: finalDestinationI) 152 153 // destination to subject input, when user tapped an amount shortcut 154 let shortcutDestination = SubjectInputV(stack: stack.push(), 155 url: url, 156 amountAvailable: nil, 157 amountToTransfer: $amountShortcut, 158 amountLabel: amountLabel, 159 summary: $summary, 160 // insufficient: $insufficient, 161 // feeAmount: $feeAmount, 162 feeIsNotZero: true, // TODO: feeIsNotZero() 163 targetView: finalDestinationS) 164 Group { 165 if amountIsEditable { // template contract amount is not fixed => let the user input an amount first 166 let amountInput = AmountInputV(stack: stack.push(), 167 scope: scope, 168 amountAvailable: $amountAvailable, 169 amountLabel: amountLabel, 170 a11yLabel: a11yLabel, 171 amountToTransfer: $amountToTransfer, 172 amountLastUsed: amountLastUsed, 173 wireFee: nil, 174 summary: $summary, 175 shortcutAction: shortcutAction, 176 buttonAction: buttonAction1, 177 isIncoming: false, 178 computeFee: computeFeePayTemplate) 179 ScrollView { 180 if summaryIsEditable { // after amount input, 181 amountInput 182 .background( NavLink($shortcutSelected) { shortcutDestination } ) 183 .background( NavLink($buttonSelected1) { inputDestination} ) 184 185 } else { 186 amountInput 187 .background( NavLink($shortcutSelected) { finalDestinationS } ) 188 .background( NavLink($buttonSelected1) { finalDestinationI } ) 189 } 190 } // amountInput 191 } else if summaryIsEditable { // template contract summary is not fixed => let the user input a summary 192 ScrollView { 193 inputDestination 194 .background( NavLink($buttonSelected2) { finalDestinationI } ) 195 } // inputDestination 196 } else { // both template contract amount and summary are fixed => directly show the payment 197 // Attention: contains a List, thus mustn't be included in a ScrollView 198 PaymentScan(stack: stack.push(), 199 url: url, 200 template: true, 201 amountToTransfer: $amountToTransfer, 202 summary: $summary, 203 amountIsEditable: amountIsEditable, 204 summaryIsEditable: summaryIsEditable, 205 prepared: $prepared) 206 } 207 } .navigationTitle(navTitle) 208 // .ignoresSafeArea(.keyboard, edges: .bottom) 209 .frame(maxWidth: .infinity, alignment: .leading) 210 .background(FullBackground()) 211 .onAppear() { 212 symLog.log("onAppear") 213 DebugViewC.shared.setSheetID(SHEET_PAY_TEMPLATE, stack: stack.push()) 214 } 215 } else { 216 LoadingView(stack: stack.push(), scopeInfo: nil, message: url.host) 217 .task { await viewDidLoad() } 218 } 219 } 220 }