taler-ios

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

SendAmountView.swift (8639B)


      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 // Called when tapping [􁉇Send]
     13 struct SendAmountView: View {
     14     private let symLog = SymLogV(0)
     15     let stack: CallStack
     16     let cash: OIMcash
     17     let balance: Balance
     18     @Binding var buttonSelected: Bool
     19     @Binding var amountLastUsed: Amount
     20     @Binding var amountToTransfer: Amount
     21     @Binding var amountAvailable: Amount
     22     @Binding var summary: String
     23     @Binding var iconID: String?
     24 
     25     @EnvironmentObject private var controller: Controller
     26     @EnvironmentObject private var model: WalletModel
     27     @AppStorage("minimalistic") var minimalistic: Bool = false
     28 
     29     @State var peerPushCheck: CheckPeerPushDebitResponse? = nil
     30     @State private var expireDays = SEVENDAYS
     31     @State private var insufficient = false
     32 //    @State private var feeAmount: Amount? = nil
     33     @State private var feeString = (EMPTYSTRING, EMPTYSTRING)
     34     @State private var shortcutSelected = false
     35     @State private var amountShortcut = Amount.zero(currency: EMPTYSTRING)      // Update currency when used
     36     @State private var exchange: Exchange? = nil                                // wg. noFees
     37 
     38     private func shortcutAction(_ shortcut: Amount) {
     39         amountShortcut = shortcut
     40         shortcutSelected = true
     41     }
     42     private func buttonAction() { buttonSelected = true }
     43 
     44     public static func navTitle(_ currency: String, _ condition: Bool = false) -> String {
     45         condition ? String(localized: "NavTitle_Send_Currency",
     46                         defaultValue: "Send \(currency)",
     47                              comment: "NavTitle: Send 'currency'")
     48                   : String(localized: "NavTitle_Send",
     49                         defaultValue: "Send",
     50                              comment: "NavTitle: Send")
     51     }
     52 
     53     private func feeLabel(_ feeStr: String) -> String {
     54         feeStr.count > 0 ? String(localized: "+ \(feeStr) fee")
     55                          : EMPTYSTRING
     56     }
     57 
     58     private func fee(raw: Amount, effective: Amount) -> Amount? {
     59         do {     // Outgoing: fee = effective - raw
     60             let fee = try effective - raw
     61             return fee
     62         } catch {}
     63         return nil
     64     }
     65 
     66     private func feeIsNotZero() -> Bool? {
     67         if let hasNoFees = exchange?.noFees {
     68             if hasNoFees {
     69                 return nil      // this exchange never has fees
     70             }
     71         }
     72         return peerPushCheck == nil ? false
     73                                     : true // TODO: !(feeAmount?.isZero ?? false)
     74     }
     75 
     76     @MainActor
     77     private func computeFee(_ amount: Amount) async -> ComputeFeeResult? {
     78         if amount.isZero {
     79             return ComputeFeeResult.zero()
     80         }
     81         let insufficient = (try? amount > amountAvailable) ?? true
     82         if insufficient {
     83             return ComputeFeeResult.insufficient()
     84         }
     85             do {
     86                 let ppCheck = try await model.checkPeerPushDebit(amount, scope: balance.scopeInfo, viewHandles: true)
     87                 let raw = ppCheck.amountRaw
     88                 let effective = ppCheck.amountEffective
     89                 if let fee = fee(raw: raw, effective: effective) {
     90                     feeString = fee.formatted(balance.scopeInfo, isNegative: false)
     91                     symLog.log("Fee = \(feeString.0)")
     92                     let insufficient = (try? effective > amountAvailable) ?? true
     93 
     94                     peerPushCheck = ppCheck
     95                     let feeLabel = (feeLabel(feeString.0), feeLabel(feeString.1))
     96 //                    announce("\(amountVoiceOver), \(feeLabel)")
     97                     return ComputeFeeResult(insufficient: insufficient,
     98                                                feeAmount: fee,
     99                                                   feeStr: feeLabel,
    100                                                 numCoins: nil)
    101                 } else {
    102                     peerPushCheck = nil
    103                 }
    104             } catch {
    105                 // handle cancel, errors
    106                 symLog.log("❗️ \(error), \(error.localizedDescription)")
    107                 switch error {
    108                     case let walletError as WalletBackendError:
    109                         switch walletError {
    110                             case .walletCoreError(let wError):
    111                                 if wError?.code == 7027 {
    112                                     return ComputeFeeResult.insufficient()
    113                                 }
    114                             default: break
    115                         }
    116                     default: break
    117                 }
    118             }
    119         return nil
    120     }
    121 
    122     @MainActor
    123     private func newBalance() async {
    124         let scope = balance.scopeInfo
    125         symLog.log("❗️ task \(scope.currency)")
    126 
    127         if let available = try? await model.getMaxPeerPushDebitAmount(scope, viewHandles: true) {
    128             amountAvailable = available
    129         } else {
    130             amountAvailable = Amount.zero(currency: scope.currency)
    131         }
    132     }
    133 
    134     var body: some View {
    135 #if PRINT_CHANGES
    136         let _ = Self._printChanges()
    137         let _ = symLog.vlog()       // just to get the # to compare it with .onAppear & onDisappear
    138 #endif
    139         Group {
    140                 let availableStr = amountAvailable.formatted(balance.scopeInfo, isNegative: false)
    141 //              let availableA11y = amountAvailable.formatted(currencyInfo, isNegative: false, useISO: true, a11y: ".")
    142 //              let amountVoiceOver = amountToTransfer.formatted(currencyInfo, isNegative: false)
    143 //              let insufficientLabel2 = String(localized: "but you only have \(availableStr) to send.")
    144 
    145                 let inputDestination = P2PSubjectV(stack: stack.push(),
    146                                                     cash: cash,
    147                                                    scope: balance.scopeInfo,
    148                                                available: balance.available,
    149                                                 feeLabel: (feeLabel(feeString.0), feeLabel(feeString.1)),
    150                                             feeIsNotZero: feeIsNotZero(),
    151                                                 outgoing: true,
    152                                         amountToTransfer: $amountToTransfer,    // from the textedit
    153                                                  summary: $summary,
    154                                                   iconID: $iconID,
    155                                               expireDays: $expireDays)
    156                 let shortcutDestination = P2PSubjectV(stack: stack.push(),
    157                                                        cash: cash,
    158                                                       scope: balance.scopeInfo,
    159                                                   available: balance.available,
    160                                                    feeLabel: nil,
    161                                                feeIsNotZero: feeIsNotZero(),
    162                                                    outgoing: true,
    163                                            amountToTransfer: $amountShortcut,   // from the tapped shortcut button
    164                                                     summary: $summary,
    165                                                      iconID: $iconID,
    166                                                  expireDays: $expireDays)
    167                 let actions = Group {
    168                     NavLink($buttonSelected) { inputDestination }
    169                     NavLink($shortcutSelected) { shortcutDestination }
    170                 }
    171                 let a11yLabel = String(localized: "Amount to send:", comment: "a11y, no abbreviations")
    172                 AmountInputV(stack: stack.push(),
    173                              scope: balance.scopeInfo,
    174                    amountAvailable: $amountAvailable,
    175                        amountLabel: nil,        // will use "Available for transfer: xxx", trailing
    176                          a11yLabel: a11yLabel,
    177                   amountToTransfer: $amountToTransfer,
    178                     amountLastUsed: amountLastUsed,
    179                            wireFee: nil,
    180                            summary: $summary,
    181                     shortcutAction: shortcutAction,
    182                       buttonAction: buttonAction,
    183                         isIncoming: false,
    184                         computeFee: computeFee)
    185                 .background(actions)
    186         } // Group
    187         .task(id: balance) { await newBalance() }
    188         .onAppear {
    189             DebugViewC.shared.setViewID(VIEW_P2P_SEND, stack: stack.push())
    190             symLog.log("❗️ onAppear")
    191         }
    192     } // body
    193 }