taler-ios

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

DepositAmountView.swift (10058B)


      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 from DepositAmountV
     13 struct DepositAmountView: View {
     14     private let symLog = SymLogV(0)
     15     let stack: CallStack
     16     @Binding var balance: Balance?
     17     @Binding var balanceIndex: Int
     18     @Binding var amountLastUsed: Amount
     19     @Binding var amountToTransfer: Amount
     20     let amountAvailable: Amount
     21     let paytoUri: String?
     22 
     23     @EnvironmentObject private var controller: Controller
     24     @EnvironmentObject private var model: WalletModel
     25     @Environment(\.colorScheme) private var colorScheme
     26     @Environment(\.colorSchemeContrast) private var colorSchemeContrast
     27     @AppStorage("minimalistic") var minimalistic: Bool = false
     28 
     29     @State var checkDepositResult: CheckDepositResponse? = nil
     30     @State private var insufficient = false
     31     @State private var feeAmount: Amount? = nil
     32     @State private var feeStr: String = EMPTYSTRING
     33     @State private var depositStarted = false
     34     @State private var exchange: Exchange? = nil                                // wg. noFees
     35 
     36     public static func navTitle(_ currency: String = EMPTYSTRING,
     37                                _ condition: Bool = false
     38     ) -> String {
     39         condition ? String(localized: "NavTitle_Deposit_Currency",
     40                         defaultValue: "Deposit \(currency)",
     41                              comment: "currencySymbol")
     42                   : String(localized: "NavTitle_Deposit",
     43                         defaultValue: "Deposit")
     44     }
     45 
     46     private func feeLabel(_ feeString: String) -> String {
     47         feeString.count > 0 ? String(localized: "+ \(feeString) fee")
     48                             : EMPTYSTRING
     49     }
     50 
     51     private func feeIsNotZero() -> Bool? {
     52         if let hasNoFees = exchange?.noFees {
     53             if hasNoFees {
     54                 return nil      // this exchange never has fees
     55             }
     56         }
     57         return checkDepositResult == nil ? false
     58                                          : true // TODO: !(feeAmount?.isZero ?? false)
     59     }
     60 
     61     @MainActor
     62     private func computeFeeDeposit(_ amount: Amount) async -> ComputeFeeResult? {
     63         if amount.isZero {
     64             return ComputeFeeResult.zero()
     65         }
     66         let insufficient = (try? amount > amountAvailable) ?? true
     67         if insufficient {
     68             return ComputeFeeResult.insufficient()
     69         }
     70 //    private func fee(ppCheck: CheckDepositResponse?) -> Amount? {
     71         do {
     72 //            if let ppCheck {
     73 //                // Outgoing: fee = effective - raw
     74 //                feeAmount = try ppCheck.fees.coin + ppCheck.fees.wire + ppCheck.fees.refresh
     75 //                return feeAmount
     76 //            }
     77         } catch {
     78 
     79         }
     80         return nil
     81     }
     82 
     83     private func buttonTitle(_ amount: Amount) -> String {
     84         if let balance {
     85             let amountWithCurrency = amount.formatted(balance.scopeInfo, isNegative: false, useISO: true)
     86             return DepositAmountView.navTitle(amountWithCurrency.0, true)
     87         }
     88         return DepositAmountView.navTitle()             // should never happen
     89     }
     90 
     91     @MainActor
     92     private func startDeposit(_ scope: ScopeInfo) {
     93         if let paytoUri {
     94             depositStarted = true    // don't run twice
     95             Task {
     96                 symLog.log("Deposit")
     97                 if let result = try? await model.createDepositGroup(paytoUri,
     98                                                              scope: scope,
     99                                                             amount: amountToTransfer) {
    100                     symLog.log(result.transactionId)
    101                     if let txState = result.txState {
    102                         if txState.isKYC || txState.isKYCauth {
    103                             symLog.log("Deposit KYC")
    104 
    105                         } else {
    106 //                          ViewState2.shared.popToRootView(stack.push())
    107                             NotificationCenter.default.post(name: .TransactionDone, object: nil, userInfo: nil)
    108                             dismissTop(stack.push())
    109                         }
    110                     }
    111                 } else {
    112                     depositStarted = false
    113                 }
    114             }
    115         }
    116     }
    117 
    118     var body: some View {
    119 #if PRINT_CHANGES
    120         let _ = Self._printChanges()
    121         let _ = symLog.vlog()       // just to get the # to compare it with .onAppear & onDisappear
    122 #endif
    123         if depositStarted {
    124             let message = String(localized: "Depositing...", comment: "loading")
    125             LoadingView(stack: stack.push(), scopeInfo: nil, message: message)
    126                 .navigationBarBackButtonHidden(true)
    127                 .interactiveDismissDisabled()           // can only use "Done" button to dismiss
    128                 .safeAreaInset(edge: .bottom) {
    129                     let buttonTitle = String(localized: "Abort", comment: "deposit")
    130                     Button(buttonTitle) { dismissTop(stack.push()) }
    131                         .buttonStyle(TalerButtonStyle(type: .prominent))
    132                         .padding(.horizontal)
    133                 }
    134         } else { Group {
    135             if let balance {
    136                 let scopeInfo = balance.scopeInfo
    137                 let availableStr = amountAvailable.formatted(scopeInfo, isNegative: false)
    138 
    139                 let currencyInfo = controller.info(for: scopeInfo, controller.currencyTicker)
    140 //                let amountVoiceOver = amountToTransfer.formatted(scopeInfo, isNegative: false)
    141                 let insufficientLabel = String(localized: "You don't have enough \(currencyInfo.specs.name).")
    142 //                let insufficientLabel2 = String(localized: "but you only have \(available) to deposit.")
    143 
    144                 let disabled = insufficient || amountToTransfer.isZero
    145 
    146                 Text("Available: \(availableStr.0)")
    147                     .accessibilityLabel(Text("Available: \(availableStr.1)", comment: "a11y"))
    148                     .talerFont(.title3)
    149                     .frame(maxWidth: .infinity, alignment: .trailing)
    150                     .padding(.horizontal)
    151 //                    .padding(.bottom, 2)
    152                 let a11yLabel = String(localized: "Amount to deposit:", comment: "a11y, no abbreviations")
    153                 let amountLabel = minimalistic ? String(localized: "Amount:")
    154                                                : a11yLabel
    155                 CurrencyInputView(scope: scopeInfo,
    156                                  amount: $amountToTransfer,
    157                          amountLastUsed: amountLastUsed,
    158                               available: amountAvailable,       // enable shortcuts if available >= value
    159                                   title: amountLabel,
    160                               a11yTitle: a11yLabel,
    161                          shortcutAction: nil)
    162                     .padding(.horizontal)
    163 
    164                 Text(insufficient ? insufficientLabel
    165                                   : feeLabel(feeStr))
    166                     .talerFont(.body)
    167                     .foregroundColor(insufficient ? WalletColors().attention
    168                                                   : (feeAmount?.isZero ?? true) ? WalletColors().secondary(colorScheme, colorSchemeContrast)
    169                                                                                 : WalletColors().negative)
    170                     .padding(4)
    171                 let hint = String(localized: "enabled when amount is non-zero, but not higher than your available amount",
    172                                     comment: "a11y")
    173                 Button(buttonTitle(amountToTransfer)) { startDeposit(balance.scopeInfo) }   // TODO: use exchange scope
    174                 .buttonStyle(TalerButtonStyle(type: .prominent, disabled: disabled || depositStarted))
    175                 .padding(.horizontal)
    176                 .disabled(disabled || depositStarted)
    177                 .accessibilityHint(disabled ? hint : EMPTYSTRING)
    178             } else {    // no balance - Yikes
    179                 Text("No balance. There seems to be a problem with the database...")
    180             }
    181         }
    182         .onAppear {
    183             DebugViewC.shared.setViewID(VIEW_DEPOSIT, stack: stack.push())
    184             symLog.log("❗️ onAppear")
    185         }
    186         .onDisappear {
    187             symLog.log("❗️ onDisappear")
    188         }
    189 
    190 //            .task(id: amountToTransfer.value) {
    191 //                if let amountAvailable {
    192 //                    do {
    193 //                        insufficient = try amountToTransfer > amountAvailable
    194 //                    } catch {
    195 //                        print("Yikes❗️ insufficient failed❗️")
    196 //                        insufficient = true
    197 //                    }
    198 //
    199 //                    if insufficient {
    200 //                        announce("\(amountVoiceOver), \(insufficientLabel2)")
    201 //                        feeStr = EMPTYSTRING
    202 //                    }
    203 //                }
    204 //                if !insufficient {
    205 //                    if amountToTransfer.isZero {
    206 //                        feeStr = EMPTYSTRING
    207 //                        checkDepositResult = nil
    208 //                    } else if let paytoUri {
    209 //                        if let ppCheck = try? await model.checkDepositM(paytoUri, amount: amountToTransfer) {
    210 //                            if let feeAmount = fee(ppCheck: ppCheck) {
    211 //                                feeStr = feeAmount.formatted(scopeInfo, isNegative: false)
    212 //                                let feeLabel = feeLabel(feeStr)
    213 //                                announce("\(amountVoiceOver), \(feeLabel)")
    214 //                            } else {
    215 //                                feeStr = EMPTYSTRING
    216 //                                announce(amountVoiceOver)
    217 //                            }
    218 //                            checkDepositResult = ppCheck
    219 //                        } else {
    220 //                            checkDepositResult = nil
    221 //                        }
    222 //                    }
    223 //                }
    224 //            }
    225         } // else
    226     } // body
    227 }
    228 // MARK: -
    229 #if DEBUG
    230 #endif