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