commit 82b542709c6848b2fd19f7eda5ec88ed3fb72c38
parent 0b63d481541d5c0d8f7b9572bb3671ca06502b75
Author: Marc Stibane <marc@taler.net>
Date: Sun, 5 Nov 2023 11:58:07 +0100
SendDone: Prevent double execution
Diffstat:
5 files changed, 127 insertions(+), 115 deletions(-)
diff --git a/TalerWallet.xcodeproj/project.pbxproj b/TalerWallet.xcodeproj/project.pbxproj
@@ -62,7 +62,7 @@
4E3EAE422A990778009F1BE8 /* KeyboardResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAD117529F672FA008EDD0B /* KeyboardResponder.swift */; };
4E3EAE432A990778009F1BE8 /* TransactionRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095302989CBFE0043A8A1 /* TransactionRowView.swift */; };
4E3EAE442A990778009F1BE8 /* PublicConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EA1ABBD29A3833A008821EA /* PublicConstants.swift */; };
- 4E3EAE452A990778009F1BE8 /* SendDone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB3136029FEE79B007D68BC /* SendDone.swift */; };
+ 4E3EAE452A990778009F1BE8 /* SendDoneV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB3136029FEE79B007D68BC /* SendDoneV.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 */; };
@@ -227,7 +227,7 @@
4EB0956E2989CBFE0043A8A1 /* Model+Pending.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0954C2989CBFE0043A8A1 /* Model+Pending.swift */; };
4EB0956F2989CBFE0043A8A1 /* PendingOpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0954D2989CBFE0043A8A1 /* PendingOpView.swift */; };
4EB095702989CBFE0043A8A1 /* PendingOpsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0954E2989CBFE0043A8A1 /* PendingOpsListView.swift */; };
- 4EB3136129FEE79B007D68BC /* SendDone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB3136029FEE79B007D68BC /* SendDone.swift */; };
+ 4EB3136129FEE79B007D68BC /* SendDoneV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB3136029FEE79B007D68BC /* SendDoneV.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 */; };
@@ -402,7 +402,7 @@
4EB0954C2989CBFE0043A8A1 /* Model+Pending.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Model+Pending.swift"; sourceTree = "<group>"; };
4EB0954D2989CBFE0043A8A1 /* PendingOpView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PendingOpView.swift; sourceTree = "<group>"; };
4EB0954E2989CBFE0043A8A1 /* PendingOpsListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PendingOpsListView.swift; sourceTree = "<group>"; };
- 4EB3136029FEE79B007D68BC /* SendDone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendDone.swift; sourceTree = "<group>"; };
+ 4EB3136029FEE79B007D68BC /* SendDoneV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendDoneV.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>"; };
@@ -773,7 +773,7 @@
children = (
4E40E0BD29F25ABB00B85369 /* SendAmount.swift */,
4E7940DD29FC307C00A9AEA1 /* SendPurpose.swift */,
- 4EB3136029FEE79B007D68BC /* SendDone.swift */,
+ 4EB3136029FEE79B007D68BC /* SendDoneV.swift */,
4E9320442A1645B600A87B0E /* RequestPayment.swift */,
4E9320462A164BC700A87B0E /* PaymentPurpose.swift */,
);
@@ -1085,7 +1085,7 @@
4E3EAE422A990778009F1BE8 /* KeyboardResponder.swift in Sources */,
4E3EAE432A990778009F1BE8 /* TransactionRowView.swift in Sources */,
4E3EAE442A990778009F1BE8 /* PublicConstants.swift in Sources */,
- 4E3EAE452A990778009F1BE8 /* SendDone.swift in Sources */,
+ 4E3EAE452A990778009F1BE8 /* SendDoneV.swift in Sources */,
4E3EAE462A990778009F1BE8 /* TextFieldAlert.swift in Sources */,
4E3EAE472A990778009F1BE8 /* QuiteSomeCoins.swift in Sources */,
4E3EAE482A990778009F1BE8 /* PayTemplateView.swift in Sources */,
@@ -1194,7 +1194,7 @@
4EAD117629F672FA008EDD0B /* KeyboardResponder.swift in Sources */,
4EB095572989CBFE0043A8A1 /* TransactionRowView.swift in Sources */,
4EA1ABBE29A3833A008821EA /* PublicConstants.swift in Sources */,
- 4EB3136129FEE79B007D68BC /* SendDone.swift in Sources */,
+ 4EB3136129FEE79B007D68BC /* SendDoneV.swift in Sources */,
4EB0956B2989CBFE0043A8A1 /* TextFieldAlert.swift in Sources */,
4EBA82AD2A3F580500E5F39A /* QuiteSomeCoins.swift in Sources */,
4EBA56412A7FF5200084948B /* PayTemplateView.swift in Sources */,
diff --git a/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift b/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
@@ -18,6 +18,7 @@ struct PaymentPurpose: View {
@AppStorage("iconOnly") var iconOnly: Bool = false
let navTitle = String(localized: "NavTitle_Request_Subject", defaultValue: "Request", comment: "NavTitle for entering the subject for Request-Payment")
+ @State private var transactionStarted: Bool = false
@FocusState private var isFocused: Bool
private var label: String {
@@ -63,11 +64,12 @@ struct PaymentPurpose: View {
let emptyStr = ""
let disabled = (expireDays == 0) || (summary.count < 1)
NavigationLink(destination: LazyView {
- SendDone(stack: stack.push(),
- amountToSend: nil,
- amountToReceive: amount,
- summary: summary,
- expireDays: expireDays)
+ SendDoneV(stack: stack.push(),
+ amountToSend: nil,
+ amountToReceive: amount,
+ summary: summary,
+ expireDays: expireDays,
+ transactionStarted: $transactionStarted)
}) {
Text("Request \(label) \(scopeInfo.currency)")
// .accessibilityFont(buttonFont)
diff --git a/TalerWallet1/Views/Peer2peer/SendDone.swift b/TalerWallet1/Views/Peer2peer/SendDone.swift
@@ -1,99 +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
-
-// Called when initiating a P2P transaction: Send coins or Send Request(Invoice)
-struct SendDone: View {
- private let symLog = SymLogV(0)
- let stack: CallStack
- let navTitle = String(localized: "P2P Ready")
-#if DEBUG
- @AppStorage("developerMode") var developerMode: Bool = true
-#else
- @AppStorage("developerMode") var developerMode: Bool = false
-#endif
- @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
-
- let amountToSend: Amount?
- let amountToReceive: Amount?
- let summary: String
- let expireDays: UInt
-
- @EnvironmentObject private var model: WalletModel
-
- @State private var transactionId: String?
-
- func reloadOneAction(_ transactionId: String) async throws -> Transaction {
- return try await model.getTransactionByIdT(transactionId)
- }
-
- var body: some View {
-#if DEBUG
- let _ = Self._printChanges()
- let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear
-#endif
- VStack {
- if let transactionId {
- TransactionDetailView(stack: stack.push(),
- transactionId: transactionId,
- reloadAction: reloadOneAction,
- navTitle: navTitle,
- doneAction: ViewState.shared.popToRootView,
- abortAction: nil,
- deleteAction: nil,
- failAction: nil,
- suspendAction: nil,
- resumeAction: nil)
- .navigationBarBackButtonHidden(true)
- .interactiveDismissDisabled() // can only use "Done" button to dismiss
- } else {
- WithdrawProgressView(message: "Loading...")
- }
- }
-// .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
- .navigationTitle(navTitle)
- .task {
- symLog.log(".task")
- do {
- let timestamp = developerMode ? Timestamp.inSomeMinutes(expireDays > 20 ? (24*60)
- : expireDays > 5 ? 60 : 3)
- : Timestamp.inSomeDays(expireDays)
- if let amountToSend {
- let terms = PeerContractTerms(amount: amountToSend,
- summary: summary,
- purse_expiration: timestamp)
- // TODO: user might choose baseURL
- let response = try await model.initiatePeerPushDebitM(nil, terms: terms)
- // will switch from WithdrawProgressView to TransactionDetailView
- transactionId = response.transactionId
- } else if let amountToReceive {
- let terms = PeerContractTerms(amount: amountToReceive,
- summary: summary,
- purse_expiration: timestamp)
- // TODO: user might choose baseURL
- let response = try await model.initiatePeerPullCreditM(nil, terms: terms)
- // will switch from WithdrawProgressView to TransactionDetailView
- transactionId = response.transactionId
- } else { fatalError() }
- } catch { // TODO: error
- symLog.log(error.localizedDescription)
- }
- } // task
- }
-}
-// MARK: -
-struct SendNow_Previews: PreviewProvider {
- static var previews: some View {
- Group {
- SendDone(stack: CallStack("Preview"),
- amountToSend: try! Amount(fromString: LONGCURRENCY + ":4.8"),
- amountToReceive: nil,
- summary: "some subject/purpose",
- expireDays: 0)
- }
- }
-}
diff --git a/TalerWallet1/Views/Peer2peer/SendDoneV.swift b/TalerWallet1/Views/Peer2peer/SendDoneV.swift
@@ -0,0 +1,106 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+// Called when initiating a P2P transaction: Send coins or Send Request(Invoice)
+struct SendDoneV: View {
+ private let symLog = SymLogV()
+ let stack: CallStack
+ let navTitle = String(localized: "P2P Ready")
+#if DEBUG
+ @AppStorage("developerMode") var developerMode: Bool = true
+#else
+ @AppStorage("developerMode") var developerMode: Bool = false
+#endif
+ @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+
+ let amountToSend: Amount?
+ let amountToReceive: Amount?
+ let summary: String
+ let expireDays: UInt
+ @Binding var transactionStarted: Bool
+
+ @EnvironmentObject private var model: WalletModel
+
+ @State private var transactionId: String? = nil
+
+ func reloadOneAction(_ transactionId: String) async throws -> Transaction {
+ return try await model.getTransactionByIdT(transactionId)
+ }
+
+ var body: some View {
+#if DEBUG
+ let _ = Self._printChanges()
+ let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear
+#endif
+ VStack {
+ if let transactionId {
+ TransactionDetailView(stack: stack.push(),
+ transactionId: transactionId,
+ reloadAction: reloadOneAction,
+ navTitle: navTitle,
+ doneAction: ViewState.shared.popToRootView,
+ abortAction: nil,
+ deleteAction: nil,
+ failAction: nil,
+ suspendAction: nil,
+ resumeAction: nil)
+ .navigationBarBackButtonHidden(true)
+ .interactiveDismissDisabled() // can only use "Done" button to dismiss
+ } else {
+ WithdrawProgressView(message: "Loading...")
+ }
+ }
+// .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+ .navigationTitle(navTitle)
+ .task {
+ symLog.log(".task")
+ do {
+ guard transactionStarted == false else {
+// TODO: logger.warning("Try to start P2P a second time")
+ symLog.log("Yikes❗️ Try to start P2P a second time")
+ return
+ }
+ transactionStarted = true
+ let timestamp = developerMode ? Timestamp.inSomeMinutes(expireDays > 20 ? (24*60)
+ : expireDays > 5 ? 60 : 3)
+ : Timestamp.inSomeDays(expireDays)
+ if let amountToSend {
+ let terms = PeerContractTerms(amount: amountToSend,
+ summary: summary,
+ purse_expiration: timestamp)
+ // TODO: user might choose baseURL
+ let response = try await model.initiatePeerPushDebitM(nil, terms: terms)
+ // will switch from WithdrawProgressView to TransactionDetailView
+ transactionId = response.transactionId
+ } else if let amountToReceive {
+ let terms = PeerContractTerms(amount: amountToReceive,
+ summary: summary,
+ purse_expiration: timestamp)
+ // TODO: user might choose baseURL
+ let response = try await model.initiatePeerPullCreditM(nil, terms: terms)
+ // will switch from WithdrawProgressView to TransactionDetailView
+ transactionId = response.transactionId
+ } else { fatalError() }
+ } catch { // TODO: error
+ symLog.log(error.localizedDescription)
+ }
+ } // task
+ }
+}
+// MARK: -
+//struct SendNow_Previews: PreviewProvider {
+// static var previews: some View {
+// Group {
+// SendDoneV(stack: CallStack("Preview"),
+// amountToSend: try! Amount(fromString: LONGCURRENCY + ":4.8"),
+// amountToReceive: nil,
+// summary: "some subject/purpose",
+// expireDays: 0)
+// }
+// }
+//}
diff --git a/TalerWallet1/Views/Peer2peer/SendPurpose.swift b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
@@ -19,6 +19,8 @@ struct SendPurpose: View {
@AppStorage("iconOnly") var iconOnly: Bool = false
let navTitle = String(localized: "NavTitle_Send_Subject", defaultValue: "Subject", comment: "NavTitle for entering the subject for Send-Money")
+ @State private var transactionStarted: Bool = false
+
private var value: String {
// let mag = pow(10, formatter.maximumFractionDigits)
// return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
@@ -81,11 +83,12 @@ struct SendPurpose: View {
let emptyStr = ""
let disabled = (expireDays == 0) || (summary.count < 1) // TODO: check amountAvailable
NavigationLink(destination: LazyView {
- SendDone(stack: stack.push(),
- amountToSend: amount,
- amountToReceive: nil,
- summary: summary,
- expireDays: expireDays)
+ SendDoneV(stack: stack.push(),
+ amountToSend: amount,
+ amountToReceive: nil,
+ summary: summary,
+ expireDays: expireDays,
+ transactionStarted: $transactionStarted)
}) {
Text("Send \(value) \(amountAvailable.currencyStr) now", comment: "first is value, second currencyString") // TODO: currency formatter
}