taler-ios

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

DepositAmountView.swift (10087B)


      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 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.isEmpty ? EMPTYSTRING : String(localized: "+ \(feeString) fee")
     48     }
     49 
     50     private func feeIsNotZero() -> Bool? {
     51         if let hasNoFees = exchange?.noFees {
     52             if hasNoFees {
     53                 return nil      // this exchange never has fees
     54             }
     55         }
     56         return checkDepositResult == nil ? false
     57                                          : true // TODO: !(feeAmount?.isZero ?? false)
     58     }
     59 
     60     @MainActor
     61     private func computeFeeDeposit(_ amount: Amount) async -> ComputeFeeResult? {
     62         if amount.isZero {
     63             return ComputeFeeResult.zero()
     64         }
     65         let insufficient = (try? amount > amountAvailable) ?? true
     66         if insufficient {
     67             return ComputeFeeResult.insufficient()
     68         }
     69 //    private func fee(ppCheck: CheckDepositResponse?) -> Amount? {
     70         do {
     71 //            if let ppCheck {
     72 //                // Outgoing: fee = effective - raw
     73 //                feeAmount = try ppCheck.fees.coin + ppCheck.fees.wire + ppCheck.fees.refresh
     74 //                return feeAmount
     75 //            }
     76         } catch {
     77 
     78         }
     79         return nil
     80     }
     81 
     82     private func buttonTitle(_ amount: Amount) -> String {
     83         if let balance {
     84             let amountWithCurrency = amount.formatted(balance.scopeInfo, isNegative: false, useISO: true)
     85             return DepositAmountView.navTitle(amountWithCurrency.0, true)
     86         }
     87         return DepositAmountView.navTitle()             // should never happen
     88     }
     89 
     90     @MainActor
     91     private func startDeposit(_ scope: ScopeInfo) {
     92         if let paytoUri {
     93             depositStarted = true    // don't run twice
     94             Task {
     95                 symLog.log("Deposit")
     96                 if let result = try? await model.createDepositGroup(paytoUri,
     97                                                              scope: scope,
     98                                                             amount: amountToTransfer) {
     99                     symLog.log(result.transactionId)
    100                     if let txState = result.txState {
    101                         if txState.isKYC || txState.isKYCauth {
    102                             symLog.log("Deposit KYC")
    103 
    104                         } else {
    105 //                          ViewState2.shared.popToRootView(stack.push())
    106                             NotificationCenter.default.post(name: .TransactionDone, object: nil, userInfo: nil)
    107                             dismissTop(stack.push())
    108                         }
    109                     }
    110                 } else {
    111                     depositStarted = false
    112                 }
    113             }
    114         }
    115     }
    116 
    117     var body: some View {
    118 #if PRINT_CHANGES
    119         let _ = Self._printChanges()
    120         let _ = symLog.vlog()       // just to get the # to compare it with .onAppear & onDisappear
    121 #endif
    122         if depositStarted {
    123             let message = String(localized: "Depositing...", comment: "loading")
    124             LoadingView(stack: stack.push(), scopeInfo: nil, message: message)
    125                 .navigationBarBackButtonHidden(true)
    126                 .interactiveDismissDisabled()           // can only use "Done" button to dismiss
    127                 .safeAreaInset(edge: .bottom) {
    128                     let buttonTitle = String(localized: "Abort", comment: "deposit")
    129                     Button(buttonTitle) { dismissTop(stack.push()) }
    130                         .buttonStyle(TalerButtonStyle(type: .prominent))
    131                         .padding(.horizontal)
    132                 }
    133         } else { ScrollView {
    134             if let balance {
    135                 let scopeInfo = balance.scopeInfo
    136                 let availableStr = amountAvailable.formatted(scopeInfo, isNegative: false)
    137 
    138                 let currencyInfo = controller.info(for: scopeInfo, controller.currencyTicker)
    139 //                let amountVoiceOver = amountToTransfer.formatted(scopeInfo, isNegative: false)
    140                 let insufficientLabel = String(localized: "You don't have enough \(currencyInfo.specs.name).")
    141 //                let insufficientLabel2 = String(localized: "but you only have \(available) to deposit.")
    142 
    143                 let disabled = insufficient || amountToTransfer.isZero
    144 
    145                 Text("Available: \(availableStr.0)")
    146                     .accessibilityLabel(Text("Available: \(availableStr.1)", comment: "a11y"))
    147                     .talerFont(.title3)
    148                     .frame(maxWidth: .infinity, alignment: .trailing)
    149                     .padding(.horizontal)
    150 //                    .padding(.bottom, 2)
    151                 let a11yLabel = String(localized: "Amount to deposit:", comment: "a11y, no abbreviations")
    152                 let amountLabel = minimalistic ? String(localized: "Amount:")
    153                                                : a11yLabel
    154                 CurrencyInputView(scope: scopeInfo,
    155                                  amount: $amountToTransfer,
    156                          amountLastUsed: amountLastUsed,
    157                               available: amountAvailable,       // enable shortcuts if available >= value
    158                                   title: amountLabel,
    159                               a11yTitle: a11yLabel,
    160                          shortcutAction: nil)
    161                     .padding(.horizontal)
    162 
    163                 Text(insufficient ? insufficientLabel
    164                                   : feeLabel(feeStr))
    165                     .talerFont(.body)
    166                     .foregroundColor(insufficient ? WalletColors().attention
    167                                                   : (feeAmount?.isZero ?? true) ? WalletColors().secondary(colorScheme, colorSchemeContrast)
    168                                                                                 : WalletColors().negative)
    169                     .padding(4)
    170                 let hint = String(localized: "enabled when amount is non-zero, but not higher than your available amount",
    171                                     comment: "a11y")
    172                 Button(buttonTitle(amountToTransfer)) { startDeposit(balance.scopeInfo) }   // TODO: use exchange scope
    173                 .buttonStyle(TalerButtonStyle(type: .prominent, disabled: disabled || depositStarted))
    174                 .padding(.horizontal)
    175                 .disabled(disabled || depositStarted)
    176                 .accessibilityHint(disabled ? hint : EMPTYSTRING)
    177             } else {    // no balance - Yikes
    178                 Text("No balance. There seems to be a problem with the database...")
    179             }
    180         }
    181 //        .ignoresSafeArea(.keyboard, edges: .bottom)
    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