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