taler-ios

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

DepositAmountAction.swift (7756B)


      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 // Called from DepositSelectV -> BankSectionView
     13 struct DepositAmountAction: View {
     14     private let symLog = SymLogV(0)
     15     let stack: CallStack
     16     // when Action is tapped while in currency TransactionList…
     17     let selectedBalance: Balance?   // …then use THIS balance, otherwise show picker
     18     @Binding var amountLastUsed: Amount
     19     let paytoUri: String?
     20     let label: String?
     21 
     22     @EnvironmentObject private var controller: Controller
     23     @EnvironmentObject private var model: WalletModel
     24 
     25     @State private var balanceIndex = 0
     26     @State private var balance: Balance? = nil      // nil only when balances == []
     27     @State private var currencyInfo: CurrencyInfo = CurrencyInfo.zero(UNKNOWN)
     28     @State private var amountToTransfer = Amount.zero(currency: EMPTYSTRING)    // Update currency when used
     29     @State private var amountAvailable = Amount.zero(currency: EMPTYSTRING)     // GetMaxPeerPushAmount
     30     @State private var depositStarted = false
     31     @State private var txId: String? = nil
     32     @State private var talerTX = TalerTransaction(dummyCurrency: DEMOCURRENCY)
     33 
     34     @MainActor
     35     private func viewDidLoad() async {
     36         let balances = controller.balances
     37         if let selectedBalance {
     38             let disableDeposits = selectedBalance.disableDirectDeposits ?? false
     39             if disableDeposits {
     40                 // find another balance
     41                 balance = Balance.firstwithDeposit(balances)
     42             } else {
     43                 balance = selectedBalance
     44             }
     45         } else {
     46             balance = Balance.firstwithDeposit(balances)
     47         }
     48         // selectedBalance is a snapshot taken when the transaction list appeared, so its
     49         // amounts may already be stale - match on the scope instead of the whole value,
     50         // otherwise the picker would show balances[0] while we deposit from another scope.
     51         if let balance,
     52            let index = balances.firstIndex(where: { $0.scopeInfo == balance.scopeInfo }) {
     53             balanceIndex = index
     54             self.balance = balances[index]      // refresh the snapshot
     55         } else {
     56             balanceIndex = 0
     57             balance = balances.isEmpty ? nil : balances[0]
     58         }
     59     }
     60 
     61     @MainActor
     62     private func newBalance() async {
     63         // runs whenever the user changes the exchange via ScopePicker, or on new currencyInfo
     64         symLog.log("❗️ task \(balanceIndex)")
     65         if let balance {
     66             let scope = balance.scopeInfo
     67             amountToTransfer.setCurrency(scope.currency)
     68             currencyInfo = controller.info(for: scope, controller.currencyTicker)
     69             do {
     70                 amountAvailable = try await model.getMaxDepositAmount(scope)
     71             } catch {
     72                 // TODO: Error
     73                 amountAvailable = balance.available
     74             }
     75         }
     76     }
     77 
     78     @MainActor
     79     private func startDeposit(_ scope: ScopeInfo) {
     80         if let paytoUri {
     81             depositStarted = true    // don't run twice
     82             Task {
     83                 symLog.log("Deposit")
     84                 if let result = try? await model.createDepositGroup(paytoUri,
     85                                                                     scope: scope,
     86                                                                     amount: amountToTransfer) {
     87                     symLog.log(result.transactionId)
     88                     txId = result.transactionId
     89                 } else {
     90                     depositStarted = false
     91                 }
     92             }
     93         }
     94     }
     95 
     96     var body: some View {
     97 #if PRINT_CHANGES
     98         let _ = Self._printChanges()
     99 #endif
    100         if let txId {
    101             TransactionSummaryList(stack: stack.push(),
    102                            transactionId: txId,
    103                                  talerTX: $talerTX,
    104                                 navTitle: nil,
    105                                  hasDone: true,                 // conclude payment
    106                                 showDone: .prominent,       // TODO: isKYC ? .bordered : .prominent
    107                                      url: nil,
    108                              withActions: false)
    109         } else if depositStarted {
    110             let message = String(localized: "Depositing...", comment: "loading")
    111             LoadingView(stack: stack.push(), scopeInfo: nil, message: message)
    112                 .navigationBarBackButtonHidden(true)
    113                 .interactiveDismissDisabled()           // can only use "Abort" button to dismiss
    114                 .safeAreaInset(edge: .bottom) {
    115                     let buttonTitle = String(localized: "Abort", comment: "deposit")
    116                     Button(buttonTitle) { dismissTop(stack.push()) }
    117                         .buttonStyle(TalerButtonStyle(type: .prominent))
    118                         .padding(.horizontal)
    119                 }
    120         } else {
    121             let count = controller.balances.count
    122             let _ = symLog.log("count = \(count)")
    123             let currencySymbol = currencyInfo.symbol
    124             let navA11y = DepositAmountView.navTitle(currencyInfo.name, true)          // always include currency for a11y
    125             let navTitle = DepositAmountView.navTitle(currencySymbol, currencyInfo.hasSymbol)
    126             let scrollView = ScrollView {
    127                 if let label {
    128                     Text("to \(label)")
    129                         .talerFont(.picker)
    130                         .frame(maxWidth: .infinity, alignment: .leading)
    131                         .padding(.horizontal)
    132                 }
    133                 if count > 1 {
    134                     ScopePicker(stack: stack.push(),
    135                                 value: $balanceIndex,
    136                           onlyNonZero: true)                                        // can only send what exists
    137                     { index in
    138                         balanceIndex = index
    139                         balance = controller.balances[index]
    140                     }
    141                     .padding(.horizontal)
    142                     .padding(.bottom, 4)
    143                 }   // TODO: else show static text?
    144                 DepositAmountView(stack: stack.push(),
    145                                 balance: $balance,
    146                            balanceIndex: $balanceIndex,
    147                          amountLastUsed: $amountLastUsed,
    148                        amountToTransfer: $amountToTransfer,
    149                         amountAvailable: amountAvailable,
    150                          depositStarted: depositStarted
    151                 ) {
    152                     if let balance {
    153                         startDeposit(balance.scopeInfo)
    154                     }
    155                 }
    156                     .onAppear {
    157                         DebugViewC.shared.setViewID(VIEW_DEPOSIT, stack: stack.push())
    158                         symLog.log("❗️ onAppear")
    159                     }
    160                     .onDisappear {
    161                         symLog.log("❗️ onDisappear")
    162                     }
    163             } // ScrollView
    164                 .navigationTitle(navTitle)
    165                 .frame(maxWidth: .infinity, alignment: .leading)
    166                 .background(FullBackground())
    167 //              .ignoresSafeArea(.keyboard, edges: .bottom)
    168                 .task {
    169                     setVoice(to: nil)
    170                     await viewDidLoad()
    171                 }
    172                 .task(id: balanceIndex + (1000 * controller.currencyTicker)) { await newBalance() }
    173 
    174             if #available(iOS 16.4, *) {
    175                 scrollView.toolbar(.hidden, for: .tabBar)
    176                     .scrollBounceBehavior(.basedOnSize)
    177             } else {
    178                 scrollView
    179             }
    180         } // else
    181     }
    182 }