taler-ios

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

PayTemplateScan.swift (12004B)


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