PayTemplateV.swift (12294B)
1 /* 2 * This file is part of GNU Taler, ©2022-25 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 PayTemplateV: 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 summary: String = EMPTYSTRING // templateParam 42 @State private var scope: ScopeInfo? = nil 43 44 // @State private var feeAmount: Amount? = nil 45 // @State private var feeStr: String = EMPTYSTRING 46 47 private func shortcutAction(_ shortcut: Amount) { 48 amountShortcut = shortcut 49 shortcutSelected = true 50 } 51 private func buttonAction1() { 52 buttonSelected1 = true 53 } 54 private func buttonAction2() { 55 buttonSelected2 = true 56 } 57 58 @MainActor 59 func acceptAction(preparePayResult: PreparePayResult) { 60 Task { // runs on MainActor 61 if let confirmPayResult = try? await model.confirmPay(preparePayResult.transactionId) { 62 // symLog.log(confirmPayResult as Any) 63 if confirmPayResult.type == "done" { 64 dismissTop(stack.push()) 65 } else if confirmPayResult.type == "error" { 66 controller.playSound(0) 67 // TODO: show error 68 } 69 } 70 } 71 } 72 73 private func computeFeePayTemplate(_ amount: Amount) async -> ComputeFeeResult? { 74 // if let result = await preparePayForTemplate(model: model, 75 // url: url, 76 // amount: amountToTransfer, 77 // summary: summaryIsEditable ? summary ?? SPACE 78 // : nil, 79 // announce: announce) 80 // { 81 // preparePayResult = result.ppCheck 82 // } 83 return nil 84 } 85 86 @MainActor 87 private func viewDidLoad() async { 88 if let response = try? await model.checkPayForTemplate(url.absoluteString) { 89 let details = response.templateDetails 90 let defaults = details.editableDefaults // might be nil, or its fields might be nil 91 // TODO: let the user choose a currency from supportedCurrencies[] 92 let supportedCurrencies = response.supportedCurrencies 93 94 /// checkPayForTemplate does not provide fees (yet) 95 let contract = details.templateContract // specifies fixed amount/summary 96 if let amount = contract.amount { 97 controller.updateAmount(amount, forSaved: url) 98 } 99 amountIsEditable = contract.amount == nil 100 summaryIsEditable = contract.summary == nil 101 102 let prepCurrency = contract.currency ?? defaults?.currency ?? 103 (supportedCurrencies.count > 0 ? supportedCurrencies[0] 104 : UNKNOWN) 105 let zeroAmount = Amount(currency: prepCurrency, cent: 0) 106 let prepAmount = contract.amount ?? defaults?.amount // might be nil 107 let prepSummary = contract.summary ?? defaults?.summary // might be nil 108 // symLog.log("LoadingView.task preparePayForTemplate") 109 /// preparePayForTemplate will make a network call to the merchant and create a TX 110 /// -> we only want to do this after the user entered amount and subject - but before confirmation of course 111 // if let result = await preparePayForTemplate(model: model, 112 // url: url, 113 // amount: amountIsEditable ? prepAmount ?? zeroAmount 114 // : nil, 115 // summary: summaryIsEditable ? prepSummary ?? SPACE 116 // : nil, 117 // announce: announce) 118 // { symLog.log("preparePayForTemplate finished") 119 amountToTransfer = prepAmount ?? zeroAmount 120 summary = prepSummary ?? EMPTYSTRING 121 templateContract = contract 122 // insufficient = result.insufficient 123 // feeAmount = result.feeAmount 124 // feeStr = result.feeStr 125 // preparePayResult = result.ppCheck 126 // } else { 127 // symLog.log("preparePayForTemplateM failed") 128 // } 129 } 130 131 } 132 var body: some View { 133 if let templateContract { // preparePayResult 134 // let currency = templateContract.currency ?? templateContract.amount?.currencyStr ?? UNKNOWN 135 let a11yLabel = String(localized: "Amount to pay:", comment: "a11y, no abbreviations") 136 let amountLabel = minimalistic ? String(localized: "Amount:") 137 : a11yLabel 138 // final destination with amountToTransfer, after user input of amount 139 let finalDestinationI = PaymentView(stack: stack.push(), 140 url: url, 141 template: true, 142 amountToTransfer: $amountToTransfer, 143 summary: $summary, 144 amountIsEditable: amountIsEditable, 145 summaryIsEditable: summaryIsEditable) 146 147 // final destination with amountShortcut, when user tapped a shortcut 148 let finalDestinationS = PaymentView(stack: stack.push(), 149 url: url, 150 template: true, 151 amountToTransfer: $amountShortcut, 152 summary: $summary, 153 amountIsEditable: amountIsEditable, 154 summaryIsEditable: summaryIsEditable) 155 156 // destination to subject input 157 let inputDestination = SubjectInputV(stack: stack.push(), 158 url: url, 159 amountAvailable: nil, 160 amountToTransfer: $amountToTransfer, 161 amountLabel: amountLabel, 162 summary: $summary, 163 // insufficient: $insufficient, 164 // feeAmount: $feeAmount, 165 feeIsNotZero: true, // TODO: feeIsNotZero() 166 targetView: finalDestinationI) 167 168 // destination to subject input, when user tapped an amount shortcut 169 let shortcutDestination = SubjectInputV(stack: stack.push(), 170 url: url, 171 amountAvailable: nil, 172 amountToTransfer: $amountShortcut, 173 amountLabel: amountLabel, 174 summary: $summary, 175 // insufficient: $insufficient, 176 // feeAmount: $feeAmount, 177 feeIsNotZero: true, // TODO: feeIsNotZero() 178 targetView: finalDestinationS) 179 Group { 180 if amountIsEditable { // template contract amount is not fixed => let the user input an amount first 181 let amountInput = AmountInputV(stack: stack.push(), 182 scope: scope, 183 amountAvailable: $amountAvailable, 184 amountLabel: amountLabel, 185 a11yLabel: a11yLabel, 186 amountToTransfer: $amountToTransfer, 187 amountLastUsed: amountLastUsed, 188 wireFee: nil, 189 summary: $summary, 190 shortcutAction: shortcutAction, 191 buttonAction: buttonAction1, 192 isIncoming: false, 193 computeFee: computeFeePayTemplate) 194 ScrollView { 195 if summaryIsEditable { // after amount input, 196 amountInput 197 .background( NavLink($shortcutSelected) { shortcutDestination } ) 198 .background( NavLink($buttonSelected1) { inputDestination} ) 199 200 } else { 201 amountInput 202 .background( NavLink($shortcutSelected) { finalDestinationS } ) 203 .background( NavLink($buttonSelected1) { finalDestinationI } ) 204 } 205 } // amountInput 206 } else if summaryIsEditable { // template contract summary is not fixed => let the user input a summary 207 ScrollView { 208 inputDestination 209 .background( NavLink($buttonSelected2) { finalDestinationI } ) 210 } // inputDestination 211 } else { // both template contract amount and summary are fixed => directly show the payment 212 // Attention: contains a List, thus mustn't be included in a ScrollView 213 PaymentView(stack: stack.push(), 214 url: url, 215 template: true, 216 amountToTransfer: $amountToTransfer, 217 summary: $summary, 218 amountIsEditable: amountIsEditable, 219 summaryIsEditable: summaryIsEditable) 220 } 221 } .navigationTitle(navTitle) 222 .frame(maxWidth: .infinity, alignment: .leading) 223 .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all)) 224 .onAppear() { 225 symLog.log("onAppear") 226 DebugViewC.shared.setSheetID(SHEET_PAY_TEMPLATE) 227 } 228 } else { 229 LoadingView(stack: stack.push(), scopeInfo: nil, message: url.host) 230 .task { await viewDidLoad() } 231 } 232 } 233 }