taler-ios

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

commit 9bd0c7d7f4fad5621fdb782695a2bd8fcdb34fc6
parent 06abe762457f47aadb6b528e1da612cee72c970e
Author: Marc Stibane <marc@taler.net>
Date:   Sat, 27 Jul 2024 14:49:21 +0200

Fee

Diffstat:
MTalerWallet1/Views/Banking/ManualWithdraw.swift | 3++-
MTalerWallet1/Views/Banking/QuiteSomeCoins.swift | 50++++++++++++++++++++++++++++----------------------
MTalerWallet1/Views/HelperViews/AmountInputV.swift | 41+++++++++++++++++++----------------------
MTalerWallet1/Views/Peer2peer/RequestPayment.swift | 15+++++++--------
MTalerWallet1/Views/Peer2peer/SendAmount.swift | 20++++++++------------
MTalerWallet1/Views/Sheets/Payment/PayTemplateV.swift | 10++++------
MTalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptView.swift | 3++-
MTalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift | 93+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
8 files changed, 123 insertions(+), 112 deletions(-)

diff --git a/TalerWallet1/Views/Banking/ManualWithdraw.swift b/TalerWallet1/Views/Banking/ManualWithdraw.swift @@ -80,10 +80,11 @@ struct ManualWithdraw: View { shortcutAction: nil) .padding(.top) QuiteSomeCoins(currencyInfo: $currencyInfo, +// amountEffective: withdrawalAmountDetails?.amountEffective, currency: currency, coinData: coinData, shouldShowFee: true, // TODO: set to false if we never charge withdrawal fees - amountEffective: withdrawalAmountDetails?.amountEffective) + feeIsNegative: true) // agePicker NavigationLink(destination: destination) { Text("Confirm Withdrawal") // VIEW_WITHDRAW_ACCEPT diff --git a/TalerWallet1/Views/Banking/QuiteSomeCoins.swift b/TalerWallet1/Views/Banking/QuiteSomeCoins.swift @@ -18,12 +18,11 @@ struct CoinData { var tooMany: Bool { numCoins > 999 } let fee: Amount? - func feeLabel(_ currencyInfo: CurrencyInfo?) -> String { + func feeLabel(_ currencyInfo: CurrencyInfo, feeZero: String?, isNegative: Bool = false) -> String { return if let fee { - invalid ? String(localized: "Amount too small!") - : tooMany ? String(localized: "Amount too big for a single withdrawal!") - : fee.isZero ? String(localized: "No withdrawal fee") - : String(localized: "- \(fee.formatted(currencyInfo, isNegative: false)) fee") + fee.isZero ? feeZero ?? EMPTYSTRING // String(localized: "No withdrawal fee") + : isNegative ? String(localized: "- \(fee.formatted(currencyInfo, isNegative: false)) fee") + : String(localized: "+ \(fee.formatted(currencyInfo, isNegative: false)) fee") } else { EMPTYSTRING } @@ -31,6 +30,10 @@ struct CoinData { } extension CoinData { + init(coins numCoins: Int?, fee: Amount?) { + self.init(numCoins: numCoins ?? -1, fee: fee) // either the number of coins, or unknown + } + init(details: WithdrawalAmountDetails?) { do { if let details { @@ -61,31 +64,34 @@ extension CoinData { struct QuiteSomeCoins: View { private let symLog = SymLogV(0) @Binding var currencyInfo: CurrencyInfo +// let amountEffective: Amount? let currency: String let coinData: CoinData let shouldShowFee: Bool - let amountEffective: Amount? + let feeIsNegative: Bool var body: some View { - if !coinData.invalid { - if !coinData.tooMany { - if coinData.manyCoins { - Text(coinData.quiteSome ? "Note: It will take quite some time to withdraw this amount! Be more patient..." - : "Note: It will take some time to withdraw this amount. Be patient...") - .foregroundColor(coinData.quiteSome ? .red : .primary) - .talerFont(.body) - .multilineTextAlignment(.leading) - .padding(.vertical, 6) - } // warnings - } - } - if shouldShowFee { + let isError = coinData.invalid || coinData.tooMany + let showFee = shouldShowFee && !isError + if showFee { if let fee = coinData.fee { - Text(coinData.feeLabel(currencyInfo)) - .foregroundColor((coinData.invalid || coinData.tooMany || !fee.isZero) ? .red : .primary) - .talerFont(.body) + Text(coinData.feeLabel(currencyInfo, + feeZero: String(localized: "No withdrawal fee"), + isNegative: feeIsNegative)) + .foregroundColor(.primary) + .talerFont(.body) } } + if isError || coinData.quiteSome || coinData.manyCoins { + Text(coinData.invalid ? "Amount too small!" + : coinData.tooMany ? "Amount too big for a single withdrawal!" + : coinData.quiteSome ? "Note: It will take quite some time to prepare this amount! Be more patient..." + : "Note: It will take some time to prepare this amount. Be patient...") + .foregroundColor(isError || coinData.quiteSome ? .red : .primary) + .talerFont(.body) + .multilineTextAlignment(.leading) + .padding(.vertical, 6) + } } } // MARK: - diff --git a/TalerWallet1/Views/HelperViews/AmountInputV.swift b/TalerWallet1/Views/HelperViews/AmountInputV.swift @@ -13,6 +13,7 @@ struct ComputeFeeResult { let insufficient: Bool let feeAmount: Amount? let feeStr: String + let numCoins: Int? } struct AmountInputV: View { @@ -20,38 +21,37 @@ struct AmountInputV: View { let stack: CallStack @Binding var currencyInfo: CurrencyInfo - // the scanned URL - let url: URL? let amountAvailable: Amount? // TODO: GetMaxPeerPushAmount + let amountLabel: String @Binding var amountToTransfer: Amount let wireFee: Amount? - let amountLabel: String - let summaryIsEditable: Bool // if true we call SubjectInputV next +// let summaryIsEditable: Bool // if true we call SubjectInputV next @Binding var summary: String // @Binding var insufficient: Bool - @Binding var feeAmount: Amount? +// @Binding var feeAmount: Amount? let shortcutAction: ((_ amount: Amount) -> Void)? let buttonAction: () -> Void + let feeIsNegative: Bool let computeFee: ((_ amount: Amount) async -> ComputeFeeResult?)? @EnvironmentObject private var controller: Controller - @EnvironmentObject private var model: WalletModel @Environment(\.colorScheme) private var colorScheme @Environment(\.colorSchemeContrast) private var colorSchemeContrast - @State private var preparePayResult: PreparePayResult? = nil + @State private var feeAmount: Amount? = nil @State private var feeStr: String = EMPTYSTRING + @State private var numCoins: Int? struct Flags { let insufficient: Bool let disabled: Bool } - func checkAvailable(amount: Amount) -> Flags { + func checkAvailable(_ amount: Amount, _ coinData: CoinData) -> Flags { if let amountAvailable { do { let insufficient = try amount > amountAvailable - let disabled = insufficient || amount.isZero + let disabled = insufficient || amount.isZero || coinData.invalid || coinData.tooMany return Flags(insufficient: insufficient, disabled: disabled) } catch { // TODO: error Amounts don't match @@ -65,7 +65,6 @@ struct AmountInputV: View { // let currencyInfo = controller.info(for: currency, controller.currencyTicker) let insufficientLabel = String(localized: "You don't have enough \(currency).") let available = amountAvailable?.formatted(currencyInfo, isNegative: false) ?? nil - let flags = checkAvailable(amount: amountToTransfer) VStack(alignment: .trailing) { if summary.count > 0 { Text(summary) @@ -86,23 +85,20 @@ struct AmountInputV: View { shortcutAction: shortcutAction) // .accessibility(sortPriority: 2) -// let color = flags.insufficient || !(feeAmount?.isZero ?? true) ? .red -// : WalletColors().secondary(colorScheme, colorSchemeContrast) -// Text(flags.insufficient ? insufficientLabel : feeLabel(feeStr)) -// .talerFont(.body) -// .foregroundColor(color) -// .padding(4) -// .accessibility(sortPriority: 1) + let coinData = CoinData(coins: numCoins, fee: feeAmount) + QuiteSomeCoins(currencyInfo: $currencyInfo, +// amountEffective: amountEffective, + currency: currency, + coinData: coinData, + shouldShowFee: true, // TODO: set to false if we never charge withdrawal fees + feeIsNegative: feeIsNegative) + let flags = checkAvailable(amountToTransfer, coinData) Button("Next") { buttonAction() } .buttonStyle(TalerButtonStyle(type: .prominent, disabled: flags.disabled)) .disabled(flags.disabled) }.padding(.horizontal) .frame(maxWidth: .infinity, alignment: .leading) .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all)) - .onAppear() { -// symLog.log("onAppear") - DebugViewC.shared.setSheetID(SHEET_PAY_TEMPL_AMOUNT) - } .task(id: amountToTransfer.value) { // re-compute the fees on every tapped digit or backspace if let computeFee { @@ -111,7 +107,8 @@ struct AmountInputV: View { symLog.log("computeFee() finished") feeStr = result.feeStr // insufficient = result.insufficient -// feeAmount = result.feeAmount + feeAmount = result.feeAmount + numCoins = result.numCoins } else { symLog.log("computeFee() failed") } diff --git a/TalerWallet1/Views/Peer2peer/RequestPayment.swift b/TalerWallet1/Views/Peer2peer/RequestPayment.swift @@ -23,7 +23,7 @@ struct RequestPayment: View { @State private var peerPullCheck: CheckPeerPullCreditResponse? = nil @State private var expireDays: UInt = 0 - @State private var feeAmount: Amount? = nil +// @State private var feeAmount: Amount? = nil @State private var buttonSelected = false @State private var shortcutSelected = false @State private var amountShortcut = Amount.zero(currency: EMPTYSTRING) // Update currency when used @@ -59,7 +59,7 @@ struct RequestPayment: View { peerPullCheck = try? await model.checkPeerPullCreditM(baseURL, amount: amount, cancellationId: "cancel") -// return ComputeFeeResult(insufficient: false, feeAmount: <#T##Amount?#>, feeStr: <#T##String#>) +// return ComputeFeeResult(insufficient: false, feeStr: feeLabel(feeStr), numCoins: details.numCoins) } return nil } @@ -82,7 +82,9 @@ struct RequestPayment: View { let inputDestination = LazyView { P2PSubjectV(stack: stack.push(), currencyInfo: $currencyInfo, - feeLabel: coinData.feeLabel(currencyInfo), + feeLabel: coinData.feeLabel(currencyInfo, + feeZero: String(localized: "No payment fee"), + isNegative: false), feeIsNotZero: feeIsNotZero(), outgoing: false, amountToTransfer: $amountToTransfer, @@ -106,17 +108,14 @@ struct RequestPayment: View { : String(localized: "Amount to request:") AmountInputV(stack: stack.push(), currencyInfo: $currencyInfo, - url: nil, amountAvailable: nil, + amountLabel: amountLabel, amountToTransfer: $amountToTransfer, wireFee: nil, - amountLabel: amountLabel, - summaryIsEditable: true, summary: $summary, -// insufficient: $insufficient, - feeAmount: $feeAmount, shortcutAction: shortcutAction, buttonAction: buttonAction, + feeIsNegative: true, computeFee: computeFeeRequest) .background(NavigationLink(destination: shortcutDestination, isActive: $shortcutSelected) { EmptyView() }.frame(width: 0).opacity(0).hidden() diff --git a/TalerWallet1/Views/Peer2peer/SendAmount.swift b/TalerWallet1/Views/Peer2peer/SendAmount.swift @@ -30,7 +30,7 @@ struct SendAmount: View { @State var peerPushCheck: CheckPeerPushDebitResponse? = nil @State private var expireDays = SEVENDAYS @State private var insufficient = false - @State private var feeAmount: Amount? = nil +// @State private var feeAmount: Amount? = nil @State private var feeStr: String = EMPTYSTRING @State private var buttonSelected = false @State private var shortcutSelected = false @@ -54,12 +54,11 @@ struct SendAmount: View { do { if let ppCheck { // Outgoing: fee = effective - raw - feeAmount = try ppCheck.amountEffective - ppCheck.amountRaw - return feeAmount + let fee = try ppCheck.amountEffective - ppCheck.amountRaw + return fee } } catch {} - feeAmount = nil - return feeAmount + return nil } private func feeIsNotZero() -> Bool? { @@ -68,8 +67,8 @@ struct SendAmount: View { return nil // this exchange never has fees } } - return peerPushCheck != nil ? (!(feeAmount?.isZero ?? false)) - : false + return peerPushCheck == nil ? false + : true // TODO: !(feeAmount?.isZero ?? false) } private func computeFeeSend(_ amount: Amount) async -> ComputeFeeResult? { @@ -150,17 +149,14 @@ struct SendAmount: View { : String(localized: "Amount to send:") AmountInputV(stack: stack.push(), currencyInfo: $currencyInfo, - url: nil, amountAvailable: amountAvailable, + amountLabel: amountLabel, amountToTransfer: $amountToTransfer, wireFee: nil, - amountLabel: amountLabel, - summaryIsEditable: true, summary: $summary, -// insufficient: $insufficient, - feeAmount: $feeAmount, shortcutAction: shortcutAction, buttonAction: buttonAction, + feeIsNegative: false, computeFee: computeFeeSend) .background(NavigationLink(destination: shortcutDestination, isActive: $shortcutSelected) { EmptyView() }.frame(width: 0).opacity(0).hidden() diff --git a/TalerWallet1/Views/Sheets/Payment/PayTemplateV.swift b/TalerWallet1/Views/Sheets/Payment/PayTemplateV.swift @@ -39,8 +39,8 @@ struct PayTemplateV: View { @State private var summaryIsEditable = false @State private var summary: String = EMPTYSTRING // templateParam - @State private var feeAmount: Amount? = nil @State private var currencyInfo: CurrencyInfo = CurrencyInfo.zero(UNKNOWN) +// @State private var feeAmount: Amount? = nil // @State private var feeStr: String = EMPTYSTRING private func shortcutAction(_ shortcut: Amount) { @@ -134,17 +134,15 @@ struct PayTemplateV: View { if amountIsEditable { // template contract amount is not fixed => let the user input an amount first let amountInput = AmountInputV(stack: stack.push(), currencyInfo: $currencyInfo, - url: url, +// url: url, amountAvailable: nil, + amountLabel: amountLabel, amountToTransfer: $amountToTransfer, wireFee: nil, - amountLabel: amountLabel, - summaryIsEditable: summaryIsEditable, summary: $summary, -// insufficient: $insufficient, - feeAmount: $feeAmount, shortcutAction: shortcutAction, buttonAction: buttonAction1, + feeIsNegative: false, computeFee: computeFeePayTemplate) ScrollView { if summaryIsEditable { // after amount input, diff --git a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptView.swift b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptView.swift @@ -73,10 +73,11 @@ struct WithdrawAcceptView: View { merchant: nil) let coinData = CoinData(details: withdrawalAmountDetails) QuiteSomeCoins(currencyInfo: $currencyInfo, +// amountEffective: effective, currency: currency, coinData: coinData, shouldShowFee: true, // TODO: set to false if we never charge withdrawal fees - amountEffective: effective) + feeIsNegative: true) } .listStyle(myListStyle.style).anyView .navigationTitle(navTitle) diff --git a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift @@ -41,7 +41,8 @@ struct WithdrawURIView: View { @State private var defaultExchangeBaseUrl: String? // if nil then use possibleExchanges @State private var currencyInfo: CurrencyInfo = CurrencyInfo.zero(UNKNOWN) - @State private var feeAmount: Amount? = nil +// @State private var feeAmount: Amount? = nil + @State private var withdrawalAmountDetails: WithdrawalAmountDetails? = nil let navTitle = String(localized: "Withdrawal") @@ -70,9 +71,19 @@ struct WithdrawURIView: View { if let details = try? await model.getWithdrawalDetailsForAmountM(exchange.exchangeBaseUrl, amount: amount) { let fee = try? details.amountRaw - details.amountEffective - let feeStr = fee?.readableDescription ?? "nix" + let feeStr = fee?.formatted(currencyInfo, isNegative: true) ?? "nix" symLog.log("Fee = \(feeStr)") + let insufficient = if let amountAvailable { + (try? details.amountRaw < amountAvailable) ?? true + } else { + false + } + withdrawalAmountDetails = details + return ComputeFeeResult(insufficient: insufficient, feeAmount: fee, + feeStr: feeLabel(feeStr), numCoins: details.numCoins) } + } else { + symLog.log("No exchange!") } return nil } @@ -85,7 +96,7 @@ struct WithdrawURIView: View { if possibleExchanges.count > 0 { if let defaultBaseUrl = defaultExchangeBaseUrl ?? possibleExchanges.first?.exchangeBaseUrl { VStack { - let title = String(localized: "Exchange") + let title = String(localized: "using:", comment: "using: exchange.taler.net") if possibleExchanges.count > 1 { Picker(title, selection: $selectedExchange) { ForEach(possibleExchanges, id: \.self) { exchange in @@ -104,10 +115,14 @@ struct WithdrawURIView: View { } } } else { - Text(defaultBaseUrl) - .task { - await loadExchange(defaultBaseUrl) - } + HStack { + Text(title) + Text(defaultBaseUrl.trimURL()) + } + .talerFont(.body) +// .task { +// await loadExchange(defaultBaseUrl) +// } } // load defaultBaseUrl let acceptDestination = WithdrawAcceptView(stack: stack.push(), currencyInfo: $currencyInfo, @@ -115,38 +130,35 @@ struct WithdrawURIView: View { amountToTransfer: $amountToTransfer, exchange: $exchange) if amountIsEditable { - ScrollView { - let shortcutDestination = LazyView { - WithdrawAcceptView(stack: stack.push(), - currencyInfo: $currencyInfo, - url: url, - amountToTransfer: $amountShortcut, - exchange: $exchange) - } - // TODO: input amount, then - let amountLabel = minimalistic ? String(localized: "Amount:") - : String(localized: "Amount to withdraw:") - AmountInputV(stack: stack.push(), - currencyInfo: $currencyInfo, - url: nil, - amountAvailable: amountAvailable, - amountToTransfer: $amountToTransfer, - wireFee: wireFee, - amountLabel: amountLabel, - summaryIsEditable: false, - summary: $summary, -// insufficient: $insufficient, - feeAmount: $feeAmount, - shortcutAction: shortcutAction, - buttonAction: buttonAction, - computeFee: computeFeeWithdraw) - .background(NavigationLink(destination: shortcutDestination, isActive: $shortcutSelected) - { EmptyView() }.frame(width: 0).opacity(0).hidden() - ) // shortcutDestination - .background(NavigationLink(destination: acceptDestination, isActive: $buttonSelected) - { EmptyView() }.frame(width: 0).opacity(0).hidden() - ) // acceptDestination - } + ScrollView { + let shortcutDestination = LazyView { + WithdrawAcceptView(stack: stack.push(), + currencyInfo: $currencyInfo, + url: url, + amountToTransfer: $amountShortcut, + exchange: $exchange) + } + // TODO: input amount, then + let amountLabel = minimalistic ? String(localized: "Amount:") + : String(localized: "Amount to withdraw:") + AmountInputV(stack: stack.push(), + currencyInfo: $currencyInfo, + amountAvailable: amountAvailable, + amountLabel: amountLabel, + amountToTransfer: $amountToTransfer, + wireFee: wireFee, + summary: $summary, + shortcutAction: shortcutAction, + buttonAction: buttonAction, + feeIsNegative: true, + computeFee: computeFeeWithdraw) + .background(NavigationLink(destination: shortcutDestination, isActive: $shortcutSelected) + { EmptyView() }.frame(width: 0).opacity(0).hidden() + ) // shortcutDestination + .background(NavigationLink(destination: acceptDestination, isActive: $buttonSelected) + { EmptyView() }.frame(width: 0).opacity(0).hidden() + ) // acceptDestination + } // ScrollView } else { acceptDestination } // directly show the accept view @@ -180,13 +192,14 @@ struct WithdrawURIView: View { let amount = uriInfoResponse.amount let currency = amount?.currencyStr ?? uriInfoResponse.currency amountToTransfer = amount ?? Amount.zero(currency: currency) + amountIsEditable = uriInfoResponse.editableAmount amountAvailable = uriInfoResponse.maxAmount // may be nil wireFee = uriInfoResponse.wireFee // may be nil let baseUrl = uriInfoResponse.defaultExchangeBaseUrl ?? uriInfoResponse.possibleExchanges[0].exchangeBaseUrl defaultExchangeBaseUrl = uriInfoResponse.defaultExchangeBaseUrl possibleExchanges = uriInfoResponse.possibleExchanges - amountIsEditable = uriInfoResponse.editableAmount + await loadExchange(baseUrl) symLog.log("\(baseUrl.trimURL()) loaded") await controller.checkInfo(for: baseUrl, model: model) symLog.log("Info(for: \(baseUrl.trimURL())) loaded")