commit f5f19a8aabcf3de80345b01375f433e569f25850
parent 1410e3d878da65ef23889f8c8c941eb506f7c244
Author: Marc Stibane <marc@taler.net>
Date: Fri, 13 Sep 2024 11:52:36 +0200
SendAmountV
Diffstat:
4 files changed, 383 insertions(+), 381 deletions(-)
diff --git a/TalerWallet.xcodeproj/project.pbxproj b/TalerWallet.xcodeproj/project.pbxproj
@@ -53,7 +53,7 @@
4E3EAE2D2A990778009F1BE8 /* Model+Exchange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E3B4BC82A42BC4800CC88B8 /* Model+Exchange.swift */; };
4E3EAE2E2A990778009F1BE8 /* QRCodeDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5A88F42A38A4FD00072618 /* QRCodeDetailView.swift */; };
4E3EAE2F2A990778009F1BE8 /* TransactionsEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E87C8722A31CB7F001C6406 /* TransactionsEmptyView.swift */; };
- 4E3EAE312A990778009F1BE8 /* SendAmount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E40E0BD29F25ABB00B85369 /* SendAmount.swift */; };
+ 4E3EAE312A990778009F1BE8 /* SendAmountV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E40E0BD29F25ABB00B85369 /* SendAmountV.swift */; };
4E3EAE332A990778009F1BE8 /* EqualIconWidthDomain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8E25322A1CD39700A27BFA /* EqualIconWidthDomain.swift */; };
4E3EAE342A990778009F1BE8 /* SuperScriptDigits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA563E2A7FD9390084948B /* SuperScriptDigits.swift */; };
4E3EAE352A990778009F1BE8 /* P2pPayURIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E578E932A4822D500F21F1C /* P2pPayURIView.swift */; };
@@ -140,7 +140,7 @@
4E3EAEA52AA12582009F1BE8 /* Nunito-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4E3EAEA12AA12582009F1BE8 /* Nunito-BoldItalic.ttf */; };
4E3EAEA82AA70157009F1BE8 /* Binding+onChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E3EAEA72AA70157009F1BE8 /* Binding+onChange.swift */; };
4E3EAEA92AA70157009F1BE8 /* Binding+onChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E3EAEA72AA70157009F1BE8 /* Binding+onChange.swift */; };
- 4E40E0BE29F25ABB00B85369 /* SendAmount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E40E0BD29F25ABB00B85369 /* SendAmount.swift */; };
+ 4E40E0BE29F25ABB00B85369 /* SendAmountV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E40E0BD29F25ABB00B85369 /* SendAmountV.swift */; };
4E448AB72C4A4109007D5C92 /* BalancesPendingRowV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E448AB62C4A4109007D5C92 /* BalancesPendingRowV.swift */; };
4E448AB82C4A4109007D5C92 /* BalancesPendingRowV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E448AB62C4A4109007D5C92 /* BalancesPendingRowV.swift */; };
4E4F60A82C3BBF9F003BB669 /* View+Condition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4F60A72C3BBF9F003BB669 /* View+Condition.swift */; };
@@ -377,7 +377,7 @@
4E3EAEA02AA12582009F1BE8 /* Nunito-Italic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Nunito-Italic.ttf"; sourceTree = "<group>"; };
4E3EAEA12AA12582009F1BE8 /* Nunito-BoldItalic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Nunito-BoldItalic.ttf"; sourceTree = "<group>"; };
4E3EAEA72AA70157009F1BE8 /* Binding+onChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Binding+onChange.swift"; sourceTree = "<group>"; };
- 4E40E0BD29F25ABB00B85369 /* SendAmount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendAmount.swift; sourceTree = "<group>"; };
+ 4E40E0BD29F25ABB00B85369 /* SendAmountV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendAmountV.swift; sourceTree = "<group>"; };
4E448AB62C4A4109007D5C92 /* BalancesPendingRowV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BalancesPendingRowV.swift; sourceTree = "<group>"; };
4E4F60A72C3BBF9F003BB669 /* View+Condition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Condition.swift"; sourceTree = "<group>"; };
4E50B34F2A1BEE8000F9F01C /* ManualWithdraw.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManualWithdraw.swift; sourceTree = "<group>"; };
@@ -875,7 +875,7 @@
4ECB627E2A0BA4DA004ABBB7 /* Peer2peer */ = {
isa = PBXGroup;
children = (
- 4E40E0BD29F25ABB00B85369 /* SendAmount.swift */,
+ 4E40E0BD29F25ABB00B85369 /* SendAmountV.swift */,
4E9320442A1645B600A87B0E /* RequestPayment.swift */,
4E7940DD29FC307C00A9AEA1 /* P2PSubjectV.swift */,
4EB3136029FEE79B007D68BC /* P2PReadyV.swift */,
@@ -1216,7 +1216,7 @@
4E3EAE2F2A990778009F1BE8 /* TransactionsEmptyView.swift in Sources */,
4EEBEFB02C8982180020D340 /* View+fixedInnerHeight.swift in Sources */,
4E605DAF2AADDD13002FB9A7 /* UIScreen+screenSize.swift in Sources */,
- 4E3EAE312A990778009F1BE8 /* SendAmount.swift in Sources */,
+ 4E3EAE312A990778009F1BE8 /* SendAmountV.swift in Sources */,
4E3EAE332A990778009F1BE8 /* EqualIconWidthDomain.swift in Sources */,
4EEC11932B83FB7A00146CFF /* SubjectInputV.swift in Sources */,
E3E48FB52B9B7D5000898A0F /* Encodable+toJSON.swift in Sources */,
@@ -1348,7 +1348,7 @@
4E87C8732A31CB7F001C6406 /* TransactionsEmptyView.swift in Sources */,
4EEBEFB12C8982180020D340 /* View+fixedInnerHeight.swift in Sources */,
4E605DB02AADDD13002FB9A7 /* UIScreen+screenSize.swift in Sources */,
- 4E40E0BE29F25ABB00B85369 /* SendAmount.swift in Sources */,
+ 4E40E0BE29F25ABB00B85369 /* SendAmountV.swift in Sources */,
4E8E25332A1CD39700A27BFA /* EqualIconWidthDomain.swift in Sources */,
4EEC11942B83FB7A00146CFF /* SubjectInputV.swift in Sources */,
E3E48FB62B9B7D5000898A0F /* Encodable+toJSON.swift in Sources */,
diff --git a/TalerWallet1/Views/Actions/Peer2peer/SendAmount.swift b/TalerWallet1/Views/Actions/Peer2peer/SendAmount.swift
@@ -1,367 +0,0 @@
-/*
- * 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 "Send Coins" in the balances list
-struct SendAmount: View {
- private let symLog = SymLogV(0)
- let stack: CallStack
- let balances: [Balance]
- @Binding var selectedBalance: Balance? // selected balance when the action button is tapped in Transactions
- @Binding var amountToTransfer: Amount
- @Binding var summary: String
- let scopeInfo: ScopeInfo
- let cameraAction: () -> Void
-
- @State private var balanceIndex = 0
- @State private var nonZeroBalances: [Balance] = []
- @State private var balance: Balance? = nil
-
- var body: some View {
-#if PRINT_CHANGES
- let _ = Self._printChanges()
-#endif
-// nonZeroBalances.count > 0
-// let balance = selectedBalance ?? nonZeroBalances[balanceIndex]
- let sendAmountView = 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]
- }
- }
- SendAmountContent(stack: stack.push(),
- balance: $balance,
- balanceIndex: $balanceIndex,
- amountToTransfer: $amountToTransfer,
- summary: $summary,
- cameraAction: cameraAction)
- } // ScrollView
- .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
-// .scrollBounceBehavior(.basedOnSize) needs iOS 16.4
- .task {
- if selectedBalance == nil {
- nonZeroBalances = Balance.nonZeroBalances(balances)
- let count = nonZeroBalances.count
- if balanceIndex >= count {
- balanceIndex = 0
- }
- if count > 0 {
- balance = nonZeroBalances[balanceIndex]
- } else {
- balance = nil
- }
- } else {
- balance = selectedBalance
- }
- }
-
- if #available(iOS 16.0, *) {
- sendAmountView.toolbar(.hidden, for: .tabBar)
- } else {
- sendAmountView
- }
- }
-}
-// MARK: -
-struct SendAmountContent: View {
- private let symLog = SymLogV()
- let stack: CallStack
- @Binding var balance: Balance?
- @Binding var balanceIndex: Int
- @Binding var amountToTransfer: Amount
- @Binding var summary: String
- let cameraAction: () -> Void
-
- // 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
- @State private var expireDays = SEVENDAYS
- @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 shortcutSelected = false
- @State private var amountShortcut = Amount.zero(currency: EMPTYSTRING) // Update currency when used
- @State private var amountAvailable = Amount.zero(currency: EMPTYSTRING) // GetMaxPeerPushAmount
- @State private var exchange: Exchange? = nil // wg. noFees
-
- @State private var currencyInfo = CurrencyInfo.zero(UNKNOWN)
- @State private var currencyName = UNKNOWN
- @State private var currencySymbol = UNKNOWN
-
- private func shortcutAction(_ shortcut: Amount) {
- amountShortcut = shortcut
- shortcutSelected = true
- }
- private func buttonAction() { buttonSelected = true }
-
- private func feeLabel(_ feeString: String) -> String {
- feeString.count > 0 ? String(localized: "+ \(feeString) fee")
- : EMPTYSTRING
- }
-
- private func fee(raw: Amount, effective: Amount) -> Amount? {
- do { // Outgoing: fee = effective - raw
- let fee = try effective - raw
- return fee
- } catch {}
- return nil
- }
-
- private func feeIsNotZero() -> Bool? {
- if let hasNoFees = exchange?.noFees {
- if hasNoFees {
- return nil // this exchange never has fees
- }
- }
- return peerPushCheck == nil ? false
- : true // TODO: !(feeAmount?.isZero ?? false)
- }
-
- private func computeFeeSend(_ amount: Amount) async -> ComputeFeeResult? {
- if amount.isZero {
- return ComputeFeeResult.zero()
- }
- let insufficient = (try? amount > amountAvailable) ?? true
- if insufficient {
- return ComputeFeeResult.insufficient()
- }
- do {
- let ppCheck = try await model.checkPeerPushDebitM(amount, scope: scopeInfo, viewHandles: true)
- let raw = ppCheck.amountRaw
- let effective = ppCheck.amountEffective
- if let fee = fee(raw: raw, effective: effective) {
- feeStr = fee.formatted(currencyInfo, isNegative: false)
- symLog.log("Fee = \(feeStr)")
- let insufficient = (try? effective > amountAvailable) ?? true
-
- peerPushCheck = ppCheck
- let feeLabel = feeLabel(feeStr)
-// announce("\(amountVoiceOver), \(feeLabel)")
- return ComputeFeeResult(insufficient: insufficient,
- feeAmount: fee,
- feeStr: feeLabel,
- numCoins: nil)
- } else {
- peerPushCheck = nil
- }
- } catch {
- // handle cancel, errors
- 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
- }
-
- var body: some View {
-#if true //PRINT_CHANGES
- let _ = Self._printChanges()
- let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear
-#endif
- let navTitle = String(localized: "NavTitle_Send",
- defaultValue: "Send",
- comment: "NavTitle: Send")
- let availableStr = amountAvailable.formatted(currencyInfo, isNegative: false)
- let amountVoiceOver = amountToTransfer.formatted(currencyInfo, isNegative: false)
- let insufficientLabel2 = String(localized: "but you only have \(availableStr) to send.")
-
- let inputDestination = LazyView {
- P2PSubjectV(stack: stack.push(),
- scope: scopeInfo,
- currencyInfo: $currencyInfo,
- feeLabel: feeLabel(feeStr),
- feeIsNotZero: feeIsNotZero(),
- outgoing: true,
- amountToTransfer: $amountToTransfer, // from the textedit
- summary: $summary,
- expireDays: $expireDays)
- }
- let shortcutDestination = LazyView {
- P2PSubjectV(stack: stack.push(),
- scope: scopeInfo,
- currencyInfo: $currencyInfo,
- feeLabel: nil,
- feeIsNotZero: feeIsNotZero(),
- outgoing: true,
- amountToTransfer: $amountShortcut, // from the tapped shortcut button
- summary: $summary,
- expireDays: $expireDays)
- }
-
- Group {
- let amountLabel = minimalistic ? String(localized: "Amount:")
- : String(localized: "Amount to send:")
- AmountInputV(stack: stack.push(),
- currencyInfo: $currencyInfo,
- amountAvailable: $amountAvailable,
- amountLabel: amountLabel,
- amountToTransfer: $amountToTransfer,
- wireFee: nil,
- summary: $summary,
- shortcutAction: shortcutAction,
- buttonAction: buttonAction,
- feeIsNegative: false,
- computeFee: computeFeeSend)
- .background(NavigationLink(destination: shortcutDestination, isActive: $shortcutSelected)
- { EmptyView() }.frame(width: 0).opacity(0).hidden()
- ) // shortcutDestination
- .background(NavigationLink(destination: inputDestination, isActive: $buttonSelected)
- { EmptyView() }.frame(width: 0).opacity(0).hidden()
- ) // inputDestination
- }
- .frame(maxWidth: .infinity, alignment: .leading)
- .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
- .navigationTitle(navTitle)
- .task {
- do {
- let amount = try await model.getMaxPeerPushDebitAmountM(amountToTransfer.currencyStr,
- scope: scopeInfo, viewHandles: true)
- amountAvailable = amount
- } catch {
- amountAvailable = Amount.zero(currency: amountToTransfer.currencyStr)
- }
- }
- .onAppear {
- DebugViewC.shared.setViewID(VIEW_P2P_SEND, stack: stack.push())
- // if we set >> controller.frontendState = -1 << here, then becomeFirstResponder won't work!
- symLog.log("❗️ \(navTitle) onAppear")
- }
- .onDisappear {
- symLog.log("❗️ \(navTitle) onDisappear")
- }
- .navigationBarItems(trailing: QRButton(action: cameraAction))
- .task(id: balanceIndex + (1000 * controller.currencyTicker)) {
- if let balance {
- let scopeInfo = balance.scopeInfo
- let currency = scopeInfo.currency
- amountAvailable = balance.available
- 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 {
-// exchange = try? await model.getExchangeByUrl(url: url)
-// }
-// }
-// do {
-// insufficient = try amountToTransfer > amountAvailable
-// } catch {
-// print("Yikes❗️ insufficient failed❗️")
-// insufficient = true
-// }
-//
-// if insufficient {
-// announce("\(amountVoiceOver), \(insufficientLabel2)")
-// } else if amountToTransfer.isZero {
-// feeStr = EMPTYSTRING
-// } else {
-// if let ppCheck = try? await model.checkPeerPushDebitM(amountToTransfer) {
-// // TODO: set from exchange
-//// agePicker.setAges(ages: peerPushCheck?.ageRestrictionOptions)
-// if let feeAmount = fee(ppCheck: ppCheck) {
-// feeStr = feeAmount.formatted(currencyInfo, isNegative: false)
-// let feeLabel = feeLabel(feeStr)
-// announce("\(amountVoiceOver), \(feeLabel)")
-// } else {
-// feeStr = EMPTYSTRING
-// announce(amountVoiceOver)
-// }
-// peerPushCheck = ppCheck
-// } else {
-// peerPushCheck = nil
-// }
-// }
-// }
- }
-}
-// MARK: -
-#if DEBUG
-fileprivate struct Preview_Content: View {
- @State private var amountToPreview = Amount(currency: DEMOCURRENCY, cent: 510)
- @State private var summary: String = ""
- @State private var currencyInfoL: CurrencyInfo = CurrencyInfo.zero(DEMOCURRENCY)
- @State private var noBalance: Balance? = nil
-
- private func checkCameraAvailable() -> Void {
- // Open Camera when QR-Button was tapped
- }
- var body: some View {
- let amount = Amount(currency: DEMOCURRENCY, cent: 1000)
- let pending = Amount(currency: DEMOCURRENCY, cent: 0)
- let currencyInfo = CurrencyInfo.zero(DEMOCURRENCY)
- let exchange2 = Exchange(exchangeBaseUrl: ARS_EXP_EXCHANGE,
- masterPub: "masterPub",
- scopeInfo: currencyInfo.scope,
- paytoUris: [],
- tosStatus: .proposed,
- exchangeEntryStatus: .ephemeral,
- exchangeUpdateStatus: .ready,
- ageRestrictionOptions: [])
- let scopeInfo = ScopeInfo(type: .exchange, url: DEMOEXCHANGE, currency: DEMOCURRENCY)
- let balance = Balance(scopeInfo: scopeInfo,
- available: amount,
- pendingIncoming: pending,
- pendingOutgoing: pending,
- flags: [])
- SendAmount(stack: CallStack("Preview"),
- balances: [balance],
- selectedBalance: $noBalance,
- amountToTransfer: $amountToPreview,
- summary: $summary,
- scopeInfo: currencyInfo.scope,
- cameraAction: checkCameraAvailable)
- }
-}
-
-fileprivate struct Previews: PreviewProvider {
- @MainActor
- struct StateContainer: View {
- @StateObject private var controller = Controller.shared
- var body: some View {
- Preview_Content()
- .environmentObject(controller)
- }
- }
- static var previews: some View {
- StateContainer()
- }
-}
-#endif
diff --git a/TalerWallet1/Views/Actions/Peer2peer/SendAmountV.swift b/TalerWallet1/Views/Actions/Peer2peer/SendAmountV.swift
@@ -0,0 +1,369 @@
+/*
+ * 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 "Send Coins" in the balances list
+struct SendAmountV: View {
+ private let symLog = SymLogV(0)
+ let stack: CallStack
+ let balances: [Balance]
+ @Binding var selectedBalance: Balance? // selected balance when the action button is tapped in Transactions
+ @Binding var amountToTransfer: Amount
+ @Binding var summary: String
+ let scopeInfo: ScopeInfo
+ let cameraAction: () -> Void
+
+ @State private var balanceIndex = 0
+ @State private var nonZeroBalances: [Balance] = []
+ @State private var balance: Balance? = nil
+
+ var body: some View {
+#if PRINT_CHANGES
+ let _ = Self._printChanges()
+#endif
+// nonZeroBalances.count > 0
+// let balance = selectedBalance ?? nonZeroBalances[balanceIndex]
+ let sendAmountView = 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]
+ }
+ }
+ SendAmountContent(stack: stack.push(),
+ balance: $balance,
+ balanceIndex: $balanceIndex,
+ amountToTransfer: $amountToTransfer,
+ summary: $summary,
+ scopeInfo: scopeInfo,
+ cameraAction: cameraAction)
+ } // ScrollView
+ .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+// .scrollBounceBehavior(.basedOnSize) needs iOS 16.4
+ .task {
+ if selectedBalance == nil {
+ nonZeroBalances = Balance.nonZeroBalances(balances)
+ let count = nonZeroBalances.count
+ if balanceIndex >= count {
+ balanceIndex = 0
+ }
+ if count > 0 {
+ balance = nonZeroBalances[balanceIndex]
+ } else {
+ balance = nil
+ }
+ } else {
+ balance = selectedBalance
+ }
+ }
+
+ if #available(iOS 16.0, *) {
+ sendAmountView.toolbar(.hidden, for: .tabBar)
+ } else {
+ sendAmountView
+ }
+ }
+}
+// MARK: -
+struct SendAmountContent: View {
+ private let symLog = SymLogV()
+ let stack: CallStack
+ @Binding var balance: Balance?
+ @Binding var balanceIndex: Int
+ @Binding var amountToTransfer: Amount
+ @Binding var summary: String
+ let scopeInfo: ScopeInfo
+ let cameraAction: () -> Void
+
+ // 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
+ @State private var expireDays = SEVENDAYS
+ @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 shortcutSelected = false
+ @State private var amountShortcut = Amount.zero(currency: EMPTYSTRING) // Update currency when used
+ @State private var amountAvailable = Amount.zero(currency: EMPTYSTRING) // GetMaxPeerPushAmount
+ @State private var exchange: Exchange? = nil // wg. noFees
+
+ @State private var currencyInfo = CurrencyInfo.zero(UNKNOWN)
+ @State private var currencyName = UNKNOWN
+ @State private var currencySymbol = UNKNOWN
+
+ private func shortcutAction(_ shortcut: Amount) {
+ amountShortcut = shortcut
+ shortcutSelected = true
+ }
+ private func buttonAction() { buttonSelected = true }
+
+ private func feeLabel(_ feeString: String) -> String {
+ feeString.count > 0 ? String(localized: "+ \(feeString) fee")
+ : EMPTYSTRING
+ }
+
+ private func fee(raw: Amount, effective: Amount) -> Amount? {
+ do { // Outgoing: fee = effective - raw
+ let fee = try effective - raw
+ return fee
+ } catch {}
+ return nil
+ }
+
+ private func feeIsNotZero() -> Bool? {
+ if let hasNoFees = exchange?.noFees {
+ if hasNoFees {
+ return nil // this exchange never has fees
+ }
+ }
+ return peerPushCheck == nil ? false
+ : true // TODO: !(feeAmount?.isZero ?? false)
+ }
+
+ private func computeFeeSend(_ amount: Amount) async -> ComputeFeeResult? {
+ if amount.isZero {
+ return ComputeFeeResult.zero()
+ }
+ let insufficient = (try? amount > amountAvailable) ?? true
+ if insufficient {
+ return ComputeFeeResult.insufficient()
+ }
+ do {
+ let ppCheck = try await model.checkPeerPushDebitM(amount, scope: scopeInfo, viewHandles: true)
+ let raw = ppCheck.amountRaw
+ let effective = ppCheck.amountEffective
+ if let fee = fee(raw: raw, effective: effective) {
+ feeStr = fee.formatted(currencyInfo, isNegative: false)
+ symLog.log("Fee = \(feeStr)")
+ let insufficient = (try? effective > amountAvailable) ?? true
+
+ peerPushCheck = ppCheck
+ let feeLabel = feeLabel(feeStr)
+// announce("\(amountVoiceOver), \(feeLabel)")
+ return ComputeFeeResult(insufficient: insufficient,
+ feeAmount: fee,
+ feeStr: feeLabel,
+ numCoins: nil)
+ } else {
+ peerPushCheck = nil
+ }
+ } catch {
+ // handle cancel, errors
+ 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
+ }
+
+ var body: some View {
+#if true //PRINT_CHANGES
+ let _ = Self._printChanges()
+ let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear
+#endif
+ let navTitle = String(localized: "NavTitle_Send",
+ defaultValue: "Send",
+ comment: "NavTitle: Send")
+ let availableStr = amountAvailable.formatted(currencyInfo, isNegative: false)
+ let amountVoiceOver = amountToTransfer.formatted(currencyInfo, isNegative: false)
+ let insufficientLabel2 = String(localized: "but you only have \(availableStr) to send.")
+
+ let inputDestination = LazyView {
+ P2PSubjectV(stack: stack.push(),
+ scope: scopeInfo,
+ currencyInfo: $currencyInfo,
+ feeLabel: feeLabel(feeStr),
+ feeIsNotZero: feeIsNotZero(),
+ outgoing: true,
+ amountToTransfer: $amountToTransfer, // from the textedit
+ summary: $summary,
+ expireDays: $expireDays)
+ }
+ let shortcutDestination = LazyView {
+ P2PSubjectV(stack: stack.push(),
+ scope: scopeInfo,
+ currencyInfo: $currencyInfo,
+ feeLabel: nil,
+ feeIsNotZero: feeIsNotZero(),
+ outgoing: true,
+ amountToTransfer: $amountShortcut, // from the tapped shortcut button
+ summary: $summary,
+ expireDays: $expireDays)
+ }
+
+ Group {
+ let amountLabel = minimalistic ? String(localized: "Amount:")
+ : String(localized: "Amount to send:")
+ AmountInputV(stack: stack.push(),
+ currencyInfo: $currencyInfo,
+ amountAvailable: $amountAvailable,
+ amountLabel: amountLabel,
+ amountToTransfer: $amountToTransfer,
+ wireFee: nil,
+ summary: $summary,
+ shortcutAction: shortcutAction,
+ buttonAction: buttonAction,
+ feeIsNegative: false,
+ computeFee: computeFeeSend)
+ .background(NavigationLink(destination: shortcutDestination, isActive: $shortcutSelected)
+ { EmptyView() }.frame(width: 0).opacity(0).hidden()
+ ) // shortcutDestination
+ .background(NavigationLink(destination: inputDestination, isActive: $buttonSelected)
+ { EmptyView() }.frame(width: 0).opacity(0).hidden()
+ ) // inputDestination
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+ .navigationTitle(navTitle)
+ .task {
+ do {
+ let amount = try await model.getMaxPeerPushDebitAmountM(amountToTransfer.currencyStr,
+ scope: scopeInfo, viewHandles: true)
+ amountAvailable = amount
+ } catch {
+ amountAvailable = Amount.zero(currency: amountToTransfer.currencyStr)
+ }
+ }
+ .onAppear {
+ DebugViewC.shared.setViewID(VIEW_P2P_SEND, stack: stack.push())
+ // if we set >> controller.frontendState = -1 << here, then becomeFirstResponder won't work!
+ symLog.log("❗️ \(navTitle) onAppear")
+ }
+ .onDisappear {
+ symLog.log("❗️ \(navTitle) onDisappear")
+ }
+ .navigationBarItems(trailing: QRButton(action: cameraAction))
+ .task(id: balanceIndex + (1000 * controller.currencyTicker)) {
+ if let balance {
+ let scopeInfo = balance.scopeInfo
+ let currency = scopeInfo.currency
+ amountAvailable = balance.available
+ 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 {
+// exchange = try? await model.getExchangeByUrl(url: url)
+// }
+// }
+// do {
+// insufficient = try amountToTransfer > amountAvailable
+// } catch {
+// print("Yikes❗️ insufficient failed❗️")
+// insufficient = true
+// }
+//
+// if insufficient {
+// announce("\(amountVoiceOver), \(insufficientLabel2)")
+// } else if amountToTransfer.isZero {
+// feeStr = EMPTYSTRING
+// } else {
+// if let ppCheck = try? await model.checkPeerPushDebitM(amountToTransfer) {
+// // TODO: set from exchange
+//// agePicker.setAges(ages: peerPushCheck?.ageRestrictionOptions)
+// if let feeAmount = fee(ppCheck: ppCheck) {
+// feeStr = feeAmount.formatted(currencyInfo, isNegative: false)
+// let feeLabel = feeLabel(feeStr)
+// announce("\(amountVoiceOver), \(feeLabel)")
+// } else {
+// feeStr = EMPTYSTRING
+// announce(amountVoiceOver)
+// }
+// peerPushCheck = ppCheck
+// } else {
+// peerPushCheck = nil
+// }
+// }
+// }
+ }
+}
+// MARK: -
+#if DEBUG
+fileprivate struct Preview_Content: View {
+ @State private var amountToPreview = Amount(currency: DEMOCURRENCY, cent: 510)
+ @State private var summary: String = ""
+ @State private var currencyInfoL: CurrencyInfo = CurrencyInfo.zero(DEMOCURRENCY)
+ @State private var noBalance: Balance? = nil
+
+ private func checkCameraAvailable() -> Void {
+ // Open Camera when QR-Button was tapped
+ }
+ var body: some View {
+ let amount = Amount(currency: DEMOCURRENCY, cent: 1000)
+ let pending = Amount(currency: DEMOCURRENCY, cent: 0)
+ let currencyInfo = CurrencyInfo.zero(DEMOCURRENCY)
+ let exchange2 = Exchange(exchangeBaseUrl: ARS_EXP_EXCHANGE,
+ masterPub: "masterPub",
+ scopeInfo: currencyInfo.scope,
+ paytoUris: [],
+ tosStatus: .proposed,
+ exchangeEntryStatus: .ephemeral,
+ exchangeUpdateStatus: .ready,
+ ageRestrictionOptions: [])
+ let scopeInfo = ScopeInfo(type: .exchange, url: DEMOEXCHANGE, currency: DEMOCURRENCY)
+ let balance = Balance(scopeInfo: scopeInfo,
+ available: amount,
+ pendingIncoming: pending,
+ pendingOutgoing: pending,
+ flags: [])
+ SendAmountV(stack: CallStack("Preview"),
+ balances: [balance],
+ selectedBalance: $noBalance,
+ amountToTransfer: $amountToPreview,
+ summary: $summary,
+ scopeInfo: currencyInfo.scope,
+ cameraAction: checkCameraAvailable)
+ }
+}
+
+fileprivate struct Previews: PreviewProvider {
+ @MainActor
+ struct StateContainer: View {
+ @StateObject private var controller = Controller.shared
+ var body: some View {
+ Preview_Content()
+ .environmentObject(controller)
+ }
+ }
+ static var previews: some View {
+ StateContainer()
+ }
+}
+#endif
diff --git a/TalerWallet1/Views/Main/MainView.swift b/TalerWallet1/Views/Main/MainView.swift
@@ -321,18 +321,18 @@ extension MainView {
url: DEMOEXCHANGE,
currency: DEMOCURRENCY)
- let sendDest = SendAmount(stack: stack.push("\(Self.className())()"),
- balances: balances,
- selectedBalance: $selectedBalance, // if nil shows currency picker
- amountToTransfer: $amountToTransfer, // with correct currency
- summary: $summary,
- scopeInfo: scope,
- cameraAction: cameraAction)
+ let sendDest = SendAmountV(stack: stack.push("\(Self.className())()"),
+ balances: balances,
+ selectedBalance: $selectedBalance, // if nil shows currency picker
+ amountToTransfer: $amountToTransfer, // currency needs to be updated!
+ summary: $summary,
+ scopeInfo: scope,
+ cameraAction: cameraAction)
let requestDest = RequestPayment(stack: stack.push("\(Self.className())()"),
balances: balances,
selectedBalance: $selectedBalance,
- amountToTransfer: $amountToTransfer, // with correct currency
+ amountToTransfer: $amountToTransfer, // currency needs to be updated!
summary: $summary,
scopeInfo: scope,
cameraAction: cameraAction)