taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

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 }