taler-ios

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

commit acd7dda4e3a53a90d45bf330ae479b8e72afb2c0
parent 2f2f820ed0dded72a110c4f9c315ad554801a2c3
Author: Marc Stibane <marc@taler.net>
Date:   Wed, 20 Nov 2024 07:28:00 +0100

DepositAmountView

Diffstat:
MTalerWallet.xcodeproj/project.pbxproj | 6++++++
MTalerWallet1/Views/Actions/Banking/DepositAmountV.swift | 189+------------------------------------------------------------------------------
ATalerWallet1/Views/Actions/Banking/DepositAmountView.swift | 205+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 212 insertions(+), 188 deletions(-)

diff --git a/TalerWallet.xcodeproj/project.pbxproj b/TalerWallet.xcodeproj/project.pbxproj @@ -289,6 +289,8 @@ 4EE77E7F2C0280E5007C9064 /* Taler_Wallet InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 4EE77E7E2C0280E5007C9064 /* Taler_Wallet InfoPlist.xcstrings */; }; 4EE77E812C06E513007C9064 /* WithdrawAcceptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE77E802C06E513007C9064 /* WithdrawAcceptView.swift */; }; 4EE77E822C06E513007C9064 /* WithdrawAcceptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE77E802C06E513007C9064 /* WithdrawAcceptView.swift */; }; + 4EE9864F2CE26E0F00F75634 /* DepositAmountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE9864E2CE26E0F00F75634 /* DepositAmountView.swift */; }; + 4EE986502CE26E0F00F75634 /* DepositAmountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE9864E2CE26E0F00F75634 /* DepositAmountView.swift */; }; 4EEBEFB02C8982180020D340 /* View+innerSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEBEFAF2C8982180020D340 /* View+innerSize.swift */; }; 4EEBEFB12C8982180020D340 /* View+innerSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEBEFAF2C8982180020D340 /* View+innerSize.swift */; }; 4EEC118D2B83DE4800146CFF /* AmountInputV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC118C2B83DE4700146CFF /* AmountInputV.swift */; }; @@ -499,6 +501,7 @@ 4EE77E7C2C0280E5007C9064 /* GNU_Taler InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = "GNU_Taler InfoPlist.xcstrings"; sourceTree = "<group>"; }; 4EE77E7E2C0280E5007C9064 /* Taler_Wallet InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = "Taler_Wallet InfoPlist.xcstrings"; sourceTree = "<group>"; }; 4EE77E802C06E513007C9064 /* WithdrawAcceptView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WithdrawAcceptView.swift; sourceTree = "<group>"; }; + 4EE9864E2CE26E0F00F75634 /* DepositAmountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DepositAmountView.swift; sourceTree = "<group>"; }; 4EEBEFAF2C8982180020D340 /* View+innerSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+innerSize.swift"; sourceTree = "<group>"; }; 4EEC118C2B83DE4700146CFF /* AmountInputV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmountInputV.swift; sourceTree = "<group>"; }; 4EEC11922B83FB7A00146CFF /* SubjectInputV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubjectInputV.swift; sourceTree = "<group>"; }; @@ -924,6 +927,7 @@ children = ( 4EBC0F002B7B3CD600C0CB19 /* DepositIbanV.swift */, 4E96583B2B79656E00404A68 /* DepositAmountV.swift */, + 4EE9864E2CE26E0F00F75634 /* DepositAmountView.swift */, 4E50B34F2A1BEE8000F9F01C /* ManualWithdraw.swift */, 4EBA82AC2A3F580500E5F39A /* QuiteSomeCoins.swift */, 4EB431662A1E55C700C5690E /* ManualWithdrawDone.swift */, @@ -1257,6 +1261,7 @@ 4E3EAE3E2A990778009F1BE8 /* ManualDetailsV.swift in Sources */, 4E3EAE3F2A990778009F1BE8 /* View+dismissTop.swift in Sources */, 4E3EAE402A990778009F1BE8 /* TransactionsListView.swift in Sources */, + 4EE9864F2CE26E0F00F75634 /* DepositAmountView.swift in Sources */, 4E0A71142C396D86002485BB /* Error+debugDescription.swift in Sources */, 4E3EAE412A990778009F1BE8 /* WalletBackendRequest.swift in Sources */, 4E3EAE422A990778009F1BE8 /* KeyboardResponder.swift in Sources */, @@ -1392,6 +1397,7 @@ 4E6EDD852A3615BE0031D520 /* ManualDetailsV.swift in Sources */, 4EB0950B2989CB7C0043A8A1 /* View+dismissTop.swift in Sources */, 4EB095562989CBFE0043A8A1 /* TransactionsListView.swift in Sources */, + 4EE986502CE26E0F00F75634 /* DepositAmountView.swift in Sources */, 4E0A71152C396D86002485BB /* Error+debugDescription.swift in Sources */, 4EB0951F2989CBCB0043A8A1 /* WalletBackendRequest.swift in Sources */, 4EAD117629F672FA008EDD0B /* KeyboardResponder.swift in Sources */, diff --git a/TalerWallet1/Views/Actions/Banking/DepositAmountV.swift b/TalerWallet1/Views/Actions/Banking/DepositAmountV.swift @@ -82,7 +82,7 @@ struct DepositAmountV: View { .padding(.horizontal) .padding(.bottom, 4) } - DepositAmountContent(stack: stack.push(), + DepositAmountView(stack: stack.push(), balance: $balance, balanceIndex: $balanceIndex, amountLastUsed: $amountLastUsed, @@ -93,13 +93,6 @@ struct DepositAmountV: View { .navigationTitle(navTitle) .frame(maxWidth: .infinity, alignment: .leading) .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all)) - .onAppear { - DebugViewC.shared.setViewID(VIEW_DEPOSIT, stack: stack.push()) - symLog.log("❗️ \(navTitle) onAppear") - } - .onDisappear { - symLog.log("❗️ \(navTitle) onDisappear") - } .task { await viewDidLoad() } .task(id: balanceIndex + (1000 * controller.currencyTicker)) { await newBalance() } @@ -115,183 +108,3 @@ struct DepositAmountV: View { } } } -// MARK: - -struct DepositAmountContent: View { - private let symLog = SymLogV(0) - let stack: CallStack - @Binding var balance: Balance? - @Binding var balanceIndex: Int - @Binding var amountLastUsed: Amount - @Binding var amountToTransfer: Amount - let amountAvailable: Amount - let paytoUri: String? - - @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 checkDepositResult: CheckDepositResponse? = nil - @State private var insufficient = false - @State private var feeAmount: Amount? = nil - @State private var feeStr: String = EMPTYSTRING - @State private var depositStarted = false - @State private var exchange: Exchange? = nil // wg. noFees - - private func feeLabel(_ feeString: String) -> String { - feeString.count > 0 ? String(localized: "+ \(feeString) fee") - : EMPTYSTRING - } - - private func feeIsNotZero() -> Bool? { - if let hasNoFees = exchange?.noFees { - if hasNoFees { - return nil // this exchange never has fees - } - } - return checkDepositResult == nil ? false - : true // TODO: !(feeAmount?.isZero ?? false) - } - - private func computeFeeDeposit(_ amount: Amount) async -> ComputeFeeResult? { - if amount.isZero { - return ComputeFeeResult.zero() - } - let insufficient = (try? amount > amountAvailable) ?? true - if insufficient { - return ComputeFeeResult.insufficient() - } -// private func fee(ppCheck: CheckDepositResponse?) -> Amount? { - do { -// if let ppCheck { -// // Outgoing: fee = effective - raw -// feeAmount = try ppCheck.fees.coin + ppCheck.fees.wire + ppCheck.fees.refresh -// return feeAmount -// } - } catch { - - } - return nil - } - - private func buttonTitle(_ amount: Amount) -> String { - if let balance { - let amountWithCurrency = amount.formatted(balance.scopeInfo, isNegative: false, useISO: true) - return String(localized: "Deposit \(amountWithCurrency)", comment: "Button: amount with currency") - } - return subjectTitle(amount) // should never happen - } - - private func subjectTitle(_ amount: Amount) -> String { -// let amountStr = amount.formatted(scopeInfo, isNegative: false) - return String(localized: "NavTitle_Deposit_AmountStr", - defaultValue: "Deposit", comment: "NavTitle: Deposit") - // defaultValue: "Deposit \(amountStr)", comment: "NavTitle: Deposit 'amountStr'") - } - - @MainActor - private func startDeposit() { - if let paytoUri { - depositStarted = true // don't run twice - Task { - symLog.log("Deposit") - if let result = try? await model.createDepositGroup(paytoUri, amount: amountToTransfer) { - symLog.log(result.transactionId) - ViewState2.shared.popToRootView(stack.push()) - NotificationCenter.default.post(name: .TransactionDone, object: nil, userInfo: nil) - } else { - depositStarted = false - } - } - } - } - - var body: some View { -#if PRINT_CHANGES - let _ = Self._printChanges() - let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear -#endif - if depositStarted { - LoadingView(scopeInfo: nil, message: "Depositing...") - .navigationBarBackButtonHidden(true) - .interactiveDismissDisabled() // can only use "Done" button to dismiss - } else { Group { - if let balance { - let scopeInfo = balance.scopeInfo - let availableStr = amountAvailable.formatted(scopeInfo, isNegative: false) - - let currencyInfo = controller.info(for: scopeInfo, controller.currencyTicker) -// let amountVoiceOver = amountToTransfer.formatted(scopeInfo, isNegative: false) - let insufficientLabel = String(localized: "You don't have enough \(currencyInfo.specs.name).") -// let insufficientLabel2 = String(localized: "but you only have \(available) to deposit.") - - let disabled = insufficient || amountToTransfer.isZero - - Text("Available:\t\(availableStr)") - .talerFont(.title3) - .padding(.bottom, 2) - CurrencyInputView(scope: scopeInfo, - amount: $amountToTransfer, - amountLastUsed: amountLastUsed, - available: nil, // amountAvailable, - title: minimalistic ? String(localized: "Amount:") - : String(localized: "Amount to deposit:"), - shortcutAction: nil) - - Text(insufficient ? insufficientLabel - : feeLabel(feeStr)) - .talerFont(.body) - .foregroundColor(insufficient ? .red - : (feeAmount?.isZero ?? true) ? WalletColors().secondary(colorScheme, colorSchemeContrast) - : .red) - .padding(4) - Button(buttonTitle(amountToTransfer)) { startDeposit() } - .buttonStyle(TalerButtonStyle(type: .prominent, disabled: disabled || depositStarted)) - .disabled(disabled || depositStarted) - .accessibilityHint(disabled ? String(localized: "enabled when amount is non-zero, but not higher than your available amount") : EMPTYSTRING) - } else { // no balance - Yikes - Text("No balance. There seems to be a problem with the database...") - } - } -// .task(id: amountToTransfer.value) { -// if let amountAvailable { -// do { -// insufficient = try amountToTransfer > amountAvailable -// } catch { -// print("Yikes❗️ insufficient failed❗️") -// insufficient = true -// } -// -// if insufficient { -// announce("\(amountVoiceOver), \(insufficientLabel2)") -// feeStr = EMPTYSTRING -// } -// } -// if !insufficient { -// if amountToTransfer.isZero { -// feeStr = EMPTYSTRING -// checkDepositResult = nil -// } else if let paytoUri { -// if let ppCheck = try? await model.checkDepositM(paytoUri, amount: amountToTransfer) { -// if let feeAmount = fee(ppCheck: ppCheck) { -// feeStr = feeAmount.formatted(scopeInfo, isNegative: false) -// let feeLabel = feeLabel(feeStr) -// announce("\(amountVoiceOver), \(feeLabel)") -// } else { -// feeStr = EMPTYSTRING -// announce(amountVoiceOver) -// } -// checkDepositResult = ppCheck -// } else { -// checkDepositResult = nil -// } -// } -// } -// } - } // else - } // body -} -// MARK: - -#if DEBUG -#endif diff --git a/TalerWallet1/Views/Actions/Banking/DepositAmountView.swift b/TalerWallet1/Views/Actions/Banking/DepositAmountView.swift @@ -0,0 +1,205 @@ +/* + * 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 + +// Called when tapping [􁾭Withdraw] +struct DepositAmountView: View { + private let symLog = SymLogV(0) + let stack: CallStack + @Binding var balance: Balance? + @Binding var balanceIndex: Int + @Binding var amountLastUsed: Amount + @Binding var amountToTransfer: Amount + let amountAvailable: Amount + let paytoUri: String? + + @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 checkDepositResult: CheckDepositResponse? = nil + @State private var insufficient = false + @State private var feeAmount: Amount? = nil + @State private var feeStr: String = EMPTYSTRING + @State private var depositStarted = false + @State private var exchange: Exchange? = nil // wg. noFees + + public static func navTitle(_ currency: String = EMPTYSTRING, + _ condition: Bool = false + ) -> String { + condition ? String(localized: "NavTitle_Deposit_Currency", + defaultValue: "Deposit \(currency)", + comment: "NavTitle: Deposit 'currency'") + : String(localized: "NavTitle_Deposit", + defaultValue: "Deposit", + comment: "NavTitle: Deposit") + } + + private func feeLabel(_ feeString: String) -> String { + feeString.count > 0 ? String(localized: "+ \(feeString) fee") + : EMPTYSTRING + } + + private func feeIsNotZero() -> Bool? { + if let hasNoFees = exchange?.noFees { + if hasNoFees { + return nil // this exchange never has fees + } + } + return checkDepositResult == nil ? false + : true // TODO: !(feeAmount?.isZero ?? false) + } + + @MainActor + private func computeFeeDeposit(_ amount: Amount) async -> ComputeFeeResult? { + if amount.isZero { + return ComputeFeeResult.zero() + } + let insufficient = (try? amount > amountAvailable) ?? true + if insufficient { + return ComputeFeeResult.insufficient() + } +// private func fee(ppCheck: CheckDepositResponse?) -> Amount? { + do { +// if let ppCheck { +// // Outgoing: fee = effective - raw +// feeAmount = try ppCheck.fees.coin + ppCheck.fees.wire + ppCheck.fees.refresh +// return feeAmount +// } + } catch { + + } + return nil + } + + private func buttonTitle(_ amount: Amount) -> String { + if let balance { + let amountWithCurrency = amount.formatted(balance.scopeInfo, isNegative: false, useISO: true) + return DepositAmountView.navTitle(amountWithCurrency, true) + } + return DepositAmountView.navTitle() // should never happen + } + + @MainActor + private func startDeposit() { + if let paytoUri { + depositStarted = true // don't run twice + Task { + symLog.log("Deposit") + if let result = try? await model.createDepositGroup(paytoUri, amount: amountToTransfer) { + symLog.log(result.transactionId) + ViewState2.shared.popToRootView(stack.push()) + NotificationCenter.default.post(name: .TransactionDone, object: nil, userInfo: nil) + } else { + depositStarted = false + } + } + } + } + + var body: some View { +#if PRINT_CHANGES + let _ = Self._printChanges() + let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear +#endif + if depositStarted { + LoadingView(scopeInfo: nil, message: "Depositing...") + .navigationBarBackButtonHidden(true) + .interactiveDismissDisabled() // can only use "Done" button to dismiss + } else { Group { + if let balance { + let scopeInfo = balance.scopeInfo + let availableStr = amountAvailable.formatted(scopeInfo, isNegative: false) + + let currencyInfo = controller.info(for: scopeInfo, controller.currencyTicker) +// let amountVoiceOver = amountToTransfer.formatted(scopeInfo, isNegative: false) + let insufficientLabel = String(localized: "You don't have enough \(currencyInfo.specs.name).") +// let insufficientLabel2 = String(localized: "but you only have \(available) to deposit.") + + let disabled = insufficient || amountToTransfer.isZero + + Text("Available:\t\(availableStr)") + .talerFont(.title3) + .padding(.bottom, 2) + CurrencyInputView(scope: scopeInfo, + amount: $amountToTransfer, + amountLastUsed: amountLastUsed, + available: nil, // amountAvailable, + title: minimalistic ? String(localized: "Amount:") + : String(localized: "Amount to deposit:"), + shortcutAction: nil) + .padding(.horizontal) + + Text(insufficient ? insufficientLabel + : feeLabel(feeStr)) + .talerFont(.body) + .foregroundColor(insufficient ? WalletColors().attention + : (feeAmount?.isZero ?? true) ? WalletColors().secondary(colorScheme, colorSchemeContrast) + : WalletColors().negative) + .padding(4) + Button(buttonTitle(amountToTransfer)) { startDeposit() } + .buttonStyle(TalerButtonStyle(type: .prominent, disabled: disabled || depositStarted)) + .disabled(disabled || depositStarted) + .accessibilityHint(disabled ? String(localized: "enabled when amount is non-zero, but not higher than your available amount") : EMPTYSTRING) + } else { // no balance - Yikes + Text("No balance. There seems to be a problem with the database...") + } + } + .onAppear { + DebugViewC.shared.setViewID(VIEW_DEPOSIT, stack: stack.push()) + symLog.log("❗️ onAppear") + } + .onDisappear { + symLog.log("❗️ onDisappear") + } + +// .task(id: amountToTransfer.value) { +// if let amountAvailable { +// do { +// insufficient = try amountToTransfer > amountAvailable +// } catch { +// print("Yikes❗️ insufficient failed❗️") +// insufficient = true +// } +// +// if insufficient { +// announce("\(amountVoiceOver), \(insufficientLabel2)") +// feeStr = EMPTYSTRING +// } +// } +// if !insufficient { +// if amountToTransfer.isZero { +// feeStr = EMPTYSTRING +// checkDepositResult = nil +// } else if let paytoUri { +// if let ppCheck = try? await model.checkDepositM(paytoUri, amount: amountToTransfer) { +// if let feeAmount = fee(ppCheck: ppCheck) { +// feeStr = feeAmount.formatted(scopeInfo, isNegative: false) +// let feeLabel = feeLabel(feeStr) +// announce("\(amountVoiceOver), \(feeLabel)") +// } else { +// feeStr = EMPTYSTRING +// announce(amountVoiceOver) +// } +// checkDepositResult = ppCheck +// } else { +// checkDepositResult = nil +// } +// } +// } +// } + } // else + } // body +} +// MARK: - +#if DEBUG +#endif