taler-ios

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

commit eaca3b532d9e817ea959f95e15d7dc860214f954
parent 969ed28e57ad67289b07cb7076fe7cfec09bf5ad
Author: Marc Stibane <marc@taler.net>
Date:   Thu, 15 Feb 2024 06:58:18 +0100

Deposit

Diffstat:
MTalerWallet.xcodeproj/project.pbxproj | 2+-
MTalerWallet1/Views/Banking/DepositAmountV.swift | 216++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
MTalerWallet1/Views/Banking/DepositIbanV.swift | 65+++++++++++++++++++++++++++++++++++++----------------------------
MTalerWallet1/Views/Banking/ExchangeListView.swift | 12++++++------
MTalerWallet1/Views/Banking/ExchangeRowView.swift | 29+++++++++++++++++------------
MTalerWallet1/Views/Banking/ExchangeSectionView.swift | 41+++++++++++++++++++++++++++++++----------
6 files changed, 213 insertions(+), 152 deletions(-)

diff --git a/TalerWallet.xcodeproj/project.pbxproj b/TalerWallet.xcodeproj/project.pbxproj @@ -684,8 +684,8 @@ 4EB095292989CBFE0043A8A1 /* ExchangeListView.swift */, 4EC90C772A1B528B0071DC58 /* ExchangeSectionView.swift */, 4EC4008E2AE8019700DF72C7 /* ExchangeRowView.swift */, - 4E96583B2B79656E00404A68 /* DepositAmountV.swift */, 4EBC0F002B7B3CD600C0CB19 /* DepositIbanV.swift */, + 4E96583B2B79656E00404A68 /* DepositAmountV.swift */, 4E50B34F2A1BEE8000F9F01C /* ManualWithdraw.swift */, 4EBA82AC2A3F580500E5F39A /* QuiteSomeCoins.swift */, 4EB431662A1E55C700C5690E /* ManualWithdrawDone.swift */, diff --git a/TalerWallet1/Views/Banking/DepositAmountV.swift b/TalerWallet1/Views/Banking/DepositAmountV.swift @@ -12,10 +12,11 @@ struct DepositAmountV: View { let stack: CallStack // let exchangeBaseUrl: String - let amountAvailable: Amount // TODO: GetMaxPeerPushAmount + let paytoUri: String? + let amountAvailable: Amount? +// @Binding var depositIBAN: String +// @Binding var accountHolder: String @Binding var amountToTransfer: Amount - @Binding var depositIBAN: String - @Binding var accountHolder: String // let scopeInfo: ScopeInfo @EnvironmentObject private var controller: Controller @@ -26,7 +27,7 @@ struct DepositAmountV: View { @State private var insufficient = false @State private var feeAmount: Amount? = nil @State private var feeStr: String = EMPTYSTRING - @State private var buttonSelected = false + @State private var depositStarted = false @State private var amountShortcut = Amount.zero(currency: EMPTYSTRING) // Update currency when used @State private var exchange: Exchange? = nil // wg. hasNoFees @@ -44,10 +45,6 @@ struct DepositAmountV: View { var feeLabel: String { feeStr.count > 0 ? String(localized: "+ \(feeStr) fee") : EMPTYSTRING } - private func shortcutAction(_ shortcut: Amount) { - amountShortcut = shortcut - buttonSelected = true - } private func feeIsNotZero() -> Bool? { if let hasNoFees = exchange?.hasNoFees { if hasNoFees { @@ -58,101 +55,130 @@ struct DepositAmountV: View { : false } + private func buttonTitle(_ amount: Amount, _ currencyInfo: CurrencyInfo) -> String { + let amountWithCurrency = amount.string(currencyInfo, useSymbol: false) + return String(localized: "Deposit \(amountWithCurrency)", comment: "amount with currency") + } + + private func subjectTitle(_ amount: Amount, _ currencyInfo: CurrencyInfo) -> String { + let amountStr = amount.string(currencyInfo) + return String(localized: "NavTitle_Deposit_AmountStr", + defaultValue: "Deposit", comment: "NavTitle: Deposit") + // defaultValue: "Deposit \(amountStr)", comment: "NavTitle: Deposit 'amountStr'") + } + var body: some View { #if PRINT_CHANGES let _ = Self._printChanges() let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear #endif - let currency = amountToTransfer.currencyStr - let currencyInfo = controller.info(for: currency, controller.currencyTicker) - let currencySymbol = currencyInfo.specs.altUnitNames?[0] ?? currency - let navTitle = String(localized: "NavTitle_Deposit_Currency", - defaultValue: "Deposit \(currencySymbol)", - comment: "NavTitle: Deposit 'currencySymbol'") - let available = amountAvailable.string(currencyInfo) - let _ = print("available: \(available)") - let _ = symLog.log("currency: \(currency), available: \(available)") - let amountVoiceOver = amountToTransfer.string(currencyInfo) - let insufficientLabel = String(localized: "You don't have enough \(currency).") - let insufficientLabel2 = String(localized: "but you only have \(available) to send.") + if depositStarted { + LoadingView(url: nil, message: "Depositing...") + .navigationBarBackButtonHidden(true) + .interactiveDismissDisabled() // can only use "Done" button to dismiss - let inputDestination = LazyView { - DepositIbanV(stack: stack.push(), - feeLabel: feeLabel, - feeIsNotZero: feeIsNotZero(), - currencyInfo: currencyInfo, - amountToTransfer: $amountToTransfer, - depositIBAN: $depositIBAN, - accountHolder: $accountHolder) - } - let shortcutDestination = LazyView { - DepositIbanV(stack: stack.push(), - feeLabel: nil, - feeIsNotZero: feeIsNotZero(), - currencyInfo: currencyInfo, - amountToTransfer: $amountShortcut, - depositIBAN: $depositIBAN, - accountHolder: $accountHolder) - } - let disabled = insufficient || amountToTransfer.isZero - ScrollView { VStack(alignment: .trailing) { -// Text("Available:\t\(available)") -// .talerFont(.title3) -// .padding(.bottom, 2) - CurrencyInputView(amount: $amountToTransfer, - available: nil, // amountAvailable, - title: minimalistic ? String(localized: "Amount:") - : String(localized: "Amount to deposit:"), - shortcutAction: shortcutAction) - Text(insufficient ? insufficientLabel - : feeLabel) - .talerFont(.body) - .foregroundColor(insufficient ? .red - : (feeAmount?.isZero ?? true) ? .secondary : .red) - .padding(4) - NavigationLink(destination: inputDestination) { Text("Next") } +// ViewState2.shared.popToRootView + + } else { + let currency = amountToTransfer.currencyStr + let currencyInfo = controller.info(for: currency, controller.currencyTicker) + let currencySymbol = currencyInfo.specs.altUnitNames?[0] ?? currency + let navTitle = String(localized: "NavTitle_Deposit_Currency", + defaultValue: "Deposit \(currencySymbol)", + comment: "NavTitle: Deposit 'currencySymbol'") + let available = amountAvailable?.string(currencyInfo) ?? "an unknown amount" + let _ = print("available: \(available)") + let _ = symLog.log("currency: \(currency), available: \(available)") + let amountVoiceOver = amountToTransfer.string(currencyInfo) + let insufficientLabel = String(localized: "You don't have enough \(currency).") + let insufficientLabel2 = String(localized: "but you only have \(available) to deposit.") + + let disabled = insufficient || amountToTransfer.isZero + ScrollView { VStack(alignment: .trailing) { +// Text("via \(exchange.exchangeBaseUrl.trimURL())") +// .multilineTextAlignment(.center) +// .talerFont(.body) + + Text("Available:\t\(available)") + .talerFont(.title3) + .padding(.bottom, 2) + CurrencyInputView(amount: $amountToTransfer, + available: nil, // amountAvailable, + title: minimalistic ? String(localized: "Amount:") + : String(localized: "Amount to deposit:"), + shortcutAction: nil) +// .padding(.top) + Text(insufficient ? insufficientLabel + : feeLabel) + .talerFont(.body) + .foregroundColor(insufficient ? .red + : (feeAmount?.isZero ?? true) ? .secondary : .red) + .padding(4) + Button(buttonTitle(amountToTransfer, currencyInfo)) { + if let paytoUri { + depositStarted = true // don't run twice + Task { // runs on MainActor + symLog.log("Deposit") + do { + let result = try await model.createDepositGroupM(paytoUri, amount: amountToTransfer) + symLog.log(result.transactionId) + ViewState2.shared.popToRootView(stack.push()) + NotificationCenter.default.post(name: .TransactionDone, object: nil, userInfo: nil) + } catch { // TODO: show error + symLog.log(error.localizedDescription) + depositStarted = false + } + } + } + } .buttonStyle(TalerButtonStyle(type: .prominent)) - .disabled(disabled) - .background(NavigationLink(destination: shortcutDestination, isActive: $buttonSelected) - { EmptyView() }.frame(width: 0).opacity(0).hidden() - ) - }.padding(.horizontal) } // ScrollVStack - .frame(maxWidth: .infinity, alignment: .leading) -// .scrollBounceBehavior(.basedOnSize) needs iOS 16.4 - .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all)) - .navigationTitle(navTitle) - .onAppear { - DebugViewC.shared.setViewID(VIEW_P2P_SEND, stack: stack.push()) - symLog.log("❗️ \(navTitle) onAppear") - } - .onDisappear { - symLog.log("❗️ \(navTitle) onDisappear") - } - .task(id: amountToTransfer.value) { -// do { -// insufficient = try amountToTransfer > amountAvailable -// } catch { -// print("Yikes❗️ insufficient failed❗️") -// insufficient = true -// } + .disabled(disabled || depositStarted) + .accessibilityHint(disabled ? "enabled when amount is non-zero, but not higher than your available amount" : EMPTYSTRING) + }.padding(.horizontal) } // ScrollVStack + .frame(maxWidth: .infinity, alignment: .leading) +// .scrollBounceBehavior(.basedOnSize) needs iOS 16.4 + .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all)) + .navigationTitle(navTitle) + .onAppear { + DebugViewC.shared.setViewID(VIEW_DEPOSIT, stack: stack.push()) + symLog.log("❗️ \(navTitle) onAppear") + } + .onDisappear { + symLog.log("❗️ \(navTitle) onDisappear") + } + .task(id: amountToTransfer.value) { + if let amountAvailable { + do { + insufficient = try amountToTransfer > amountAvailable + } catch { + print("Yikes❗️ insufficient failed❗️") + insufficient = true + } -// if insufficient { -// announce(this: "\(amountVoiceOver), \(insufficientLabel2)") -// } else if amountToTransfer.isZero { -// feeStr = EMPTYSTRING -// } else { -// do { -// let ppCheck = try await model.prepareDepositM("", amount: amountToTransfer) -// prepareDepositResult = ppCheck -// if let feeAmount = fee(ppCheck: prepareDepositResult) { -// feeStr = feeAmount.string(currencyInfo) -// } else { feeStr = EMPTYSTRING } -// announce(this: "\(amountVoiceOver), \(feeLabel)") -// } catch { // TODO: error -// symLog.log(error.localizedDescription) -// prepareDepositResult = nil -// } -// } + if insufficient { + announce(this: "\(amountVoiceOver), \(insufficientLabel2)") + feeStr = EMPTYSTRING + } + } + if !insufficient { + if amountToTransfer.isZero { + feeStr = EMPTYSTRING + prepareDepositResult = nil + } else if let paytoUri { + do { + let ppCheck = try await model.prepareDepositM(paytoUri, amount: amountToTransfer) + prepareDepositResult = ppCheck + if let feeAmount = fee(ppCheck: prepareDepositResult) { + feeStr = feeAmount.string(currencyInfo) + } else { feeStr = EMPTYSTRING } + announce(this: "\(amountVoiceOver), \(feeLabel)") + } catch { // TODO: error + symLog.log(error.localizedDescription) + prepareDepositResult = nil + } + } + } + } } } } diff --git a/TalerWallet1/Views/Banking/DepositIbanV.swift b/TalerWallet1/Views/Banking/DepositIbanV.swift @@ -12,27 +12,33 @@ struct DepositIbanV: View { let stack: CallStack let feeLabel: String? let feeIsNotZero: Bool? // nil = no fees at all, false = no fee for this tx - let currencyInfo: CurrencyInfo + let amountAvailable: Amount? +// @Binding var depositIBAN: String +// @Binding var accountHolder: String @Binding var amountToTransfer: Amount - @Binding var depositIBAN: String - @Binding var accountHolder: String + @EnvironmentObject private var controller: Controller @EnvironmentObject private var model: WalletModel @AppStorage("minimalistic") var minimalistic: Bool = false + @AppStorage("depositIBAN") var depositIBAN: String = EMPTYSTRING + @AppStorage("accountHolder") var accountHolder: String = EMPTYSTRING @State private var myFeeLabel: String = EMPTYSTRING @State private var transactionStarted: Bool = false + @State private var paytoUri: String? = nil @FocusState private var isFocused: Bool private func buttonTitle(_ amount: Amount, _ currencyInfo: CurrencyInfo) -> String { let amountWithCurrency = amount.string(currencyInfo, useSymbol: false) + return String(localized: "Next", comment: "advance Deposit to Amount") return String(localized: "Deposit \(amountWithCurrency)", comment: "amount with currency") } private func subjectTitle(_ amount: Amount, _ currencyInfo: CurrencyInfo) -> String { let amountStr = amount.string(currencyInfo) return String(localized: "NavTitle_Deposit_AmountStr", - defaultValue: "Deposit \(amountStr)", comment: "NavTitle: Deposit 'amountStr'") + defaultValue: "Deposit", comment: "NavTitle: Deposit") +// defaultValue: "Deposit \(amountStr)", comment: "NavTitle: Deposit 'amountStr'") } var body: some View { @@ -40,6 +46,17 @@ struct DepositIbanV: View { let _ = Self._printChanges() let _ = symLog.vlog(amountToTransfer.readableDescription) // just to get the # to compare it with .onAppear & onDisappear #endif + let currency = amountToTransfer.currencyStr + let currencyInfo = controller.info(for: currency, controller.currencyTicker) + let destination = LazyView { + DepositAmountV(stack: stack.push(), + paytoUri: paytoUri, +// exchangeBaseUrl: baseURL, + amountAvailable: amountAvailable, +// depositIBAN: $depositIBAN, +// accountHolder: $accountHolder, + amountToTransfer: $amountToTransfer) + } ScrollView { VStack (alignment: .leading, spacing: 6) { if let feeIsNotZero { // don't show fee if nil let label = feeLabel ?? myFeeLabel @@ -63,6 +80,7 @@ struct DepositIbanV: View { .foregroundColor(WalletColors().fieldForeground) // text color .background(WalletColors().fieldBackground) .textFieldStyle(.roundedBorder) + .padding(minimalistic ? .bottom : .vertical) .onAppear { symLog.log("dispatching kbd...") DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) { @@ -78,22 +96,15 @@ struct DepositIbanV: View { .accessibilityRemoveTraits(.isStaticText) .padding(.top) } - TextField(minimalistic ? "Account holder" : EMPTYSTRING, text: $depositIBAN) + TextField(minimalistic ? "IBAN" : EMPTYSTRING, text: $depositIBAN) .talerFont(.title2) .foregroundColor(WalletColors().fieldForeground) // text color .background(WalletColors().fieldBackground) .textFieldStyle(.roundedBorder) + .padding(minimalistic ? .bottom : .vertical) - let disabled = (accountHolder.count < 1) || (depositIBAN.count < 1) // TODO: check amountAvailable - NavigationLink(destination: LazyView { - EmptyView() -// P2PReadyV(stack: stack.push(), -// summary: summary, -// expireDays: expireDays, -// amountToSend: amountToSend, -// amountToTransfer: amountToTransfer, -// transactionStarted: $transactionStarted) - }) { + let disabled = (accountHolder.count < 1) || paytoUri == nil // TODO: check amountAvailable + NavigationLink(destination: destination) { Text(buttonTitle(amountToTransfer, currencyInfo)) } .buttonStyle(TalerButtonStyle(type: .prominent)) @@ -110,19 +121,17 @@ struct DepositIbanV: View { .onDisappear { // print("❗️ P2PSubjectV onDisappear") } -// .task(id: amountToTransfer.value) { -// if amountToSend && feeLabel == nil { -// do { -// let ppCheck = try await model.checkPeerPushDebitM(amountToTransfer) -// if let feeAmount = p2pFee(ppCheck: ppCheck) { -// let feeStr = feeAmount.string(currencyInfo) -// myFeeLabel = String(localized: "+ \(feeStr) fee") -// } else { myFeeLabel = EMPTYSTRING } -// } catch { // TODO: error -// symLog.log(error.localizedDescription) -// } -// } -// } + .task(id: depositIBAN) { + do { + if try await model.validateIbanM(depositIBAN) { + let payto = "payto://iban/\(depositIBAN)?receiver-name=\(accountHolder)" + paytoUri = payto.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + } + } catch { // TODO: error + symLog.log(error.localizedDescription) + paytoUri = nil + } + } } } // MARK: - diff --git a/TalerWallet1/Views/Banking/ExchangeListView.swift b/TalerWallet1/Views/Banking/ExchangeListView.swift @@ -72,18 +72,17 @@ struct ExchangeListCommonV { @EnvironmentObject private var model: WalletModel @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic +// @AppStorage("depositIBAN") var depositIBAN = EMPTYSTRING +// @AppStorage("accountHolder") var accountHolder = EMPTYSTRING @State private var exchanges: [Exchange] = [] // source of truth for the value the user enters in currencyField for exchange withdrawals @State private var amountToTransfer = Amount.zero(currency: EMPTYSTRING) // TODO: Hold different values for different currencies? - @State private var depositIBAN = EMPTYSTRING - @State private var accountHolder = EMPTYSTRING func reloadExchanges() async -> Void { exchanges = await model.listExchangesM() } - } // MARK: - extension ExchangeListCommonV: View { @@ -97,10 +96,11 @@ extension ExchangeListCommonV: View { Group { List(sortedExchanges, id: \.self) { exchange in ExchangeSectionView(stack: stack.push(), + balances: $balances, exchange: exchange, - amountToTransfer: $amountToTransfer, // does still have the wrong currency - depositIBAN: $depositIBAN, - accountHolder: $accountHolder) +// depositIBAN: $depositIBAN, +// accountHolder: $accountHolder, + amountToTransfer: $amountToTransfer) // does still have the wrong currency } .refreshable { symLog?.log("refreshing") diff --git a/TalerWallet1/Views/Banking/ExchangeRowView.swift b/TalerWallet1/Views/Banking/ExchangeRowView.swift @@ -10,10 +10,11 @@ struct ExchangeRowView: View { private let symLog = SymLogV(0) let stack: CallStack let exchange: Exchange +// @Binding var depositIBAN: String +// @Binding var accountHolder: String + let amountAvailable: Amount? let currency: String // this is the currency to be used @Binding var amountToTransfer: Amount // does still have the wrong currency - @Binding var depositIBAN: String - @Binding var accountHolder: String @Environment(\.sizeCategory) var sizeCategory @EnvironmentObject private var controller: Controller @@ -53,13 +54,15 @@ struct ExchangeRowView: View { let withdrawTitle1 = String(localized: "Withdraw\t\(currency)", comment: "Button `Withdraw (currency)´, must have ONE \\t and ONE %@") let baseURL = exchange.exchangeBaseUrl + let deposit = LazyView { - DepositAmountV(stack: stack.push(), -// exchangeBaseUrl: baseURL, - amountAvailable: Amount.zero(currency: currency), - amountToTransfer: $amountToTransfer, - depositIBAN: $depositIBAN, - accountHolder: $accountHolder) + DepositIbanV(stack: stack.push(), + feeLabel: nil, + feeIsNotZero: nil, + amountAvailable: amountAvailable, +// depositIBAN: $depositIBAN, +// accountHolder: $accountHolder, + amountToTransfer: $amountToTransfer) } let manualWithdraw = LazyView { ManualWithdraw(stack: stack.push(), @@ -72,11 +75,12 @@ struct ExchangeRowView: View { viewID: VIEW_WITHDRAW_TOS, acceptAction: nil) // pop back to here } + let disableDeposit = false // TODO: availableAmount.isZero let twoRowButtons = TwoRowButtons(sendTitle: minimalistic ? depositTitle0 : depositTitle1, recvTitle: minimalistic ? withdrawTitle0 : withdrawTitle1, fitsSideBySide: false, lineLimit: 5, - sendDisabled: false, // TODO: availableAmount.isZero + sendDisabled: disableDeposit, sendAction: { selectAndUpdate(1) }, recvAction: { selectAndUpdate(2) }) Group { @@ -151,10 +155,11 @@ fileprivate struct ExchangeRow_Container : View { ageRestrictionOptions: []) ExchangeRowView(stack: CallStack("Preview"), exchange: exchange1, + amountAvailable: Amount(currency: LONGCURRENCY, cent: 5000), // 50,00 currency: LONGCURRENCY, - amountToTransfer: $amountToPreview, - depositIBAN: $depositIBAN, - accountHolder: $accountHolder) +// depositIBAN: $depositIBAN, +// accountHolder: $accountHolder, + amountToTransfer: $amountToPreview) } } diff --git a/TalerWallet1/Views/Banking/ExchangeSectionView.swift b/TalerWallet1/Views/Banking/ExchangeSectionView.swift @@ -10,15 +10,32 @@ import taler_swift /// [Deposit Coins] [Withdraw Coins] struct ExchangeSectionView: View { let stack: CallStack + @Binding var balances: [Balance] let exchange: Exchange // let exchanges: [Exchange] +// @Binding var depositIBAN: String +// @Binding var accountHolder: String @Binding var amountToTransfer: Amount // does still have the wrong currency - @Binding var depositIBAN: String - @Binding var accountHolder: String @EnvironmentObject private var controller: Controller @AppStorage("minimalistic") var minimalistic: Bool = false @State private var shouldReloadBalances: Int = 0 + + func amountAvailable(_ exchangeBaseUrl: String, currency: String?) -> Amount? { + for balance in balances { + if let baseUrl = balance.scopeInfo.url { + if baseUrl == exchangeBaseUrl { + return balance.available + } + } else if let currency { + if currency == balance.scopeInfo.currency { + return balance.available + } + } + } + return nil + } + var body: some View { #if PRINT_CHANGES let _ = Self._printChanges() @@ -34,10 +51,12 @@ struct ExchangeSectionView: View { // ForEach(exchanges) { exchange in ExchangeRowView(stack: stack.push(), exchange: exchange, + amountAvailable: amountAvailable(exchange.exchangeBaseUrl, + currency: currency), +// depositIBAN: $depositIBAN, +// accountHolder: $accountHolder, currency: currencyName, // TODO: (balance.available) amount.isZero to disable Deposit-button - amountToTransfer: $amountToTransfer, // does still have the wrong currency - depositIBAN: $depositIBAN, - accountHolder: $accountHolder) + amountToTransfer: $amountToTransfer) // does still have the wrong currency .listRowSeparator(.hidden) // } if DEMOCURRENCY == currency { @@ -61,9 +80,10 @@ struct ExchangeSectionView: View { } } // MARK: - -#if DEBUG +#if false // model crashes fileprivate struct ExchangeSection_Previews: PreviewProvider { fileprivate struct ExchangeRow_Container : View { + @State private var previewBalances: [Balance] = [] @State private var amountToPreview = Amount(currency: LONGCURRENCY, cent: 1234) @State private var depositIBAN = "DE1234567890" @State private var accountHolder = "Marc Stibane" @@ -85,13 +105,14 @@ fileprivate struct ExchangeRow_Container : View { exchangeEntryStatus: .ephemeral, exchangeUpdateStatus: .ready, ageRestrictionOptions: []) - ExchangeSectionView(stack: CallStack("Preview"), + ExchangeSectionView(stack: CallStack("Preview"), + balances: $previewBalances, // scopeInfo: scopeInfo, exchange: exchange1, // exchanges: [exchange1, exchange2], - amountToTransfer: $amountToPreview, - depositIBAN: $depositIBAN, - accountHolder: $accountHolder) +// depositIBAN: $depositIBAN, +// accountHolder: $accountHolder, + amountToTransfer: $amountToPreview) } }