commit d2a363c17370d1af852683cf4ca5f0877fdca6a9
parent b8635ef50af639db230a8323712a2d2022ccda13
Author: Marc Stibane <marc@taler.net>
Date: Tue, 20 Feb 2024 19:15:34 +0100
Pay-Template
Diffstat:
8 files changed, 499 insertions(+), 154 deletions(-)
diff --git a/TalerWallet.xcodeproj/project.pbxproj b/TalerWallet.xcodeproj/project.pbxproj
@@ -65,7 +65,6 @@
4E3EAE452A990778009F1BE8 /* P2PReadyV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB3136029FEE79B007D68BC /* P2PReadyV.swift */; };
4E3EAE462A990778009F1BE8 /* TextFieldAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095482989CBFE0043A8A1 /* TextFieldAlert.swift */; };
4E3EAE472A990778009F1BE8 /* QuiteSomeCoins.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA82AC2A3F580500E5F39A /* QuiteSomeCoins.swift */; };
- 4E3EAE482A990778009F1BE8 /* PayTemplateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA56402A7FF5200084948B /* PayTemplateView.swift */; };
4E3EAE492A990778009F1BE8 /* ManualWithdrawDone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB431662A1E55C700C5690E /* ManualWithdrawDone.swift */; };
4E3EAE4B2A990778009F1BE8 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E753A072A0B6A5F002D9328 /* ShareSheet.swift */; };
4E3EAE4C2A990778009F1BE8 /* AmountRowV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095492989CBFE0043A8A1 /* AmountRowV.swift */; };
@@ -227,7 +226,6 @@
4EB3136129FEE79B007D68BC /* P2PReadyV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB3136029FEE79B007D68BC /* P2PReadyV.swift */; };
4EB431672A1E55C700C5690E /* ManualWithdrawDone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB431662A1E55C700C5690E /* ManualWithdrawDone.swift */; };
4EBA563F2A7FD9390084948B /* SuperScriptDigits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA563E2A7FD9390084948B /* SuperScriptDigits.swift */; };
- 4EBA56412A7FF5200084948B /* PayTemplateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA56402A7FF5200084948B /* PayTemplateView.swift */; };
4EBA82AB2A3EB2CA00E5F39A /* TransactionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA82AA2A3EB2CA00E5F39A /* TransactionButton.swift */; };
4EBA82AD2A3F580500E5F39A /* QuiteSomeCoins.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA82AC2A3F580500E5F39A /* QuiteSomeCoins.swift */; };
4EBC0F012B7B3CD600C0CB19 /* DepositIbanV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBC0F002B7B3CD600C0CB19 /* DepositIbanV.swift */; };
@@ -247,6 +245,12 @@
4EE171882B49635800BF9FF5 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4EE171872B49635800BF9FF5 /* MarkdownUI */; };
4EE171902B49FE2B00BF9FF5 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 4EE1718F2B49FE2B00BF9FF5 /* OrderedCollections */; };
4EE171922B49FE4E00BF9FF5 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 4EE171912B49FE4E00BF9FF5 /* OrderedCollections */; };
+ 4EEC118D2B83DE4800146CFF /* AmountInputV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC118C2B83DE4700146CFF /* AmountInputV.swift */; };
+ 4EEC118E2B83DE4800146CFF /* AmountInputV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC118C2B83DE4700146CFF /* AmountInputV.swift */; };
+ 4EEC11932B83FB7A00146CFF /* SubjectInputV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC11922B83FB7A00146CFF /* SubjectInputV.swift */; };
+ 4EEC11942B83FB7A00146CFF /* SubjectInputV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC11922B83FB7A00146CFF /* SubjectInputV.swift */; };
+ 4EEC11962B840F1100146CFF /* PayTemplateV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC11952B840F1100146CFF /* PayTemplateV.swift */; };
+ 4EEC11972B840F1100146CFF /* PayTemplateV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC11952B840F1100146CFF /* PayTemplateV.swift */; };
4EEC157329F8242800D46A03 /* QRGeneratorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC157229F8242800D46A03 /* QRGeneratorView.swift */; };
4EEC157629F8ECBF00D46A03 /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = 4EEC157529F8ECBF00D46A03 /* CodeScanner */; };
4EEC157829F9032900D46A03 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC157729F9032900D46A03 /* Sheet.swift */; };
@@ -411,7 +415,6 @@
4EB3136029FEE79B007D68BC /* P2PReadyV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = P2PReadyV.swift; sourceTree = "<group>"; };
4EB431662A1E55C700C5690E /* ManualWithdrawDone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualWithdrawDone.swift; sourceTree = "<group>"; };
4EBA563E2A7FD9390084948B /* SuperScriptDigits.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuperScriptDigits.swift; sourceTree = "<group>"; };
- 4EBA56402A7FF5200084948B /* PayTemplateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayTemplateView.swift; sourceTree = "<group>"; };
4EBA82AA2A3EB2CA00E5F39A /* TransactionButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionButton.swift; sourceTree = "<group>"; };
4EBA82AC2A3F580500E5F39A /* QuiteSomeCoins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuiteSomeCoins.swift; sourceTree = "<group>"; };
4EBC0F002B7B3CD600C0CB19 /* DepositIbanV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DepositIbanV.swift; sourceTree = "<group>"; };
@@ -423,6 +426,9 @@
4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectDays.swift; sourceTree = "<group>"; };
4ED2F94A2A278F5100453B40 /* ThreeAmountsV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeAmountsV.swift; sourceTree = "<group>"; };
4EDBDCD82AB787CB00925C02 /* CallStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallStack.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>"; };
+ 4EEC11952B840F1100146CFF /* PayTemplateV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayTemplateV.swift; sourceTree = "<group>"; };
4EEC157229F8242800D46A03 /* QRGeneratorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRGeneratorView.swift; sourceTree = "<group>"; };
4EEC157729F9032900D46A03 /* Sheet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = "<group>"; };
4EEC157929F9427F00D46A03 /* QRSheet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRSheet.swift; sourceTree = "<group>"; };
@@ -698,7 +704,7 @@
children = (
4EB0952D2989CBFE0043A8A1 /* PaymentView.swift */,
4E6EF56A2B65A33300AF252A /* PaymentDone.swift */,
- 4EBA56402A7FF5200084948B /* PayTemplateView.swift */,
+ 4EEC11952B840F1100146CFF /* PayTemplateV.swift */,
);
path = Payment;
sourceTree = "<group>";
@@ -758,6 +764,8 @@
4EF840A62A0B85F400EE0D47 /* CopyShare.swift */,
4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */,
4EA551242A2C923600FEC9A8 /* CurrencyInputView.swift */,
+ 4EEC118C2B83DE4700146CFF /* AmountInputV.swift */,
+ 4EEC11922B83FB7A00146CFF /* SubjectInputV.swift */,
4E2D8DD42B45822A00234039 /* AmountV.swift */,
4E53A33629F50B7B00830EC2 /* CurrencyField.swift */,
4EEC157229F8242800D46A03 /* QRGeneratorView.swift */,
@@ -1068,6 +1076,7 @@
4E3EAE202A990778009F1BE8 /* MainView.swift in Sources */,
4E3EAE212A990778009F1BE8 /* Buttons.swift in Sources */,
4E3EAE222A990778009F1BE8 /* TransactionButton.swift in Sources */,
+ 4EEC118D2B83DE4800146CFF /* AmountInputV.swift in Sources */,
4E3EAE232A990778009F1BE8 /* BalancesSectionView.swift in Sources */,
4E3EAE242A990778009F1BE8 /* QRGeneratorView.swift in Sources */,
4E3EAE252A990778009F1BE8 /* WithdrawAcceptDone.swift in Sources */,
@@ -1090,11 +1099,13 @@
4E605DAF2AADDD13002FB9A7 /* UIScreen+screenSize.swift in Sources */,
4E3EAE312A990778009F1BE8 /* SendAmount.swift in Sources */,
4E3EAE332A990778009F1BE8 /* EqualIconWidthDomain.swift in Sources */,
+ 4EEC11932B83FB7A00146CFF /* SubjectInputV.swift in Sources */,
4E3EAE342A990778009F1BE8 /* SuperScriptDigits.swift in Sources */,
4E3EAE352A990778009F1BE8 /* P2pPayURIView.swift in Sources */,
4E3EAE362A990778009F1BE8 /* Model+Payment.swift in Sources */,
4E3EAE372A990778009F1BE8 /* SettingsView.swift in Sources */,
4E3EAE382A990778009F1BE8 /* PaymentView.swift in Sources */,
+ 4EEC11962B840F1100146CFF /* PayTemplateV.swift in Sources */,
4E3EAE392A990778009F1BE8 /* WithdrawURIView.swift in Sources */,
4E3EAE3A2A990778009F1BE8 /* CopyShare.swift in Sources */,
4E3EAE3B2A990778009F1BE8 /* TalerWallet1App.swift in Sources */,
@@ -1111,7 +1122,6 @@
4E3EAE462A990778009F1BE8 /* TextFieldAlert.swift in Sources */,
4E3EAE472A990778009F1BE8 /* QuiteSomeCoins.swift in Sources */,
4E2D8DD52B45822A00234039 /* AmountV.swift in Sources */,
- 4E3EAE482A990778009F1BE8 /* PayTemplateView.swift in Sources */,
4E3EAE492A990778009F1BE8 /* ManualWithdrawDone.swift in Sources */,
4E3EAE4B2A990778009F1BE8 /* ShareSheet.swift in Sources */,
4EC4008F2AE8019700DF72C7 /* ExchangeRowView.swift in Sources */,
@@ -1179,6 +1189,7 @@
4EB095682989CBFE0043A8A1 /* MainView.swift in Sources */,
4EB0956A2989CBFE0043A8A1 /* Buttons.swift in Sources */,
4EBA82AB2A3EB2CA00E5F39A /* TransactionButton.swift in Sources */,
+ 4EEC118E2B83DE4800146CFF /* AmountInputV.swift in Sources */,
4EB095602989CBFE0043A8A1 /* BalancesSectionView.swift in Sources */,
4EEC157329F8242800D46A03 /* QRGeneratorView.swift in Sources */,
4E5A88F72A3B9E5B00072618 /* WithdrawAcceptDone.swift in Sources */,
@@ -1201,11 +1212,13 @@
4E605DB02AADDD13002FB9A7 /* UIScreen+screenSize.swift in Sources */,
4E40E0BE29F25ABB00B85369 /* SendAmount.swift in Sources */,
4E8E25332A1CD39700A27BFA /* EqualIconWidthDomain.swift in Sources */,
+ 4EEC11942B83FB7A00146CFF /* SubjectInputV.swift in Sources */,
4EBA563F2A7FD9390084948B /* SuperScriptDigits.swift in Sources */,
4E578E942A4822D500F21F1C /* P2pPayURIView.swift in Sources */,
4EB095542989CBFE0043A8A1 /* Model+Payment.swift in Sources */,
4EB0954F2989CBFE0043A8A1 /* SettingsView.swift in Sources */,
4EB095552989CBFE0043A8A1 /* PaymentView.swift in Sources */,
+ 4EEC11972B840F1100146CFF /* PayTemplateV.swift in Sources */,
4EB095612989CBFE0043A8A1 /* WithdrawURIView.swift in Sources */,
4EF840A72A0B85F400EE0D47 /* CopyShare.swift in Sources */,
4EB094ED298979620043A8A1 /* TalerWallet1App.swift in Sources */,
@@ -1222,7 +1235,6 @@
4EB0956B2989CBFE0043A8A1 /* TextFieldAlert.swift in Sources */,
4EBA82AD2A3F580500E5F39A /* QuiteSomeCoins.swift in Sources */,
4E2D8DD62B45822A00234039 /* AmountV.swift in Sources */,
- 4EBA56412A7FF5200084948B /* PayTemplateView.swift in Sources */,
4EB431672A1E55C700C5690E /* ManualWithdrawDone.swift in Sources */,
4E753A082A0B6A5F002D9328 /* ShareSheet.swift in Sources */,
4EC400902AE8019700DF72C7 /* ExchangeRowView.swift in Sources */,
diff --git a/TalerWallet1/Controllers/DebugViewC.swift b/TalerWallet1/Controllers/DebugViewC.swift
@@ -81,7 +81,9 @@ public let SHEET_WITHDRAW_CONFIRM = SHEET_WITHDRAW_ACCEPT + 1 // 133 waiti
// openURL (Link, NFC or scan QR) ==> pays merchant
public let SHEET_PAYMENT = SHEET_WITHDRAWAL + 10 // 140 Pay Merchant
public let SHEET_PAY_TEMPLATE = SHEET_PAYMENT + 1 // 141 Pay Merchant Template
-public let SHEET_PAY_ACCEPT = SHEET_PAY_TEMPLATE + 1 // 142 Pay Accept
+public let SHEET_PAY_TEMPL_AMOUNT = SHEET_PAY_TEMPLATE + 1 // 142 Pay Template Amount
+public let SHEET_PAY_TEMPL_SUBJECT = SHEET_PAY_TEMPL_AMOUNT + 1 // 143 Pay Template Subject
+public let SHEET_PAY_ACCEPT = SHEET_PAY_TEMPL_SUBJECT + 1 // 144 Pay Accept
// MARK: P2P Pay Invoice
// p2p pull debit - openURL (Link or scan QR)
diff --git a/TalerWallet1/Views/HelperViews/AmountInputV.swift b/TalerWallet1/Views/HelperViews/AmountInputV.swift
@@ -0,0 +1,114 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct AmountInputV: View {
+ private let symLog = SymLogV(0)
+ let stack: CallStack
+ // the scanned URL
+ let url: URL?
+ let amountAvailable: Amount? // TODO: GetMaxPeerPushAmount
+ @Binding var amountToTransfer: Amount
+ let amountLabel: String
+ let wantsSummary: Bool // if true we call SubjectInputV next
+ @Binding var summary: String
+ @Binding var insufficient: Bool
+ @Binding var feeAmount: Amount?
+ let shortcutAction: ((_ amount: Amount) -> Void)?
+ let buttonAction: () -> Void
+
+ @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 feeStr: String = EMPTYSTRING
+
+ var feeLabel: String { feeStr.count > 0 ? String(localized: "+ \(feeStr) fee") : EMPTYSTRING }
+
+ func preparePayForTemplate() async {
+ if let url {
+ do {
+ let ppCheck = try await model.preparePayForTemplateM(url.absoluteString, amount: amountToTransfer, summary: summary)
+ let amount = ppCheck.amountRaw
+ let currency = amount.currencyStr
+ let currencyInfo = controller.info(for: currency, controller.currencyTicker)
+ insufficient = ppCheck.status == .insufficientBalance
+ feeAmount = templateFee(ppCheck: ppCheck)
+ if let feeAmount {
+ feeStr = feeAmount.string(currencyInfo)
+ } else { feeStr = EMPTYSTRING }
+ let amountVoiceOver = amount.string(currencyInfo)
+ announce(this: "\(amountVoiceOver), \(feeLabel)")
+ preparePayResult = ppCheck
+ } catch { // TODO: error
+ symLog.log(error.localizedDescription)
+ }
+ }
+ }
+
+ var body: some View {
+ let currency = amountToTransfer.currencyStr
+ let currencyInfo = controller.info(for: currency, controller.currencyTicker)
+ let insufficientLabel = String(localized: "You don't have enough \(currency).")
+ let available = amountAvailable?.string(currencyInfo) ?? nil
+ let disabled = insufficient || amountToTransfer.isZero
+ VStack(alignment: .trailing) {
+ if let available {
+ Text("Available:\t\(available)")
+ .talerFont(.title3)
+ .padding(.bottom, 2)
+ // .accessibility(sortPriority: 3)
+ }
+ CurrencyInputView(amount: $amountToTransfer,
+ available: amountAvailable,
+ title: amountLabel,
+ shortcutAction: shortcutAction)
+// .accessibility(sortPriority: 2)
+ Text(insufficient ? insufficientLabel
+ : feeLabel)
+ .talerFont(.body)
+ .foregroundColor(insufficient ? .red
+ : (feeAmount?.isZero ?? true) ? WalletColors().secondary(colorScheme, colorSchemeContrast)
+ : .red)
+// .accessibility(sortPriority: 1)
+ .padding(4)
+ Group {
+ if let url {
+ let destination = LazyView {
+ PaymentView(stack: stack.push(),
+ url: url,
+ template: true,
+ amountToTransfer: $amountToTransfer,
+ summary: $summary)
+ }
+ NavigationLink(destination: destination) {
+ Text("Next")
+ }
+ .buttonStyle(TalerButtonStyle(type: .prominent))
+ .disabled(disabled)
+ } else {
+ Button("Next") {
+ buttonAction()
+ }
+ .buttonStyle(TalerButtonStyle(type: .prominent))
+ .disabled(disabled)
+ }
+ }
+// .accessibility(sortPriority: 0)
+ .task(id: amountToTransfer.value) {
+ symLog.log(".task")
+ await preparePayForTemplate()
+ }
+ }.padding(.horizontal)
+ .onAppear() {
+ // symLog.log("onAppear")
+ DebugViewC.shared.setSheetID(SHEET_PAY_TEMPL_AMOUNT)
+ }
+ }
+}
diff --git a/TalerWallet1/Views/HelperViews/SubjectInputV.swift b/TalerWallet1/Views/HelperViews/SubjectInputV.swift
@@ -0,0 +1,122 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct SubjectInputV<TargetView: View>: View {
+ private let symLog = SymLogV(0)
+ let stack: CallStack
+ // the scanned URL
+ let url: URL?
+ let amountAvailable: Amount? // TODO: GetMaxPeerPushAmount
+ @Binding var amountToTransfer: Amount
+ let amountLabel: String
+ @Binding var summary: String
+ @Binding var insufficient: Bool
+ @Binding var feeAmount: Amount?
+ let feeIsNotZero: Bool? // nil = no fees at all, false = no fee for this tx
+ let currencyInfo: CurrencyInfo
+
+ var targetView: TargetView
+
+// let destination: Destination
+
+ @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 private var preparePayResult: PreparePayResult? = nil
+ @State private var feeStr: String = EMPTYSTRING
+ @FocusState private var isFocused: Bool
+
+ var feeLabel: String { feeStr.count > 0 ? String(localized: "+ \(feeStr) fee") : EMPTYSTRING }
+
+ func preparePayForTemplate() async {
+ if let url {
+ do {
+ let ppCheck = try await model.preparePayForTemplateM(url.absoluteString, amount: amountToTransfer, summary: summary)
+ let amount = ppCheck.amountRaw
+ let currency = amount.currencyStr
+ let currencyInfo = controller.info(for: currency, controller.currencyTicker)
+ insufficient = ppCheck.status == .insufficientBalance
+ feeAmount = templateFee(ppCheck: ppCheck)
+ if let feeAmount {
+ feeStr = feeAmount.string(currencyInfo)
+ } else { feeStr = EMPTYSTRING }
+ let amountVoiceOver = amount.string(currencyInfo)
+ announce(this: "\(amountVoiceOver), \(feeLabel)")
+ preparePayResult = ppCheck
+ } catch { // TODO: error
+ symLog.log(error.localizedDescription)
+ }
+ }
+ }
+
+ var body: some View {
+ let currency = amountToTransfer.currencyStr
+ let currencyInfo = controller.info(for: currency, controller.currencyTicker)
+ let insufficientLabel = String(localized: "You don't have enough \(currency).")
+ let available = amountAvailable?.string(currencyInfo) ?? nil
+ let disabled = insufficient || summary.count == 0
+ VStack(alignment: .leading) {
+ if let available {
+ Text("Available:\t\(available)")
+ .talerFont(.title3)
+ .padding(.bottom, 2)
+// .accessibility(sortPriority: 3)
+ }
+
+ if !minimalistic {
+ Text("Enter subject:") // Purpose
+ .talerFont(.title3)
+ .accessibilityAddTraits(.isHeader)
+ .accessibilityRemoveTraits(.isStaticText)
+ .padding(.top)
+ }
+ Group { if #available(iOS 16.0, *) {
+ TextField(minimalistic ? "Subject" : EMPTYSTRING, text: $summary, axis: .vertical)
+ .focused($isFocused)
+ .lineLimit(2...)
+ } else {
+ TextField("Subject", text: $summary)
+ .focused($isFocused)
+ // .lineLimit(2...5) // lineLimit' is only available in iOS 16.0 or newer
+ } } // Group for iOS16+ & iOS15
+ .talerFont(.title2)
+ .foregroundColor(WalletColors().fieldForeground) // text color
+ .background(WalletColors().fieldBackground)
+ .textFieldStyle(.roundedBorder)
+ .onAppear {
+ symLog.log("dispatching kbd...")
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
+ isFocused = true // make first responder - raise keybord
+ symLog.log("...kbd isFocused")
+ }
+ }
+
+ let subLabel = insufficient ? insufficientLabel
+ : feeLabel
+ Text(subLabel)
+ .talerFont(.body)
+ .foregroundColor(insufficient ? .red
+ : (feeAmount?.isZero ?? true) ? WalletColors().secondary(colorScheme, colorSchemeContrast)
+ : .red)
+ // .accessibility(sortPriority: 1)
+ .padding(4)
+
+ NavigationLink("Next", destination: targetView)
+ .buttonStyle(TalerButtonStyle(type: .prominent))
+ .disabled(disabled)
+// .accessibility(sortPriority: 0)
+ }.padding(.horizontal)
+ .onAppear() {
+// symLog.log("onAppear")
+ DebugViewC.shared.setSheetID(SHEET_PAY_TEMPL_SUBJECT)
+ }
+ }
+}
diff --git a/TalerWallet1/Views/Sheets/Payment/PayTemplateV.swift b/TalerWallet1/Views/Sheets/Payment/PayTemplateV.swift
@@ -0,0 +1,217 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+func templateFee(ppCheck: PreparePayResult?) -> Amount? {
+ do {
+ if let ppCheck {
+ // Outgoing: fee = effective - raw
+ if let effective = ppCheck.amountEffective {
+ let fee = try effective - ppCheck.amountRaw
+ return fee
+ }
+ }
+ } catch {}
+ return nil
+}
+
+// Will be called either by the user scanning a QR code or tapping the provided link,
+// both from the shop's website. We show the payment details
+struct PayTemplateV: View {
+ private let symLog = SymLogV(0)
+ let stack: CallStack
+
+ // the scanned URL
+ let url: URL
+
+ @EnvironmentObject private var controller: Controller
+ @EnvironmentObject private var model: WalletModel
+ @AppStorage("minimalistic") var minimalistic: Bool = false
+ @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+
+ let navTitle = String(localized: "Customize Order", comment:"pay merchant")
+
+ @State private var insufficient = false
+ @State private var preparePayResult: PreparePayResult? = nil
+ @State private var currencyName: String? = nil
+ @State private var amountToTransfer = Amount.zero(currency: EMPTYSTRING) // Update currency when used
+ @State private var amountShortcut = Amount.zero(currency: EMPTYSTRING) // Update currency when used
+ @State private var shortcutSelected = false
+ @State private var buttonSelected1 = false
+ @State private var buttonSelected2 = false
+ @State private var summary: String = EMPTYSTRING // templateParam
+ @State private var wantsSummary: Bool = false
+ @State private var feeAmount: Amount? = nil
+ @State private var feeStr: String = EMPTYSTRING
+
+ var feeLabel: String { feeStr.count > 0 ? String(localized: "+ \(feeStr) fee") : EMPTYSTRING }
+
+ private func shortcutAction(_ shortcut: Amount) {
+ amountShortcut = shortcut
+ shortcutSelected = true
+ }
+ private func buttonAction1() {
+ buttonSelected1 = true
+ }
+ private func buttonAction2() {
+ buttonSelected2 = true
+ }
+ func acceptAction(preparePayResult: PreparePayResult) {
+ Task { // runs on MainActor
+ do {
+ let confirmPayResult = try await model.confirmPayM(preparePayResult.transactionId)
+// symLog.log(confirmPayResult as Any)
+ if confirmPayResult.type != "done" {
+ controller.playSound(0)
+ // TODO: show error
+ }
+ } catch {
+ controller.playSound(0)
+ // TODO: error
+ symLog.log(error.localizedDescription)
+ }
+ dismissTop()
+ }
+ }
+
+ func queryURL() -> Bool {
+ if let queryParameters = url.queryParameters {
+ if let amountStr = queryParameters["amount"] {
+ currencyName = amountStr
+ amountToTransfer = Amount.zero(currency: amountStr)
+ }
+ if let summaryStr = queryParameters["summary"] {
+ summary = summaryStr
+ wantsSummary = true
+ }
+ return true
+ }
+ return false
+ }
+
+ func preparePayForTemplate() async {
+ do {
+ let ppCheck = try await model.preparePayForTemplateM(url.absoluteString, amount: amountToTransfer, summary: summary)
+ let amount = ppCheck.amountRaw
+ let currency = amount.currencyStr
+ let currencyInfo = controller.info(for: currency, controller.currencyTicker)
+ insufficient = ppCheck.status == .insufficientBalance
+ feeAmount = templateFee(ppCheck: ppCheck)
+ if let feeAmount {
+ feeStr = feeAmount.string(currencyInfo)
+ } else { feeStr = EMPTYSTRING }
+ let amountVoiceOver = amount.string(currencyInfo)
+ announce(this: "\(amountVoiceOver), \(feeLabel)")
+ preparePayResult = ppCheck
+ } catch { // TODO: error
+ symLog.log(error.localizedDescription)
+ }
+ }
+
+ var body: some View {
+ if let preparePayResult {
+ let effective = preparePayResult.amountEffective
+ let baseURL = preparePayResult.contractTerms.exchanges.first?.url
+ let raw = preparePayResult.amountRaw
+ let currency = raw.currencyStr
+ let currencyInfo = controller.info(for: currency, controller.currencyTicker)
+ let amountLabel = minimalistic ? String(localized: "Amount:")
+ : String(localized: "Amount to pay:")
+ let finalDestinationI = LazyView {
+ PaymentView(stack: stack.push(),
+ url: url,
+ template: true,
+ amountToTransfer: $amountToTransfer,
+ summary: $summary)
+ }
+ let inputDestination = LazyView {
+ SubjectInputV(stack: stack.push(), url: url,
+ amountAvailable: nil,
+ amountToTransfer: $amountToTransfer,
+ amountLabel: amountLabel,
+ summary: $summary,
+ insufficient: $insufficient,
+ feeAmount: $feeAmount,
+ feeIsNotZero: true, // feeIsNotZero(),
+ currencyInfo: currencyInfo,
+ targetView: finalDestinationI)
+ }
+ let finalDestinationS = LazyView {
+ PaymentView(stack: stack.push(),
+ url: url,
+ template: true,
+ amountToTransfer: $amountShortcut,
+ summary: $summary)
+ }
+ let shortcutDestination = LazyView {
+ SubjectInputV(stack: stack.push(), url: url,
+ amountAvailable: nil,
+ amountToTransfer: $amountShortcut,
+ amountLabel: amountLabel,
+ summary: $summary,
+ insufficient: $insufficient,
+ feeAmount: $feeAmount,
+ feeIsNotZero: true, // feeIsNotZero(),
+ currencyInfo: currencyInfo,
+ targetView: finalDestinationS)
+ }
+ Group {
+ if let currencyName { // template included a currency name => let the user input an amount
+ let amountInput = AmountInputV(stack: stack.push(), url: url,
+ amountAvailable: nil,
+ amountToTransfer: $amountToTransfer,
+ amountLabel: amountLabel,
+ wantsSummary: wantsSummary,
+ summary: $summary,
+ insufficient: $insufficient,
+ feeAmount: $feeAmount,
+ shortcutAction: shortcutAction,
+ buttonAction: buttonAction1)
+ ScrollView {
+ if wantsSummary {
+ amountInput
+ .background(NavigationLink(destination: shortcutDestination, isActive: $shortcutSelected)
+ { EmptyView() }.frame(width: 0).opacity(0).hidden())
+ .background( NavigationLink(destination: inputDestination, isActive: $buttonSelected1)
+ { EmptyView() }.frame(width: 0).opacity(0).hidden())
+
+ } else {
+ amountInput
+ .background(NavigationLink(destination: finalDestinationS, isActive: $shortcutSelected)
+ { EmptyView() }.frame(width: 0).opacity(0).hidden())
+ .background(NavigationLink(destination: finalDestinationI, isActive: $buttonSelected1)
+ { EmptyView() }.frame(width: 0).opacity(0).hidden())
+ }
+ } // ScrollVStack
+ } else if wantsSummary { // check summary
+ ScrollView {
+ inputDestination
+ .background(NavigationLink(destination: finalDestinationI, isActive: $buttonSelected2)
+ { EmptyView() }.frame(width: 0).opacity(0).hidden()
+ )
+ }
+ } else {
+ PaymentView(stack: stack.push(),
+ url: url,
+ template: true,
+ amountToTransfer: $amountToTransfer,
+ summary: $summary)
+ }
+ }.onAppear() {
+ symLog.log("onAppear")
+ DebugViewC.shared.setSheetID(SHEET_PAY_TEMPLATE)
+ }.navigationTitle(navTitle)
+ } else {
+ LoadingView(url: url, message: nil)
+ .task {
+ symLog.log(".task")
+ let hasParams = queryURL()
+ await preparePayForTemplate()
+ }
+ }
+ }
+}
diff --git a/TalerWallet1/Views/Sheets/Payment/PayTemplateView.swift b/TalerWallet1/Views/Sheets/Payment/PayTemplateView.swift
@@ -1,140 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
- * See LICENSE.md
- */
-import SwiftUI
-import taler_swift
-import SymLog
-
-// Will be called either by the user scanning a QR code or tapping the provided link,
-// both from the shop's website. We show the payment details
-struct PayTemplateView: View {
- private let symLog = SymLogV(0)
- let stack: CallStack
- let navTitle = String(localized: "Confirm Payment", comment:"pay merchant")
-
- // the scanned URL
- let url: URL
-
- @EnvironmentObject private var controller: Controller
- @EnvironmentObject private var model: WalletModel
- @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
-
- @State private var preparePayResult: PreparePayResult? = nil
- @State private var amount: Amount? = nil // templateParam
- @State private var summary: String? = nil // templateParam
-
- func acceptAction(preparePayResult: PreparePayResult) {
- Task { // runs on MainActor
- do {
- let confirmPayResult = try await model.confirmPayM(preparePayResult.transactionId)
-// symLog.log(confirmPayResult as Any)
- if confirmPayResult.type != "done" {
- controller.playSound(0)
- // TODO: show error
- }
- } catch {
- controller.playSound(0)
- // TODO: error
- symLog.log(error.localizedDescription)
- }
- dismissTop()
- }
- }
-
- func queryURL() -> Bool {
- if let queryParameters = url.queryParameters {
- if let amountStr = queryParameters["amount"] {
- amount = Amount.zero(currency: amountStr)
- }
- if let summaryStr = queryParameters["summary"] {
- summary = summaryStr
- }
- return true
- }
- return false
- }
-
- func preparePayForTemplate() async {
- do {
- let result = try await model.preparePayForTemplateM(url.absoluteString, amount: amount, summary: summary)
- preparePayResult = result
- } catch { // TODO: error
- symLog.log(error.localizedDescription)
- }
- }
-
- var body: some View {
- Group {
- if let preparePayResult {
- let effective = preparePayResult.amountEffective
- List {
- let baseURL = preparePayResult.contractTerms.exchanges.first?.url
- let raw = preparePayResult.amountRaw
- let currency = raw.currencyStr
- let topTitle = String(localized: "Amount to pay:")
- let topAbbrev = String(localized: "Pay:", comment: "mini")
- if let effective {
- // TODO: already paid
- let fee = try! Amount.diff(raw, effective) // TODO: different currencies
- ThreeAmountsV(stack: stack.push(),
- topTitle: topTitle,
- topAbbrev: topAbbrev,
- topAmount: raw, fee: fee,
- bottomTitle: String(localized: "Amount to spend:"),
- bottomAbbrev: String(localized: "Effective:", comment: "mini"),
- bottomAmount: effective,
- large: false, pending: false, incoming: false,
- baseURL: baseURL,
- status: nil,
- summary: nil,
- merchant: nil)
- // TODO: payment: popup with all possible exchanges, check fees
- } else if let balanceDetails = preparePayResult.balanceDetails { // Insufficient
- Text("You don't have enough \(currency)")
- .talerFont(.body)
- ThreeAmountsV(stack: stack.push(),
- topTitle: topTitle,
- topAbbrev: topAbbrev,
- topAmount: raw, fee: nil,
- bottomTitle: String(localized: "Amount available:"),
- bottomAbbrev: String(localized: "Available:", comment: "mini"),
- bottomAmount: balanceDetails.balanceAvailable,
- large: false, pending: false, incoming: false,
- baseURL: baseURL,
- status: nil,
- summary: nil,
- merchant: nil)
- } else {
- // TODO: Error - neither effective nor balanceDetails
- Text("Error")
- .talerFont(.body)
- }
- }
- .listStyle(myListStyle.style).anyView
- .safeAreaInset(edge: .bottom) {
- if let effective {
- Button(navTitle, action: { acceptAction(preparePayResult: preparePayResult) })
- .buttonStyle(TalerButtonStyle(type: .prominent))
- .padding(.horizontal)
- } else {
- Button("Cancel", action: { dismissTop() })
- .buttonStyle(TalerButtonStyle(type: .bordered))
- .padding(.horizontal)
- }
- }
- .navigationTitle(navTitle)
- } else {
- LoadingView(url: url, message: nil)
- .task {
- symLog.log(".task")
- let hasParams = queryURL()
- await preparePayForTemplate()
- }
- }
- }.onAppear() {
- symLog.log("onAppear")
- DebugViewC.shared.setSheetID(SHEET_PAY_TEMPLATE)
- }
- }
-}
diff --git a/TalerWallet1/Views/Sheets/Payment/PaymentView.swift b/TalerWallet1/Views/Sheets/Payment/PaymentView.swift
@@ -15,6 +15,9 @@ struct PaymentView: View {
// the scanned URL
let url: URL
+ let template: Bool
+ @Binding var amountToTransfer: Amount
+ @Binding var summary: String
@EnvironmentObject private var model: WalletModel
@AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
@@ -93,8 +96,15 @@ struct PaymentView: View {
.task { // this runs only once
do { // TODO: cancelled
symLog.log(".task")
- let result = try await model.preparePayForUriM(url.absoluteString)
- preparePayResult = result
+ if template {
+ let result = try await model.preparePayForTemplateM(url.absoluteString,
+ amount: amountToTransfer,
+ summary: summary)
+ preparePayResult = result
+ } else {
+ let result = try await model.preparePayForUriM(url.absoluteString)
+ preparePayResult = result
+ }
} catch { // TODO: error
symLog.log(error.localizedDescription)
}
@@ -107,6 +117,7 @@ struct PaymentView: View {
}
}
// MARK: -
+#if false
struct PaymentURIView_Previews: PreviewProvider {
static var previews: some View {
let merchant = Merchant(name: "Merchant")
@@ -153,6 +164,12 @@ struct PaymentURIView_Previews: PreviewProvider {
)
let url = URL(string: "taler://pay/some_amount")!
- PaymentView(stack: CallStack("Preview"), url: url, preparePayResult: details)
+// @State private var amount: Amount? = nil // templateParam
+// @State private var summary: String? = nil // templateParam
+
+ PaymentView(stack: CallStack("Preview"),
+ url: url, template: false, amountToTransfer: nil, summary: nil,
+ preparePayResult: details)
}
}
+#endif
diff --git a/TalerWallet1/Views/Sheets/URLSheet.swift b/TalerWallet1/Views/Sheets/URLSheet.swift
@@ -13,8 +13,8 @@ struct URLSheet: View {
var urlToOpen: URL
@EnvironmentObject private var controller: Controller
- @State private var amountToTransfer = Amount.zero(currency: "")
-
+ @State private var amountToTransfer = Amount.zero(currency: EMPTYSTRING)
+ @State private var summary = EMPTYSTRING
var body: some View {
#if PRINT_CHANGES
let _ = Self._printChanges()
@@ -29,13 +29,14 @@ struct URLSheet: View {
case .withdrawExchange:
WithdrawExchangeV(stack: stack.push(), url: urlToOpen)
case .pay:
- PaymentView(stack: stack.push(), url: urlToOpen)
+ PaymentView(stack: stack.push(), url: urlToOpen,
+ template: false, amountToTransfer: $amountToTransfer, summary: $summary)
case .payPull:
P2pPayURIView(stack: stack.push(), url: urlToOpen)
case .payPush:
P2pReceiveURIView(stack: stack.push(), url: urlToOpen)
case .payTemplate:
- PayTemplateView(stack: stack.push(), url: urlToOpen)
+ PayTemplateV(stack: stack.push(), url: urlToOpen)
case .refund:
RefundURIView(stack: stack.push(), url: urlToOpen)
default: // Error view