taler-ios

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

commit 897cfbef8789f7e56941713704f4af372f704369
parent be4a609cdba82c49eccf048c47a317191d9ceda5
Author: Marc Stibane <marc@taler.net>
Date:   Sat, 12 Oct 2024 23:06:24 +0200

ScopePicker

Diffstat:
MTalerWallet1/Views/Actions/Peer2peer/RequestPayment.swift | 170++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
MTalerWallet1/Views/Actions/Peer2peer/SendAmountV.swift | 79++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
2 files changed, 183 insertions(+), 66 deletions(-)

diff --git a/TalerWallet1/Views/Actions/Peer2peer/RequestPayment.swift b/TalerWallet1/Views/Actions/Peer2peer/RequestPayment.swift @@ -1,7 +1,10 @@ /* - * This file is part of GNU Taler, ©2022-23 Taler Systems S.A. + * This file is part of GNU Taler, ©2022-24 Taler Systems S.A. * See LICENSE.md */ +/** + * @author Marc Stibane + */ import SwiftUI import taler_swift import SymLog @@ -12,7 +15,88 @@ struct RequestPayment: View { let stack: CallStack let balances: [Balance] @Binding var selectedBalance: Balance? + @Binding var amountLastUsed: Amount + @Binding var summary: String + + @State private var balanceIndex = 0 + @State private var pickerBalances: [Balance] = [] + @State private var balance: Balance? = nil // nil only when balances == [] + + 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 scrollView = ScrollView { + let count = pickerBalances.count + let _ = symLog.log("count = \(count)") + if count > 0 { + let disabled = (count == 1 || selectedBalance != nil) + Group { + let scopePicker = ScopePicker(value: $balanceIndex, balances: pickerBalances) { index in + balanceIndex = index + balance = pickerBalances[index] + } + let available = balance?.available + let availableA11y = available?.formatted(isNegative: false, useISO: true, a11y: ".") + + let url = balance?.scopeInfo.url?.trimURL ?? EMPTYSTRING + let a11yLabel = url + ", " + (availableA11y ?? EMPTYSTRING) + HStack { + Text("via", comment: "ScopePicker") + .foregroundColor(disabled ? .secondary : .primary) + Spacer(minLength: 2) + scopePicker + .disabled(disabled) + Spacer(minLength: 2) + } + .accessibilityElement(children: .combine) + .accessibilityHint(String(localized: "Choose the payment provider.", comment: "a11y")) + .accessibilityLabel(a11yLabel) + } + .talerFont(.picker) +// .padding(.bottom) + .padding(.leading) + } + RequestPaymentContent(stack: stack.push(), + balance: $balance, + balanceIndex: $balanceIndex, + amountLastUsed: $amountLastUsed, + summary: $summary) + } // ScrollView + .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all)) +// .scrollBounceBehavior(.basedOnSize) needs iOS 16.4 + .task { + if let selectedBalance { + pickerBalances = [selectedBalance] + balance = selectedBalance + } else { + pickerBalances = balances + let count = pickerBalances.count + if balanceIndex >= count { + balanceIndex = 0 + } + if count > 0 { + balance = pickerBalances[balanceIndex] + } else { + balance = nil + } + } + } + if #available(iOS 16.0, *) { + scrollView.toolbar(.hidden, for: .tabBar) + } else { + scrollView + } + } +} +// MARK: - +struct RequestPaymentContent: View { + private let symLog = SymLogV() + let stack: CallStack + @Binding var balance: Balance? + @Binding var balanceIndex: Int @Binding var amountLastUsed: Amount @Binding var summary: String @@ -30,6 +114,7 @@ struct RequestPayment: View { @State private var amountShortcut = Amount.zero(currency: EMPTYSTRING) // Update currency when used @State private var amountZero = Amount.zero(currency: EMPTYSTRING) // needed for isZero @State private var exchange: Exchange? = nil // wg. noFees + @State private var currencyInfo: CurrencyInfo = CurrencyInfo.zero(UNKNOWN) @State private var currencyName: String = UNKNOWN @State private var currencySymbol: String = UNKNOWN @@ -43,7 +128,7 @@ struct RequestPayment: View { private func feeLabel(_ feeString: String) -> String { feeString.count > 0 ? String(localized: "- \(feeString) fee") - : EMPTYSTRING + : EMPTYSTRING } private func fee(raw: Amount, effective: Amount) -> Amount? { @@ -91,7 +176,18 @@ struct RequestPayment: View { } } catch { // handle cancel, errors - symLog.log("❗️ \(error)") + symLog.log("❗️ \(error), \(error.localizedDescription)") + switch error { + case let walletError as WalletBackendError: + switch walletError { + case .walletCoreError(let wError): + if wError?.code == 7027 { + return ComputeFeeResult.insufficient() + } + default: break + } + default: break + } } return nil } @@ -111,32 +207,27 @@ struct RequestPayment: View { let _ = symLog.log("currency: \(currency)") let amountVoiceOver = amountToTransfer.formatted(currencyInfo, isNegative: false) - let inputDestination = LazyView { - P2PSubjectV(stack: stack.push(), - scope: scopeInfo, - currencyInfo: $currencyInfo, - feeLabel: coinData.feeLabel(currencyInfo, - feeZero: String(localized: "No payment fee"), - isNegative: false), - feeIsNotZero: feeIsNotZero(), - outgoing: false, - amountToTransfer: $amountToTransfer, - summary: $summary, - expireDays: $expireDays) - } - let shortcutDestination = LazyView { - P2PSubjectV(stack: stack.push(), - scope: scopeInfo, - currencyInfo: $currencyInfo, - feeLabel: nil, - feeIsNotZero: feeIsNotZero(), - outgoing: false, - amountToTransfer: $amountShortcut, - summary: $summary, - expireDays: $expireDays) - } - - ScrollView { + let inputDestination = P2PSubjectV(stack: stack.push(), + scope: scopeInfo, + currencyInfo: $currencyInfo, + feeLabel: coinData.feeLabel(currencyInfo, + feeZero: String(localized: "No payment fee"), + isNegative: false), + feeIsNotZero: feeIsNotZero(), + outgoing: false, + amountToTransfer: $amountToTransfer, + summary: $summary, + expireDays: $expireDays) + let shortcutDestination = P2PSubjectV(stack: stack.push(), + scope: scopeInfo, + currencyInfo: $currencyInfo, + feeLabel: nil, + feeIsNotZero: feeIsNotZero(), + outgoing: false, + amountToTransfer: $amountShortcut, + summary: $summary, + expireDays: $expireDays) + Group { let amountLabel = minimalistic ? String(localized: "Amount:") : String(localized: "Amount to request:") AmountInputV(stack: stack.push(), @@ -152,14 +243,13 @@ struct RequestPayment: View { feeIsNegative: true, computeFee: computeFeeRequest) .background(NavigationLink(destination: shortcutDestination, isActive: $shortcutSelected) - { EmptyView() }.frame(width: 0).opacity(0).hidden() - ) // shortcutDestination + { EmptyView() }.frame(width: 0).opacity(0).hidden() + ) // shortcutDestination .background(NavigationLink(destination: inputDestination, isActive: $buttonSelected) - { EmptyView() }.frame(width: 0).opacity(0).hidden() - ) // inputDestination - } // ScrollView + { EmptyView() }.frame(width: 0).opacity(0).hidden() + ) // inputDestination + } // Group .frame(maxWidth: .infinity, alignment: .leading) -// .scrollBounceBehavior(.basedOnSize) needs iOS 16.4 .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all)) .navigationTitle(navTitle) .onAppear { @@ -170,6 +260,16 @@ struct RequestPayment: View { .onDisappear { symLog.log("❗️ \(navTitle) onDisappear") } + .task(id: balanceIndex + (1000 * controller.currencyTicker)) { + if let balance { + scopeInfo = balance.scopeInfo + let currency = scopeInfo.currency + amountToTransfer.setCurrency(currency) + currencyInfo = controller.info(for: currency, controller.currencyTicker) + currencyName = currencyInfo.scope.currency +// currencySymbol = currencyInfo.altUnitSymbol ?? currencyInfo.specs.name + } + } // .task(id: amountToTransfer.value) { // if exchange == nil { // if let url = scopeInfo.url { diff --git a/TalerWallet1/Views/Actions/Peer2peer/SendAmountV.swift b/TalerWallet1/Views/Actions/Peer2peer/SendAmountV.swift @@ -26,24 +26,36 @@ struct SendAmountV: View { #if PRINT_CHANGES let _ = Self._printChanges() #endif -// nonZeroBalances.count > 0 -// let balance = selectedBalance ?? nonZeroBalances[balanceIndex] - let sendAmountView = ScrollView { + let scrollView = ScrollView { let count = nonZeroBalances.count - if let selectedBalance { - let urlOrCurrency = selectedBalance.scopeInfo.url?.trimURL - ?? selectedBalance.scopeInfo.currency - let amount = selectedBalance.available - let formattedAmount = amount.formatted(isNegative: false, useISO: false) - let label = String("\(urlOrCurrency):\t\(formattedAmount.nbs)") - Text(label) -// .padding(.leading) - .talerFont(.title3) - } else if count > 0 { - ScopePicker(value: $balanceIndex, balances: nonZeroBalances) { index in - balanceIndex = index - balance = nonZeroBalances[index] + let _ = symLog.log("count = \(count)") + if count > 0 { + let disabled = (count == 1 || selectedBalance != nil) + Group { + let scopePicker = ScopePicker(value: $balanceIndex, balances: nonZeroBalances) { index in + balanceIndex = index + balance = nonZeroBalances[index] + } + let available = balance?.available + let availableA11y = available?.formatted(isNegative: false, useISO: true, a11y: ".") + + let url = balance?.scopeInfo.url?.trimURL ?? EMPTYSTRING + let a11yLabel = url + ", " + (availableA11y ?? EMPTYSTRING) + HStack { + Text("via", comment: "ScopePicker") + .foregroundColor(disabled ? .secondary : .primary) + Spacer(minLength: 2) + scopePicker + .disabled(disabled) + Spacer(minLength: 2) + } + .accessibilityElement(children: .combine) + .accessibilityHint(String(localized: "Choose the payment provider.", comment: "a11y")) + .accessibilityLabel(a11yLabel) } + .talerFont(.picker) +// .padding(.bottom) + .padding(.leading) } SendAmountContent(stack: stack.push(), balance: $balance, @@ -52,9 +64,12 @@ struct SendAmountV: View { summary: $summary) } // ScrollView .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all)) -// .scrollBounceBehavior(.basedOnSize) needs iOS 16.4 +// .scrollBounceBehavior(.basedOnSize) needs iOS 16.4 .task { - if selectedBalance == nil { + if let selectedBalance { + nonZeroBalances = [selectedBalance] + balance = selectedBalance + } else { nonZeroBalances = Balance.nonZeroBalances(balances) let count = nonZeroBalances.count if balanceIndex >= count { @@ -65,15 +80,13 @@ struct SendAmountV: View { } else { balance = nil } - } else { - balance = selectedBalance } } if #available(iOS 16.0, *) { - sendAmountView.toolbar(.hidden, for: .tabBar) + scrollView.toolbar(.hidden, for: .tabBar) } else { - sendAmountView + scrollView } } } @@ -86,12 +99,8 @@ struct SendAmountContent: View { @Binding var amountLastUsed: Amount @Binding var summary: String - // TODO: call getMaxPeerPushDebitAmountM - @EnvironmentObject private var controller: Controller @EnvironmentObject private var model: WalletModel - @Environment(\.colorScheme) private var colorScheme - @Environment(\.colorSchemeContrast) private var colorSchemeContrast @AppStorage("minimalistic") var minimalistic: Bool = false @State var peerPushCheck: CheckPeerPushDebitResponse? = nil @@ -197,6 +206,7 @@ struct SendAmountContent: View { if let balance { let scopeInfo = balance.scopeInfo let availableStr = amountAvailable.formatted(currencyInfo, isNegative: false) + let availableA11y = amountAvailable.formatted(currencyInfo, isNegative: false, useISO: true, a11y: ".") let amountVoiceOver = amountToTransfer.formatted(currencyInfo, isNegative: false) let insufficientLabel2 = String(localized: "but you only have \(availableStr) to send.") @@ -219,6 +229,11 @@ struct SendAmountContent: View { summary: $summary, expireDays: $expireDays) Group { + HStack { + Spacer() + Text("available: \(availableStr)") + .accessibilityLabel("available: \(availableA11y)") + }.padding(.horizontal) let amountLabel = minimalistic ? String(localized: "Amount:") : String(localized: "Amount to send:") AmountInputV(stack: stack.push(), @@ -248,12 +263,14 @@ struct SendAmountContent: View { .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all)) .navigationTitle(navTitle) .task { - do { - let amount = try await model.getMaxPeerPushDebitAmountM(scopeInfo, viewHandles: true) - amountAvailable = amount - } catch { - amountAvailable = Amount.zero(currency: amountToTransfer.currencyStr) + if scopeInfo.type != .madeUp { + do { + let amount = try await model.getMaxPeerPushDebitAmountM(scopeInfo, viewHandles: true) + amountAvailable = amount + return + } catch {} } + amountAvailable = Amount.zero(currency: amountToTransfer.currencyStr) } .onAppear { DebugViewC.shared.setViewID(VIEW_P2P_SEND, stack: stack.push())