gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-taler-ios] 13/54: Overhaul withdraw + p2p


From: gnunet
Subject: [taler-taler-ios] 13/54: Overhaul withdraw + p2p
Date: Fri, 30 Jun 2023 22:33:45 +0200

This is an automated email from the git hooks/post-receive script.

marc-stibane pushed a commit to branch master
in repository taler-ios.

commit a41e3ea751bf572995a11e81e5aa7065fae2160b
Author: Marc Stibane <marc@taler.net>
AuthorDate: Tue Jun 20 09:23:54 2023 +0200

    Overhaul withdraw + p2p
---
 TalerWallet.xcodeproj/project.pbxproj              |  12 +-
 TalerWallet1/Controllers/PublicConstants.swift     |   3 +-
 TalerWallet1/Model/Peer2peerModel.swift            |  17 ++-
 TalerWallet1/Model/WithdrawModel.swift             |  18 +--
 TalerWallet1/Views/Balances/BalancesListView.swift |  10 +-
 .../Views/Balances/BalancesSectionView.swift       |  44 ++++--
 .../Views/Exchange/ExchangeSectionView.swift       |   9 +-
 TalerWallet1/Views/Exchange/ManualWithdraw.swift   | 107 ++++----------
 .../Views/Exchange/ManualWithdrawDone.swift        |  23 ++-
 TalerWallet1/Views/Exchange/QuiteSomeCoins.swift   | 102 +++++++++++++
 .../Views/HelperViews/TransactionButton.swift      |  88 +++++++++++
 TalerWallet1/Views/Peer2peer/ReceivePurpose.swift  |   2 +-
 TalerWallet1/Views/Peer2peer/RequestPayment.swift  |  25 +++-
 TalerWallet1/Views/Peer2peer/SendAmount.swift      |  57 +++----
 TalerWallet1/Views/Peer2peer/SendNow.swift         |   4 +-
 .../Views/Transactions/TransactionDetailView.swift | 164 ++++++++-------------
 .../Views/Transactions/TransactionsListView.swift  |  14 +-
 .../WithdrawAcceptDone.swift                       |  79 +++++-----
 .../WithdrawAcceptView.swift                       | 103 +++++++------
 .../WithdrawProgressView.swift                     |  10 +-
 .../WithdrawBankIntegrated/WithdrawURIView.swift   |  89 +++++++----
 21 files changed, 607 insertions(+), 373 deletions(-)

diff --git a/TalerWallet.xcodeproj/project.pbxproj 
b/TalerWallet.xcodeproj/project.pbxproj
index fbd3794..4d3cbfe 100644
--- a/TalerWallet.xcodeproj/project.pbxproj
+++ b/TalerWallet.xcodeproj/project.pbxproj
@@ -74,7 +74,6 @@
                4EB095602989CBFE0043A8A1 /* BalancesSectionView.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4EB0953A2989CBFE0043A8A1 /* 
BalancesSectionView.swift */; };
                4EB095612989CBFE0043A8A1 /* WithdrawURIView.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB0953C2989CBFE0043A8A1 /* 
WithdrawURIView.swift */; };
                4EB095622989CBFE0043A8A1 /* WithdrawModel.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB0953D2989CBFE0043A8A1 /* WithdrawModel.swift 
*/; };
-               4EB095632989CBFE0043A8A1 /* WithdrawAcceptView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB0953E2989CBFE0043A8A1 /* 
WithdrawAcceptView.swift */; };
                4EB095642989CBFE0043A8A1 /* WithdrawProgressView.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4EB0953F2989CBFE0043A8A1 /* 
WithdrawProgressView.swift */; };
                4EB095652989CBFE0043A8A1 /* WithdrawTOSView.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB095402989CBFE0043A8A1 /* 
WithdrawTOSView.swift */; };
                4EB095662989CBFE0043A8A1 /* SideBarView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095422989CBFE0043A8A1 /* SideBarView.swift 
*/; };
@@ -90,6 +89,8 @@
                4EB095702989CBFE0043A8A1 /* PendingOpsListView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB0954E2989CBFE0043A8A1 /* 
PendingOpsListView.swift */; };
                4EB3136129FEE79B007D68BC /* SendNow.swift in Sources */ = {isa 
= PBXBuildFile; fileRef = 4EB3136029FEE79B007D68BC /* SendNow.swift */; };
                4EB431672A1E55C700C5690E /* ManualWithdrawDone.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB431662A1E55C700C5690E /* 
ManualWithdrawDone.swift */; };
+               4EBA82AB2A3EB2CA00E5F39A /* TransactionButton.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EBA82AA2A3EB2CA00E5F39A /* 
TransactionButton.swift */; };
+               4EBA82AD2A3F580500E5F39A /* QuiteSomeCoins.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EBA82AC2A3F580500E5F39A /* 
QuiteSomeCoins.swift */; };
                4EC90C782A1B528B0071DC58 /* ExchangeSectionView.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4EC90C772A1B528B0071DC58 /* 
ExchangeSectionView.swift */; };
                4ECB62802A0BA6DF004ABBB7 /* Peer2peerModel.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4ECB627F2A0BA6DF004ABBB7 /* 
Peer2peerModel.swift */; };
                4ECB62822A0BB01D004ABBB7 /* SelectDays.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */; 
};
@@ -202,7 +203,6 @@
                4EB0953A2989CBFE0043A8A1 /* BalancesSectionView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = BalancesSectionView.swift; sourceTree = "<group>"; };
                4EB0953C2989CBFE0043A8A1 /* WithdrawURIView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= WithdrawURIView.swift; sourceTree = "<group>"; };
                4EB0953D2989CBFE0043A8A1 /* WithdrawModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= WithdrawModel.swift; sourceTree = "<group>"; };
-               4EB0953E2989CBFE0043A8A1 /* WithdrawAcceptView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = WithdrawAcceptView.swift; sourceTree = "<group>"; };
                4EB0953F2989CBFE0043A8A1 /* WithdrawProgressView.swift */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.swift; path = WithdrawProgressView.swift; sourceTree = "<group>"; };
                4EB095402989CBFE0043A8A1 /* WithdrawTOSView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= WithdrawTOSView.swift; sourceTree = "<group>"; };
                4EB095422989CBFE0043A8A1 /* SideBarView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SideBarView.swift; sourceTree = "<group>"; };
@@ -218,6 +218,8 @@
                4EB0954E2989CBFE0043A8A1 /* PendingOpsListView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = PendingOpsListView.swift; sourceTree = "<group>"; };
                4EB3136029FEE79B007D68BC /* SendNow.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SendNow.swift; sourceTree = "<group>"; };
                4EB431662A1E55C700C5690E /* ManualWithdrawDone.swift */ = {isa 
= PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
ManualWithdrawDone.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>"; };
                4EC90C772A1B528B0071DC58 /* ExchangeSectionView.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = ExchangeSectionView.swift; sourceTree = "<group>"; };
                4ECB627F2A0BA6DF004ABBB7 /* Peer2peerModel.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= Peer2peerModel.swift; sourceTree = "<group>"; };
                4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SelectDays.swift; sourceTree = "<group>"; };
@@ -393,6 +395,7 @@
                                4EB095292989CBFE0043A8A1 /* 
ExchangeListView.swift */,
                                4EC90C772A1B528B0071DC58 /* 
ExchangeSectionView.swift */,
                                4E50B34F2A1BEE8000F9F01C /* 
ManualWithdraw.swift */,
+                               4EBA82AC2A3F580500E5F39A /* 
QuiteSomeCoins.swift */,
                                4EB431662A1E55C700C5690E /* 
ManualWithdrawDone.swift */,
                        );
                        path = Exchange;
@@ -436,7 +439,6 @@
                        isa = PBXGroup;
                        children = (
                                4EB0953C2989CBFE0043A8A1 /* 
WithdrawURIView.swift */,
-                               4EB0953E2989CBFE0043A8A1 /* 
WithdrawAcceptView.swift */,
                                4E5A88F62A3B9E5B00072618 /* 
WithdrawAcceptDone.swift */,
                                4EB0953F2989CBFE0043A8A1 /* 
WithdrawProgressView.swift */,
                                4EB095402989CBFE0043A8A1 /* 
WithdrawTOSView.swift */,
@@ -467,6 +469,7 @@
                                4E5A88F42A38A4FD00072618 /* 
QRCodeDetailView.swift */,
                                4E6EDD862A363D8D0031D520 /* ListStyle.swift */,
                                4EB095482989CBFE0043A8A1 /* 
TextFieldAlert.swift */,
+                               4EBA82AA2A3EB2CA00E5F39A /* 
TransactionButton.swift */,
                                4EB095492989CBFE0043A8A1 /* AmountView.swift */,
                                4EB0954A2989CBFE0043A8A1 /* LoadingView.swift 
*/,
                                4EB095432989CBFE0043A8A1 /* 
LaunchAnimationView.swift */,
@@ -701,6 +704,7 @@
                                4EB095032989C9BC0043A8A1 /* Controller.swift in 
Sources */,
                                4EB095682989CBFE0043A8A1 /* MainView.swift in 
Sources */,
                                4EB0956A2989CBFE0043A8A1 /* Buttons.swift in 
Sources */,
+                               4EBA82AB2A3EB2CA00E5F39A /* 
TransactionButton.swift in Sources */,
                                4EB095602989CBFE0043A8A1 /* 
BalancesSectionView.swift in Sources */,
                                4EEC157329F8242800D46A03 /* 
QRGeneratorView.swift in Sources */,
                                4E5A88F72A3B9E5B00072618 /* 
WithdrawAcceptDone.swift in Sources */,
@@ -711,7 +715,6 @@
                                4EB095212989CBCB0043A8A1 /* 
WalletBackendError.swift in Sources */,
                                4EB0955E2989CBFE0043A8A1 /* 
PendingRowView.swift in Sources */,
                                4EB0955B2989CBFE0043A8A1 /* BalancesModel.swift 
in Sources */,
-                               4EB095632989CBFE0043A8A1 /* 
WithdrawAcceptView.swift in Sources */,
                                4EB0956D2989CBFE0043A8A1 /* LoadingView.swift 
in Sources */,
                                4E50B3502A1BEE8000F9F01C /* 
ManualWithdraw.swift in Sources */,
                                4E5A88F52A38A4FD00072618 /* 
QRCodeDetailView.swift in Sources */,
@@ -736,6 +739,7 @@
                                4EA1ABBE29A3833A008821EA /* 
PublicConstants.swift in Sources */,
                                4EB3136129FEE79B007D68BC /* SendNow.swift in 
Sources */,
                                4EB0956B2989CBFE0043A8A1 /* 
TextFieldAlert.swift in Sources */,
+                               4EBA82AD2A3F580500E5F39A /* 
QuiteSomeCoins.swift in Sources */,
                                4EB431672A1E55C700C5690E /* 
ManualWithdrawDone.swift in Sources */,
                                4E9320472A164BC700A87B0E /* 
ReceivePurpose.swift in Sources */,
                                4E753A082A0B6A5F002D9328 /* ShareSheet.swift in 
Sources */,
diff --git a/TalerWallet1/Controllers/PublicConstants.swift 
b/TalerWallet1/Controllers/PublicConstants.swift
index 94103a0..3a117ff 100644
--- a/TalerWallet1/Controllers/PublicConstants.swift
+++ b/TalerWallet1/Controllers/PublicConstants.swift
@@ -8,7 +8,8 @@ public let LAUNCHDURATION: Double = 1.60
 public let SLIDEDURATION: Double = 0.45
 
 public let HTTPS = "https://";
-public let DEMOBANK = HTTPS + "bAnK.dEmO.tAlEr.nEt"             // should be 
weird to read, but still work
+//public let DEMOBANK = HTTPS + "bAnK.dEmO.tAlEr.nEt"             // should be 
weird to read, but still work
+public let DEMOBANK = HTTPS + "bank.demo.taler.net"
 public let DEMOSHOP = HTTPS + "shop.demo.taler.net"
 public let DEMOBACKEND = HTTPS + "backend.demo.taler.net"
 //public let DEMOEXCHANGE = HTTPS + "eXcHaNgE.dEmO.tAlEr.nEt"
diff --git a/TalerWallet1/Model/Peer2peerModel.swift 
b/TalerWallet1/Model/Peer2peerModel.swift
index ded3d10..9074ec1 100644
--- a/TalerWallet1/Model/Peer2peerModel.swift
+++ b/TalerWallet1/Model/Peer2peerModel.swift
@@ -22,7 +22,7 @@ struct PeerContractTerms: Codable {
 // MARK: -
 /// The result from CheckPeerPushDebit
 struct CheckPeerPushDebitResponse: Codable {
-    let exchangeBaseUrl: String
+    let exchangeBaseUrl: String?
     let amountRaw: Amount
     let amountEffective: Amount
     let maxExpirationDate: Timestamp?          // TODO: limit expiration (30 
days or 7 days)
@@ -43,8 +43,9 @@ fileprivate struct CheckPeerPushDebit: 
WalletBackendFormattedRequest {
 struct CheckPeerPullCreditResponse: Codable {
     let scopeInfo: ScopeInfo?
     let exchangeBaseUrl: String?
-    let amountEffective: Amount
     let amountRaw: Amount
+    let amountEffective: Amount
+    var numCoins: Int?                  // Number of coins this 
amountEffective will create
 }
 /// A request to check fees before invoicing another wallet.
 fileprivate struct CheckPeerPullCredit: WalletBackendFormattedRequest {
@@ -105,7 +106,7 @@ extension Peer2peerModel {
     }
     /// generate peer-push. Networking involved
     @MainActor
-    func initiatePeerPushDebitM(_ baseURL: String, terms: PeerContractTerms)   
    // M for MainActor
+    func initiatePeerPushDebitM(_ baseURL: String?, terms: PeerContractTerms)  
     // M for MainActor
       async throws -> PeerPushResponse {
         let request = InitiatePeerPushDebit(exchangeBaseUrl: baseURL,
                                        partialContractTerms: terms)
@@ -116,15 +117,17 @@ extension Peer2peerModel {
 
 // MARK: -
 extension Peer2peerModel {
-    private static var models: [Peer2peerModel] = []     // a list of models 
even though I currently need only one
+    private static var exchanges: [String] = []         // names of exchanges
+    private static var models: [Peer2peerModel] = []    // one model per 
exchange
 
-    static func model() -> Peer2peerModel {
-        if Peer2peerModel.models.count > 0 {
-            let model = Peer2peerModel.models[0]
+    static func model(baseURL: String) -> Peer2peerModel {
+        if let index = Peer2peerModel.exchanges.firstIndex(of:baseURL) {
+            let model = Peer2peerModel.models[index]
             return model
         } else {        // new model
             let model = Peer2peerModel()
             Peer2peerModel.models.append(model)
+            Peer2peerModel.exchanges.append(baseURL)
             return model
         }
     }
diff --git a/TalerWallet1/Model/WithdrawModel.swift 
b/TalerWallet1/Model/WithdrawModel.swift
index 28fed21..6198915 100644
--- a/TalerWallet1/Model/WithdrawModel.swift
+++ b/TalerWallet1/Model/WithdrawModel.swift
@@ -52,12 +52,12 @@ fileprivate struct GetWithdrawalDetailsForURI: 
WalletBackendFormattedRequest {
 // MARK: -
 /// The result from getWithdrawalDetailsForAmount
 struct ManualWithdrawalDetails: Decodable {
-    var amountRaw: Amount
-    var amountEffective: Amount
-    var paytoUris: [String]
-    var tosAccepted: Bool
-    var ageRestrictionOptions: [Int]?
-    var numCoins: Int?
+    var tosAccepted: Bool               // Did the user accept the current 
version of the exchange's terms of service?
+    var amountRaw: Amount               // Amount that the user will transfer 
to the exchange
+    var amountEffective: Amount         // Amount that will be added to the 
user's wallet balance
+    var paytoUris: [String]             // Ways to pay the exchange
+    var ageRestrictionOptions: [Int]?   // Array of ages
+    var numCoins: Int?                  // Number of coins this 
amountEffective will create
 }
 /// A request to get an exchange's withdrawal details.
 fileprivate struct GetWithdrawalDetailsForAmount: 
WalletBackendFormattedRequest {
@@ -180,10 +180,10 @@ extension WithdrawModel {
     }
     @MainActor
     func sendAcceptIntWithdrawalM(_ exchangeBaseUrl: String, withdrawURL: 
String)   // M for MainActor
-      async throws -> String? {
+      async throws -> AcceptWithdrawalResponse? {
         let request = AcceptBankIntegratedWithdrawal(talerWithdrawUri: 
withdrawURL, exchangeBaseUrl: exchangeBaseUrl)
         let response = try await sendRequest(request, ASYNCDELAY)
-        return response.confirmTransferUrl
+        return response
     }
     @MainActor
     func sendAcceptManualWithdrawalM(_ exchangeBaseUrl: String, amount: 
Amount, restrictAge: Int?)   // M for MainActor
@@ -196,7 +196,7 @@ extension WithdrawModel {
 
 // MARK: -
 extension WithdrawModel {
-    private static var exchanges: [String] = []            // names of 
exchanges
+    private static var exchanges: [String] = []         // names of exchanges
     private static var models: [WithdrawModel] = []     // one model per 
exchange
 
     static func model(baseURL: String) -> WithdrawModel {
diff --git a/TalerWallet1/Views/Balances/BalancesListView.swift 
b/TalerWallet1/Views/Balances/BalancesListView.swift
index cd9d667..43398ce 100644
--- a/TalerWallet1/Views/Balances/BalancesListView.swift
+++ b/TalerWallet1/Views/Balances/BalancesListView.swift
@@ -18,6 +18,7 @@ struct BalancesListView: View {
     let hamburgerAction: () -> Void
 
     @State private var centsToTransfer: UInt64 = 0
+    @State private var purpose: String = ""
     @State private var showQRScanner: Bool = false
     @State private var showCameraAlert: Bool = false
 
@@ -73,7 +74,8 @@ struct BalancesListView: View {
         let _ = Self._printChanges()
         let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
 #endif
-        Content(symLog: symLog, balances: $balances, centsToTransfer: 
$centsToTransfer,
+        Content(symLog: symLog, balances: $balances,
+                centsToTransfer: $centsToTransfer, purpose: $purpose,
                 reloadAction: reloadAction)
             .navigationTitle(navTitle)
             .navigationBarTitleDisplayMode(.automatic)
@@ -109,6 +111,7 @@ extension BalancesListView {
         @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
         @Binding var balances: [Balance]
         @Binding var centsToTransfer: UInt64
+        @Binding var purpose: String
         var reloadAction: () async -> Void
 
         var body: some View {
@@ -119,7 +122,10 @@ extension BalancesListView {
             Group { // necessary for .backslide transition (bug in SwiftUI)
                 List(balances, id: \.self) { balance in
                     let model = TransactionsModel.model(currency: 
balance.available.currencyStr)
-                    BalancesSectionView(balance: balance, centsToTransfer: 
$centsToTransfer, model: model)
+                    BalancesSectionView(balance: balance,
+                                centsToTransfer: $centsToTransfer,
+                                        purpose: $purpose,
+                                          model: model)
                 }
                 .refreshable {
                     symLog?.log("refreshing")
diff --git a/TalerWallet1/Views/Balances/BalancesSectionView.swift 
b/TalerWallet1/Views/Balances/BalancesSectionView.swift
index ce14f10..813e614 100644
--- a/TalerWallet1/Views/Balances/BalancesSectionView.swift
+++ b/TalerWallet1/Views/Balances/BalancesSectionView.swift
@@ -18,6 +18,7 @@ struct BalancesSectionView: View {
     private let symLog = SymLogV()
     var balance:Balance
     @Binding var centsToTransfer: UInt64
+    @Binding var purpose: String
     var model: TransactionsModel?
 
     @State private var isShowingDetailView = false
@@ -29,7 +30,19 @@ struct BalancesSectionView: View {
     @State private var uncompletedTransactions: [Transaction] = []
 
     func dummyTransaction (_ transactionId: String) async throws {}
+    func reloadOneAction(_ transactionId: String) async throws -> Transaction {
+        if let model {
+            return try await model.getTransactionByIdT(transactionId)
+        } else {
+            throw WalletBackendError.walletCoreError
+        }
+    }
+
     var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
         let currency = balance.available.currencyStr
         let reloadCompleted = {
             if let model {
@@ -52,6 +65,7 @@ struct BalancesSectionView: View {
         let deleteAction = model?.deleteTransactionT ?? dummyTransaction
         let abortAction = model?.abortTransactionT ?? dummyTransaction
 
+        let p2pModel = Peer2peerModel.model(baseURL: currency)
 
         Section {
 //            if "KUDOS" == currency && !balance.available.isZero {
@@ -60,27 +74,33 @@ struct BalancesSectionView: View {
 //            }
             HStack(spacing: 0) {
                 NavigationLink(destination: LazyView {
-                    SendAmount(amountAvailable: balance.available)
+                    SendAmount(model: p2pModel,
+                     amountAvailable: balance.available,
+                     centsToTransfer: $centsToTransfer,
+                             purpose: $purpose)
                   }, tag: 1, selection: $buttonSelected
                 ) { EmptyView() }.frame(width: 0).opacity(0).hidden()
 
                 NavigationLink(destination: LazyView {
-                    RequestPayment(scopeInfo: balance.scopeInfo,
-                             centsToTransfer: $centsToTransfer)
+                    RequestPayment(model: p2pModel,
+                               scopeInfo: balance.scopeInfo,
+                         centsToTransfer: $centsToTransfer,
+                                 purpose: $purpose)
                   }, tag: 2, selection: $buttonSelected
                 ) { EmptyView() }.frame(width: 0).opacity(0).hidden()
 
                 NavigationLink(destination: LazyView {
                     TransactionsListView(navTitle: String(localized: 
"Transactions"), currency: currency,
                                      transactions: completedTransactions,
-                                     reloadAction: reloadCompleted,
+                                  reloadAllAction: reloadCompleted,
+                                  reloadOneAction: reloadOneAction,
                                      deleteAction: deleteAction,
                                       abortAction: abortAction)
                   }, tag: 3, selection: $buttonSelected
                 ) { EmptyView() }.frame(width: 0).opacity(0).hidden()
 
                 BalanceRowView(amount: balance.available, sendAction: {
-print("button: Send Coins: \(currency)")
+print("button: Send \(currency)")
                         buttonSelected = 1      // will trigger SendAmount 
NavigationLink
                     }, recvAction: {
 print("button: Request Payment: \(currency)")
@@ -99,7 +119,8 @@ let _ = print("button: Pending Transactions: \(currency)")
                     LazyView {
                         TransactionsListView(navTitle: String(localized: 
"Pending"), currency: currency,
                                          transactions: pendingTransactions,
-                                         reloadAction: reloadPending,
+                                      reloadAllAction: reloadPending,
+                                      reloadOneAction: reloadOneAction,
                                          deleteAction: deleteAction,
                                           abortAction: abortAction)
                     }
@@ -121,9 +142,10 @@ let _ = print("button: Uncompleted Transactions: 
\(currency)")
                     LazyView {
                         TransactionsListView(navTitle: String(localized: 
"Uncompleted"), currency: currency,
                                              transactions: 
uncompletedTransactions,
-                                             reloadAction: reloadUncompleted,
+                                          reloadAllAction: reloadUncompleted,
+                                          reloadOneAction: reloadOneAction,
                                              deleteAction: deleteAction,
-                                             abortAction: abortAction)
+                                              abortAction: abortAction)
                     }
                 } label: {
                     UncompletedRowView(uncompletedTransactions: 
uncompletedTransactions)
@@ -146,6 +168,7 @@ let _ = print("button: Uncompleted Transactions: 
\(currency)")
 #if DEBUG
 fileprivate struct BindingViewContainer : View {
     @State var centsToTransfer: UInt64 = 333
+    @State private var purpose: String = "bla-bla"
 
     var body: some View {
         let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, 
exchangeBaseUrl: DEMOEXCHANGE, currency: LONGCURRENCY)
@@ -156,7 +179,10 @@ fileprivate struct BindingViewContainer : View {
                       requiresUserInput: false,
                               scopeInfo: scopeInfo)
         List {
-            BalancesSectionView(balance: balance, centsToTransfer: 
$centsToTransfer, model: nil)
+            BalancesSectionView(balance: balance,
+                        centsToTransfer: $centsToTransfer,
+                                purpose: $purpose,
+                                  model: nil)
         }
     }
 }
diff --git a/TalerWallet1/Views/Exchange/ExchangeSectionView.swift 
b/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
index 725b6b9..a7c9fad 100644
--- a/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
+++ b/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
@@ -7,6 +7,7 @@ import taler_swift
 
 struct ExchangeRowView: View {
     let exchange: Exchange
+    let currency: String
     @Binding var centsToTransfer: UInt64
 
     @State private var buttonSelected: Int? = nil
@@ -30,15 +31,13 @@ struct ExchangeRowView: View {
         }.listRowSeparator(.hidden)
 
         HStack {        // buttons just set "buttonSelected" so the 
NavigationLink will trigger
-            Button("Deposit\nCoins") { buttonSelected = 1 }
+            Button("Deposit\n\(currency)") { buttonSelected = 1 }
                 .multilineTextAlignment(.center)
-                .lineLimit(2)
                 .buttonStyle(TalerButtonStyle(type: .bordered))
                 .disabled(true)     // TODO: after implementing Deposit check 
available
 
-            Button("Withdraw\nCoins") { buttonSelected = 2 }
+            Button("Withdraw\n\(currency)") { buttonSelected = 2 }
                 .multilineTextAlignment(.center)
-                .lineLimit(2)
                 .buttonStyle(TalerButtonStyle(type: .bordered))
         }.listRowSeparator(.visible)
 //        .listRowSeparatorTint(.red)
@@ -61,7 +60,7 @@ struct ExchangeSectionView: View {
 #endif
         Section {
             ForEach(exchanges) { exchange in
-                ExchangeRowView(exchange: exchange, centsToTransfer: 
$centsToTransfer)
+                ExchangeRowView(exchange: exchange, currency: currency, 
centsToTransfer: $centsToTransfer)
             }
             .accessibilityElement(children: .combine)
         } header: {
diff --git a/TalerWallet1/Views/Exchange/ManualWithdraw.swift 
b/TalerWallet1/Views/Exchange/ManualWithdraw.swift
index 68a37bc..ab849a0 100644
--- a/TalerWallet1/Views/Exchange/ManualWithdraw.swift
+++ b/TalerWallet1/Views/Exchange/ManualWithdraw.swift
@@ -6,33 +6,18 @@ import SwiftUI
 import taler_swift
 import SymLog
 
+// Will be called by the user tapping "Withdraw Coins" in the exchange list
 struct ManualWithdraw: View {
     private let symLog = SymLogV()
-    let navTitle = String(localized: "Withdraw Coins")
 
-    var exchange: Exchange
-    var model: WithdrawModel?
+    let exchange: Exchange
+    let model: WithdrawModel?
     @Binding var centsToTransfer: UInt64
 
     @State var manualWithdrawalDetails: ManualWithdrawalDetails? = nil
-//    @State var numCoins: Int = 0
-
-    // returns numCoins, 0 if invalid, -1 if unknown
-    //         either fees or empty string
-    private func numAndFee(detailsForAmount: ManualWithdrawalDetails?) -> 
(Int, String) {
-        do {
-            if let details = detailsForAmount {
-                let fee = try details.amountRaw - details.amountEffective
-                return (details.numCoins ?? -1,    // either the number of 
coins, or unknown
-                        fee.isZero ? "" : fee.readableDescription)
-            }
-        } catch {}
-        symLog.log("invalid")
-        return (0, "")      // invalid
-    }
-    @State var ageMenuList: [Int] = []
-    @State var selectedAge = 0
 
+//    @State var ageMenuList: [Int] = []
+//    @State var selectedAge = 0
 
     var body: some View {
 #if DEBUG
@@ -42,64 +27,32 @@ struct ManualWithdraw: View {
         let currency = exchange.currency!
         let navTitle = String(localized: "Withdraw \(currency)")
         let currencyField = CurrencyField(value: $centsToTransfer, currency: 
currency) // becomeFirstResponder
-        let agePicker = AgePicker(ageMenuList: $ageMenuList, selectedAge: 
$selectedAge)
+//        let agePicker = AgePicker(ageMenuList: $ageMenuList, selectedAge: 
$selectedAge)
 
         ScrollView {
             Text("from \(exchange.exchangeBaseUrl.trimURL())")
                 .font(.title3)
-            CurrencyInputView(currencyField: currencyField, title: 
String(localized: "Amount to withdraw:"))
-
-            let (numCoins, fee) = numAndFee(detailsForAmount: 
manualWithdrawalDetails)
-            let unknown   = (numCoins < 0)
-            let invalid   = (numCoins == 0)
-            let manyCoins = (numCoins > 99)
-            let quiteSome = (numCoins > 199)
-            let tooMany   = (numCoins > 999)
-            let hasFee    = (fee.count > 0)
-            let shownFee = hasFee ? String(localized: "- \(fee)") : 
String(localized: "No")
-            Text(invalid ? "invalid amount"
-               : tooMany ? "too many coins for a single withdrawal"
-                         : "\(shownFee) withdrawal fee")
-                .foregroundColor((invalid || tooMany || hasFee) ? .red : 
.primary)
-                .padding()
+            CurrencyInputView(currencyField: currencyField,
+                              title: String(localized: "Amount to withdraw:"))
 
-            if !invalid {
-                HStack {
-                    Text(unknown ? "Some" : "\(numCoins)")
-                        .foregroundColor(quiteSome ? .red : .primary)
-                    Text(tooMany ? "coins" : "coins to obtain:")
-                        .foregroundColor(tooMany ? .red : .primary)
+            let someCoins = SomeCoins(details: manualWithdrawalDetails)
+            QuiteSomeCoins(someCoins: someCoins, shouldShowFee: true,
+                           currency: currency, amountEffective: 
manualWithdrawalDetails?.amountEffective)
 
-                    Spacer()
-                    if !tooMany {
-                        let effective = 
manualWithdrawalDetails?.amountEffective ?? Amount(currency: currency, value: 0)
-                        Text(effective.readableDescription)
-                    }
-                } // xx coins to obtain:    YYY currency
-                .padding(.top)
-//                .font(.title3)
-
-                if !tooMany {
-                    if manyCoins {
-                        Text(quiteSome ? "Warning: It will take quite some 
time\nto generate this many coins!"
-                                       : "Warning: It will take some time\nto 
generate this many coins.")
-                        .multilineTextAlignment(.leading)
-                        .padding(.top, 6)
-                        .foregroundColor(quiteSome ? .red : .primary)
-                    } // warnings
+            if !someCoins.invalid {
+                if !someCoins.tooMany {
+//                    agePicker
 
-                    agePicker
-
-                    if let tosAcc = manualWithdrawalDetails?.tosAccepted {
-                        if tosAcc {
-                            let restrictAge: Int? = (selectedAge == 0) ? nil
-                                                                       : 
selectedAge
-let _ = print(selectedAge, restrictAge)
+                    if let tosAccepted = manualWithdrawalDetails?.tosAccepted {
+                        if tosAccepted {
+//                            let restrictAge: Int? = (selectedAge == 0) ? nil
+//                                                                       : 
selectedAge
+//let _ = print(selectedAge, restrictAge)
                             NavigationLink(destination: LazyView {
                                 ManualWithdrawDone(exchange: exchange,
                                                       model: model,
-                                            centsToTransfer: centsToTransfer,
-                                                restrictAge: restrictAge)
+                                            centsToTransfer: centsToTransfer)
+//                                                restrictAge: restrictAge)
                             }) {
                                 Text("Confirm Withdrawal")      // 
VIEW_WITHDRAW_ACCEPT
                             }.buttonStyle(TalerButtonStyle(type: .prominent))
@@ -144,15 +97,13 @@ let _ = print(selectedAge, restrictAge)
 #if DEBUG
 struct ManualWithdraw_Container : View {
     @State private var centsToTransfer: UInt64 = 510
-    @State private var manualWithdrawalDetails = 
ManualWithdrawalDetails(amountRaw: try! Amount(fromString: LONGCURRENCY + 
":5.1"),
-                                                                   
amountEffective: try! Amount(fromString: LONGCURRENCY + ":5.0"),
-                                                                         
paytoUris: [],
-                                                                       
tosAccepted: false,
-                                                             
ageRestrictionOptions: [],
-                                                                          
numCoins: 6)
-
+    @State private var details = ManualWithdrawalDetails(tosAccepted: false,
+                                                           amountRaw: try! 
Amount(fromString: LONGCURRENCY + ":5.1"),
+                                                     amountEffective: try! 
Amount(fromString: LONGCURRENCY + ":5.0"),
+                                                           paytoUris: [],
+                                               ageRestrictionOptions: [],
+                                                            numCoins: 6)
     var body: some View {
-        let model = WithdrawModel.model(baseURL: DEMOEXCHANGE)
         let exchange = Exchange(exchangeBaseUrl: DEMOEXCHANGE,
                                        currency: LONGCURRENCY,
                                       paytoUris: [],
@@ -161,9 +112,9 @@ struct ManualWithdraw_Container : View {
                           ageRestrictionOptions: [],
                                       permanent: false)
         ManualWithdraw(exchange: exchange,
-                          model: model,
+                          model: nil,
                 centsToTransfer: $centsToTransfer,
-        manualWithdrawalDetails: manualWithdrawalDetails)
+        manualWithdrawalDetails: details)
     }
 }
 
diff --git a/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift 
b/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
index 2e771ac..27c4213 100644
--- a/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
+++ b/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
@@ -10,13 +10,22 @@ struct ManualWithdrawDone: View {
     private let symLog = SymLogV()
     let navTitle = String(localized: "Wire Transfer")
 
-    var exchange: Exchange
-    var model: WithdrawModel?
-    var centsToTransfer: UInt64
-    var restrictAge: Int?
+    let exchange: Exchange
+    let model: WithdrawModel?
+    let centsToTransfer: UInt64
+//    let restrictAge: Int?
+
     @State var acceptManualWithdrawalResult: AcceptManualWithdrawalResult?
     @State var withdrawalTransaction: Transaction?
 
+    func reloadOneAction(_ transactionId: String) async throws -> Transaction {
+        if let model {
+            return try await model.getTransactionByIdT(transactionId)
+        } else {
+            throw WalletBackendError.walletCoreError
+        }
+    }
+
     var body: some View {
 #if DEBUG
         let _ = Self._printChanges()
@@ -25,7 +34,8 @@ struct ManualWithdrawDone: View {
         VStack {
             if let transaction = withdrawalTransaction {
                 TransactionDetailView(transaction: transaction,
-                                       doneAction: 
{ViewState.shared.popToRootView()})
+                                     reloadAction: reloadOneAction,
+                                       doneAction: 
ViewState.shared.popToRootView)
                 .navigationBarBackButtonHidden(true)            // exit only 
by Done-Button
                 .navigationTitle(navTitle)
             } else {
@@ -40,11 +50,10 @@ struct ManualWithdrawDone: View {
                 if let model {
                     let amount = Amount.amountFromCents(exchange.currency!, 
centsToTransfer)
                     let result = try await 
model.sendAcceptManualWithdrawalM(exchange.exchangeBaseUrl,
-                                                                             
amount: amount, restrictAge: restrictAge)
+                                                                             
amount: amount, restrictAge: 0)
 print(result as Any)
                     let transaction = try await 
model.getTransactionByIdT(result!.transactionId)
                     withdrawalTransaction = transaction
-//                  acceptManualWithdrawalResult = result
                 }
             } catch {    // TODO: error
                 symLog.log(error.localizedDescription)
diff --git a/TalerWallet1/Views/Exchange/QuiteSomeCoins.swift 
b/TalerWallet1/Views/Exchange/QuiteSomeCoins.swift
new file mode 100644
index 0000000..1c2febf
--- /dev/null
+++ b/TalerWallet1/Views/Exchange/QuiteSomeCoins.swift
@@ -0,0 +1,102 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct SomeCoins {
+    let numCoins: Int       // 0 == invalid, -1 == unknown
+    var unknown:   Bool { numCoins < 0 }
+    var invalid:   Bool { numCoins == 0 }
+    var manyCoins: Bool { numCoins > 99 }
+    var quiteSome: Bool { numCoins > 199 }
+    var tooMany:   Bool { numCoins > 999 }
+
+    let fee: String
+    var hasFee:    Bool { fee.count > 0 }
+}
+
+extension SomeCoins {
+    init(details: ManualWithdrawalDetails?) {
+        do {
+            if let details {
+                // Incoming: fee = raw - effective
+                let fee = try details.amountRaw - details.amountEffective
+                self.init(numCoins: details.numCoins ?? -1,    // either the 
number of coins, or unknown
+                               fee: fee.isZero ? "" : fee.readableDescription)
+                return
+            }
+        } catch {}
+        self.init(numCoins: 0, fee:"")      // invalid
+    }
+
+    init(details: CheckPeerPullCreditResponse?) {
+        do {
+            if let details {
+                // Incoming: fee = raw - effective
+                let fee = try details.amountRaw - details.amountEffective
+                self.init(numCoins: details.numCoins ?? -1,    // either the 
number of coins, or unknown
+                               fee: fee.isZero ? "" : fee.readableDescription)
+                return
+            }
+        } catch {}
+        self.init(numCoins: 0, fee:"")      // invalid
+    }
+}
+// MARK: -
+struct QuiteSomeCoins: View {
+    private let symLog = SymLogV()
+    let someCoins: SomeCoins
+    let shouldShowFee: Bool
+    let currency: String
+    let amountEffective: Amount?
+
+    var body: some View {
+        if shouldShowFee {
+            let shownFee = someCoins.hasFee ? String(localized: "- 
\(someCoins.fee)")
+                                            : String(localized: "No")
+            Text(someCoins.invalid ? "invalid amount"
+               : someCoins.tooMany ? "too many coins for a single withdrawal"
+                                   : "\(shownFee) withdrawal fee")
+            .foregroundColor((someCoins.invalid || someCoins.tooMany || 
someCoins.hasFee) ? .red : .primary)
+            .padding(4)
+        }
+        if !someCoins.invalid {
+            HStack {
+                Text(someCoins.unknown ? "Some" : "\(someCoins.numCoins)")
+                    .foregroundColor(someCoins.quiteSome ? .red : .primary)
+                Text(someCoins.tooMany ? "coins" : "coins to obtain:")
+                    .foregroundColor(someCoins.tooMany ? .red : .primary)
+
+                Spacer()
+                if !someCoins.tooMany {
+                    let effective = amountEffective ?? Amount(currency: 
currency, value: 0)
+                    Text(effective.readableDescription)
+                }
+            } // xx coins to obtain:    YYY currency
+//            .font(.title3)
+            .padding(.top)
+
+            if !someCoins.tooMany {
+                if someCoins.manyCoins {
+                    Text(someCoins.quiteSome ? "Warning: It will take quite 
some time\nto generate this many coins!"
+                                             : "Warning: It will take some 
time\nto generate this many coins.")
+                    .multilineTextAlignment(.leading)
+                    .padding(.top, 6)
+                    .foregroundColor(someCoins.quiteSome ? .red : .primary)
+                } // warnings
+            }
+        }
+    }
+}
+// MARK: -
+struct QuiteSomeCoins_Previews: PreviewProvider {
+    static var previews: some View {
+        QuiteSomeCoins(someCoins: SomeCoins(numCoins: 4, fee: "20 " + 
LONGCURRENCY),
+                   shouldShowFee: true,
+                        currency: LONGCURRENCY,
+                 amountEffective: nil)
+    }
+}
diff --git a/TalerWallet1/Views/HelperViews/TransactionButton.swift 
b/TalerWallet1/Views/HelperViews/TransactionButton.swift
new file mode 100644
index 0000000..3c017e6
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/TransactionButton.swift
@@ -0,0 +1,88 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import AVFoundation
+
+struct TransactionButton: View {
+    let transactionId : String
+    let command : TxAction
+    let action: (_ transactionId: String) async throws -> Void
+
+    @State var disabled: Bool = false
+    @State var executed: Bool = false
+    var body: some View {
+        let role: ButtonRole? = (command == .abort) ? .cancel
+                              : (command == .delete) ? .destructive
+                                                     : nil
+        Button(role: role, action: {
+            Task {
+                disabled = true     // don't try this more than once
+                do {
+                    try await action(transactionId)
+//                    symLog.log("\(executed) \(transactionId)")
+                    executed = true
+                } catch {    // TODO: error
+//                    symLog.log(error.localizedDescription)
+                }
+            }
+        }, label: {
+            HStack {
+                if executed {
+                    switch command {
+                        case .delete:
+                            Text("Deleted from list")
+                        case .abort:
+                            Text("Abort pending...")
+                        case .fail:
+                            Text("Failing...")
+                        case .suspend:
+                            Text("Suspending...")
+                        case .resume:
+                            Text("Resuming...")
+                    }
+                } else {
+                    let spaces = "        "
+                    switch command {
+                        case .delete:
+                            Text("Delete from list" + spaces)
+                            Image(systemName: "trash")                  // 􀈑
+                        case .abort:
+                            Text("Abort" + spaces)
+                            Image(systemName: "x.circle")               // 􀀲
+                        case .fail:
+                            Text("Fail" + spaces)
+                            Image(systemName: "fanblades.slash")        // 􁝚
+                        case .suspend:
+                            Text("Suspend" + spaces)
+                            Image(systemName: "clock.badge.xmark")      // 􁜒
+                        case .resume:
+                            Text("Resume" + spaces)
+                            Image(systemName: "clock.arrow.circlepath") // 􀣔
+                    }
+                }
+            }
+            .font(.title)
+            .frame(maxWidth: .infinity)
+        })
+        .buttonStyle(.bordered)
+        .controlSize(.large)
+        .padding(.horizontal)
+        .disabled(disabled)
+    }
+
+}
+
+
+#if DEBUG
+struct TransactionButton_Previews: PreviewProvider {
+
+    static var previews: some View {
+        List {
+            TransactionButton(transactionId: "String", command: .abort, 
action: {transactionId in })
+        }
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Peer2peer/ReceivePurpose.swift 
b/TalerWallet1/Views/Peer2peer/ReceivePurpose.swift
index cbbafbf..16add0d 100644
--- a/TalerWallet1/Views/Peer2peer/ReceivePurpose.swift
+++ b/TalerWallet1/Views/Peer2peer/ReceivePurpose.swift
@@ -9,7 +9,6 @@ import SymLog
 struct ReceivePurpose: View {
     private let symLog = SymLogV()
     @FocusState private var isFocused: Bool
-    let model = Peer2peerModel.model()
     @State var peerPullCheck: CheckPeerPullCreditResponse?
 
     var scopeInfo: ScopeInfo
@@ -38,6 +37,7 @@ struct ReceivePurpose: View {
 
     var body: some View {
         let amount = Amount.amountFromCents(scopeInfo.currency, 
centsToTransfer)
+        let model = Peer2peerModel.model(baseURL: scopeInfo.currency)
 
         let fee = pullFee(ppCheck: peerPullCheck)
         VStack (spacing: 6) {
diff --git a/TalerWallet1/Views/Peer2peer/RequestPayment.swift 
b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
index 1b18aed..d8d3be3 100644
--- a/TalerWallet1/Views/Peer2peer/RequestPayment.swift
+++ b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
@@ -9,11 +9,12 @@ import SymLog
 struct RequestPayment: View {
     private let symLog = SymLogV()
 
+    let model: Peer2peerModel?
     var scopeInfo: ScopeInfo
     @Binding var centsToTransfer: UInt64
+    @Binding var purpose: String
 
     @State private var peerPullCheck: CheckPeerPullCreditResponse? = nil
-    @State private var purpose: String = ""
     @State private var expireDays: UInt = 0
 
     var body: some View {
@@ -29,6 +30,10 @@ struct RequestPayment: View {
             CurrencyInputView(currencyField: currencyField,
                               title: String(localized: "Amount to receive:"))
 
+            let someCoins = SomeCoins(details: peerPullCheck)
+            QuiteSomeCoins(someCoins: someCoins, shouldShowFee: true,
+                           currency: currency, amountEffective: 
peerPullCheck?.amountEffective)
+
             HStack {
                 let disabled = centsToTransfer == 0
 
@@ -59,10 +64,24 @@ struct RequestPayment: View {
         .navigationTitle(navTitle)
         .onAppear {   // make CurrencyField show the keyboard
             DebugViewC.shared.setViewID(VIEW_INVOICE_P2P)
-            print("❗️Yikes \(navTitle) onAppear")
+            symLog.log("❗️Yikes \(navTitle) onAppear")
         }
         .onDisappear {
-            print("❗️Yikes \(navTitle) onDisappear")
+            symLog.log("❗️Yikes \(navTitle) onDisappear")
+        }
+        .task(id: centsToTransfer) {
+            let amount = Amount.amountFromCents(currency, centsToTransfer)
+            do {
+                if let model {
+                    let ppCheck = try await model.checkPeerPullCreditM(amount, 
exchangeBaseUrl: nil)
+                    peerPullCheck = ppCheck
+                    // TODO: set from exchange
+//                agePicker.setAges(ages: peerPushCheck?.ageRestrictionOptions)
+                }
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+                peerPullCheck = nil
+            }
         }
     }
 }
diff --git a/TalerWallet1/Views/Peer2peer/SendAmount.swift 
b/TalerWallet1/Views/Peer2peer/SendAmount.swift
index 47cd64c..17e7026 100644
--- a/TalerWallet1/Views/Peer2peer/SendAmount.swift
+++ b/TalerWallet1/Views/Peer2peer/SendAmount.swift
@@ -6,32 +6,29 @@ import SwiftUI
 import taler_swift
 import SymLog
 
+// Will be called by the user tapping "Send Coins" in the balances list
 struct SendAmount: View {
     private let symLog = SymLogV()
-    let navTitle = String(localized: "Send Coins")
-//    @ObservedObject private var keyboardResponder = KeyboardResponder()
-//    @FocusState private var isFocused: Bool
-    let model = Peer2peerModel.model()
-    @State var peerPushCheck: CheckPeerPushDebitResponse?
 
+    let model: Peer2peerModel?
     let amountAvailable: Amount
-    let buttonFont: Font = .title2
+    @Binding var centsToTransfer: UInt64
+    @Binding var purpose: String
 
-    @State private var centsToTransfer: UInt64 = 0
-    @State private var purpose: String = ""
+    @State var peerPushCheck: CheckPeerPushDebitResponse?
     @State private var expireDays: UInt = 0
 
     private func fee(ppCheck: CheckPeerPushDebitResponse?) -> String {
         do {
-            if let p2pcheck = ppCheck {
-                let fee = try p2pcheck.amountEffective - p2pcheck.amountRaw
+            if let ppCheck {
+                // Outgoing: fee = effective - raw
+                let fee = try ppCheck.amountEffective - ppCheck.amountRaw
                 return fee.readableDescription
             }
         } catch {}
         return ""
     }
 
-
     var body: some View {
 #if DEBUG
         let _ = Self._printChanges()
@@ -51,15 +48,11 @@ struct SendAmount: View {
 
             Text("+ \(fee) payment fee")
                 .foregroundColor(.red)
-            Text("Choose where to send to:")
-                .padding(.top)
-                .font(.title3)
+                .padding(4)
+
             HStack {
-                let kbdShown: Bool = false  // 
keyboardResponder.keyboardHeight > 0
-                let title2 = kbdShown ? "To wallet" : "To another\nwallet"
                 let disabled = centsToTransfer == 0    // TODO: check 
amountAvailable
 
-
                 NavigationLink(destination: LazyView {
                     SendPurpose(model: model,
                       amountAvailable: amountAvailable,
@@ -82,19 +75,21 @@ struct SendAmount: View {
         .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .navigationTitle(navTitle)
         .onAppear {   // make CurrencyField show the keyboard
-            symLog.log("onAppear")
             DebugViewC.shared.setViewID(VIEW_SEND_P2P)
-            print("❗️Yikes SendAmount onAppear")
+            symLog.log("❗️Yikes SendAmount onAppear")
         }
         .onDisappear {
-            print("❗️Yikes SendAmount onDisappear")
+            symLog.log("❗️Yikes SendAmount onDisappear")
         }
         .task(id: centsToTransfer) {
             let amount = Amount.amountFromCents(currency, centsToTransfer)
             do {
-                peerPushCheck = try await model.checkPeerPushDebitM(amount)
-                // TODO: set from exchange
-//                agePicker.setAges(ages: peerPushCheck?.ageRestrictionOptions)
+                if let model {
+                    let ppCheck = try await model.checkPeerPushDebitM(amount)
+                    peerPushCheck = ppCheck
+                    // TODO: set from exchange
+                    //                agePicker.setAges(ages: 
peerPushCheck?.ageRestrictionOptions)
+                }
             } catch {    // TODO: error
                 symLog.log(error.localizedDescription)
                 peerPushCheck = nil
@@ -104,10 +99,22 @@ struct SendAmount: View {
 }
 // MARK: -
 #if DEBUG
+struct SendAmount_Container : View {
+    @State private var centsToTransfer: UInt64 = 510
+    @State private var purpose: String = ""
+
+    var body: some View {
+        let amount = Amount(currency: LONGCURRENCY, integer: 10, fraction: 0)
+        SendAmount(model: nil,
+         amountAvailable: amount,
+         centsToTransfer: $centsToTransfer,
+                 purpose: $purpose)
+    }
+}
+
 struct SendAmount_Previews: PreviewProvider {
     static var previews: some View {
-        let amount = Amount(currency: "TaLeR", integer: 10, fraction: 0)
-        SendAmount(amountAvailable: amount)
+        SendAmount_Container()
     }
 }
 #endif
diff --git a/TalerWallet1/Views/Peer2peer/SendNow.swift 
b/TalerWallet1/Views/Peer2peer/SendNow.swift
index 3ead004..8615341 100644
--- a/TalerWallet1/Views/Peer2peer/SendNow.swift
+++ b/TalerWallet1/Views/Peer2peer/SendNow.swift
@@ -53,8 +53,8 @@ struct SendNow: View {
                 if let model {
                     let timestamp = Timestamp.inSomeDays(expireDays)
                     let terms = PeerContractTerms(amount: amountToSend, 
summary: purpose, purse_expiration: timestamp)
-                    let baseURL = DEMOEXCHANGE  // TODO: use correct baseURL
-                    peerPushResponse = try await 
model.initiatePeerPushDebitM(baseURL, terms: terms)
+                    // TODO: user might choose baseURL
+                    peerPushResponse = try await 
model.initiatePeerPushDebitM(nil, terms: terms)
                     talerURI = peerPushResponse?.talerUri
                 }
             } catch {    // TODO: error
diff --git a/TalerWallet1/Views/Transactions/TransactionDetailView.swift 
b/TalerWallet1/Views/Transactions/TransactionDetailView.swift
index c048cc0..d9e16c1 100644
--- a/TalerWallet1/Views/Transactions/TransactionDetailView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionDetailView.swift
@@ -9,11 +9,15 @@ import SymLog
 struct TransactionDetailView: View {
     private let symLog = SymLogV()
     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+    @AppStorage("developerMode") var developerMode: Bool = false
 
-    var transaction: Transaction
+    @State var transaction: Transaction
+    var reloadAction: ((_ transactionId: String) async throws -> Transaction)
     var deleteAction: ((_ transactionId: String) async throws -> Void)?
     var abortAction: ((_ transactionId: String) async throws -> Void)?
     var doneAction: (() -> Void)?
+    var suspendAction: ((_ transactionId: String) async throws -> Void)?
+    var resumeAction: ((_ transactionId: String) async throws -> Void)?
 
     var body: some View {
 #if DEBUG
@@ -30,35 +34,49 @@ struct TransactionDetailView: View {
                 Text("\(dateString)")
                     .font(.title2)
 //                .listRowSeparator(.hidden)
-                SwitchCase(transaction: transaction, common: common)
+                SwitchCase(transaction: $transaction)
 
                 if transaction.isAbortable { if let abortAction {
-                    AbortButton(common: common, abortAction: abortAction)
+                    TransactionButton(transactionId: common.transactionId,
+                                      command: .abort, action: abortAction)
                 } }
                 if transaction.isDeleteable { if let deleteAction {
-                    DeleteButton(common: common, deleteAction: deleteAction)
+                    TransactionButton(transactionId: common.transactionId,
+                                      command: .delete, action: deleteAction)
                 } }
 
                 if let doneAction {
                     DoneButton(doneAction: doneAction)
-                        .onNotification(.TransactionStateTransition) { 
notification in
-print(notification.userInfo)
-                            if let transition = 
notification.userInfo?[TRANSACTIONTRANSITION] as? TransactionTransition {
-print(transition.newTxState.major)
-                                if transition.transactionId == 
common.transactionId {
-                                    doneAction()
-                                }
-                            }
-                        }
                 }
-//                if transaction.isSuspendable { if let suspendAction {
-//                    SuspendButton(common: common, suspendAction: 
suspendAction)
-//                } }
-//                if transaction.isResumable { if let resumeAction {
-//                    ResumeButton(common: common, resumeAction: resumeAction)
-//                } }
+                if developerMode {
+                    if transaction.isSuspendable { if let suspendAction {
+                        TransactionButton(transactionId: common.transactionId,
+                                          command: .suspend, action: 
suspendAction)
+                    } }
+                    if transaction.isResumable { if let resumeAction {
+                        TransactionButton(transactionId: common.transactionId,
+                                          command: .resume, action: 
resumeAction)
+                    } }
+                }
+            }.listStyle(myListStyle.style).anyView
+        }.onNotification(.TransactionStateTransition) { notification in
+            if let transition = notification.userInfo?[TRANSACTIONTRANSITION] 
as? TransactionTransition {
+                if transition.transactionId == common.transactionId {
+                    let newState = transition.newTxState.major
+                    if newState == .done { if let doneAction {
+                        symLog.log("newTxState.major == done")
+                        doneAction()
+                    }} else { Task { do {
+                        symLog.log("newState: \(newState), reloading 
transaction")
+                        let reloadedTransaction = try await 
reloadAction(common.transactionId)
+                        transaction = reloadedTransaction       // redraw
+                      } catch {
+                          symLog.log(error.localizedDescription)
+                    }}}
+                }
+            } else {
+                symLog.log(notification.userInfo as Any)
             }
-            .listStyle(myListStyle.style).anyView
         }
         .navigationTitle(navTitle)
         .onAppear {
@@ -73,21 +91,34 @@ print(transition.newTxState.major)
 //
 //extension TransactionDetail {
     struct SwitchCase: View {
-        let transaction: Transaction
-        let common: TransactionCommon
+        @Binding var transaction: Transaction
 
         var body: some View {
-            let pending = (common.txState.major == 
TransactionMajorState.pending)
+            let common = transaction.common
+            let pending = transaction.isPending
             switch transaction {
                 case .withdrawal(let withdrawalTransaction):
                     let details = withdrawalTransaction.details
                     if pending {
-                        switch details.withdrawalDetails.type {
-                            case .manual:
-                                ManualDetails(common: common, details: 
details.withdrawalDetails)
+                        let withdrawalDetails = details.withdrawalDetails
+                        switch withdrawalDetails.type {
+                            case .manual:               // "Make a wire 
transfer of \(amount) to"
+                                ManualDetails(common: common, details: 
withdrawalDetails)
+
+                            case .bankIntegrated:       // "Confirm with bank"
+                                VStack {
+                                    if let confirmationUrl = 
withdrawalDetails.bankConfirmationUrl {
+                                        if let destination = URL(string: 
confirmationUrl) {
+                                            // Show Hint that User should 
Confirm on bank website
+                                            Text("Waiting for bank 
confirmation")
+                                                .listRowSeparator(.hidden)
+                                            Link("Confirm with bank", 
destination: destination)
+                                                
.buttonStyle(TalerButtonStyle(type: .prominent, narrow: false, aligned: 
.center))
+                                                .padding(.horizontal)
 
-                            case .bankIntegrated:
-                                QRCodeDetails(transaction: transaction)
+                                        }
+                                    }
+                                }
                         }
                     }
                     ThreeAmounts(common: common, topTitle: String(localized: 
"Chosen amount to withdraw:"),
@@ -148,78 +179,6 @@ print(transition.newTxState.major)
             }
         }
     }
-    struct DeleteButton: View {
-        var common : TransactionCommon
-        var deleteAction: (_ transactionId: String) async throws -> Void
-
-        @State var disabled: Bool = false
-        @State var deleted: Bool = false
-        var body: some View {
-            Button(role: .destructive, action: {
-                Task {                  // delete from wallet-core
-                    disabled = true     // don't try this more than once
-                    do {
-                        try await deleteAction(common.transactionId)
-//                        symLog.log("deleted \(common.transactionId)")
-                        deleted = true
-                    } catch {    // TODO: error
-//                        symLog.log(error.localizedDescription)
-                    }
-                }
-            }, label: {
-                HStack {
-                    if deleted {
-                        Text("Deleted from list")
-                    } else {
-                        Text("Delete from list" + "        ")
-                        Image(systemName: "trash")
-                    }
-                }
-                .font(.title)
-                .frame(maxWidth: .infinity)
-            })
-            .buttonStyle(.bordered)
-            .controlSize(.large)
-            .padding(.horizontal)
-            .disabled(disabled)
-        }
-    }
-    struct AbortButton: View {
-        var common : TransactionCommon
-        var abortAction: (_ transactionId: String) async throws -> Void
-
-        @State var disabled: Bool = false
-        @State var aborting: Bool = false
-        var body: some View {
-            Button(role: .cancel, action: {
-                Task {                  // delete from wallet-core
-                    disabled = true     // don't try this more than once
-                    do {
-                        try await abortAction(common.transactionId)
-//                        symLog.log("aborted \(common.transactionId)")
-                        aborting = true
-                    } catch {    // TODO: error
-//                        symLog.log(error.localizedDescription)
-                    }
-                }
-            }, label: {
-                HStack {
-                    if aborting {
-                        Text("Abort pending...")
-                    } else {
-                        Text("Abort" + "        ")
-                        Image(systemName: "x.circle")
-                    }
-                }
-                .font(.title)
-                .frame(maxWidth: .infinity)
-            })
-            .buttonStyle(.bordered)
-            .controlSize(.large)
-            .padding(.horizontal)
-            .disabled(disabled)
-        }
-    }
     struct DoneButton: View {
         var doneAction: () -> Void
 
@@ -245,10 +204,11 @@ struct TransactionDetail_Previews: PreviewProvider {
                                       pending: false,
                                            id: "some payment ID",
                                          time: Timestamp(from: 
1_666_666_000_000))
+    static func reloadActionDummy(transactionId: String) async -> Transaction 
{ return withdrawal }
     static var previews: some View {
         Group {
-            TransactionDetailView(transaction: withdrawal, doneAction: 
doneActionDummy)
-            TransactionDetailView(transaction: payment, deleteAction: 
deleteTransactionDummy)
+            TransactionDetailView(transaction: withdrawal, reloadAction: 
reloadActionDummy, doneAction: doneActionDummy)
+            TransactionDetailView(transaction: payment, reloadAction: 
reloadActionDummy, deleteAction: deleteTransactionDummy)
         }
     }
 }
diff --git a/TalerWallet1/Views/Transactions/TransactionsListView.swift 
b/TalerWallet1/Views/Transactions/TransactionsListView.swift
index 0404d62..218bf75 100644
--- a/TalerWallet1/Views/Transactions/TransactionsListView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionsListView.swift
@@ -12,7 +12,8 @@ struct TransactionsListView: View {
 
     let currency: String
     let transactions: [Transaction]
-    var reloadAction: () async -> ()
+    let reloadAllAction: () async -> ()
+    let reloadOneAction: ((_ transactionId: String) async throws -> 
Transaction)
     let deleteAction: (_ transactionId: String) async throws -> ()
     let abortAction: (_ transactionId: String) async throws -> ()
 
@@ -26,7 +27,8 @@ struct TransactionsListView: View {
 //        let title = AttributedString(localized: "^[\(count) Ticket](inflect: 
true)")
         let title: String = "\(count) \(navTitle)"
         Content(symLog: symLog, currency: currency, transactions: 
transactions, myListStyle: $myListStyle,
-                reloadAction: reloadAction, deleteAction: deleteAction, 
abortAction: abortAction)
+                reloadAllAction: reloadAllAction, reloadOneAction: 
reloadOneAction,
+                deleteAction: deleteAction, abortAction: abortAction)
             .navigationTitle(title)
             .navigationBarTitleDisplayMode(.large)      // .inline
             .onAppear {
@@ -34,7 +36,7 @@ struct TransactionsListView: View {
             }
             .task {
                 symLog.log(".task ")
-                await reloadAction()
+                await reloadAllAction()
             }
     }
 }
@@ -45,7 +47,8 @@ extension TransactionsListView {
         let currency: String
         let transactions: [Transaction]
         @Binding var myListStyle: MyListStyle
-        let reloadAction: () async -> ()
+        let reloadAllAction: () async -> ()
+        let reloadOneAction: ((_ transactionId: String) async throws -> 
Transaction)
         let deleteAction: (_ transactionId: String) async throws -> ()
         let abortAction: (_ transactionId: String) async throws -> ()
 
@@ -87,6 +90,7 @@ extension TransactionsListView {
                         NavigationLink { LazyView {        // whole row like 
in a tableView
                             // pending may not be deleted, but only aborted
                             TransactionDetailView(transaction: transaction,
+                                                 reloadAction: reloadOneAction,
                                                  deleteAction: deleteAction,
                                                   abortAction: abortAction)
                         }} label: {
@@ -97,7 +101,7 @@ extension TransactionsListView {
                 }
                 .refreshable {
                     symLog?.log("refreshing")
-                    await reloadAction()
+                    await reloadAllAction()
                 }
                 .listStyle(myListStyle.style).anyView
                 .onAppear {
diff --git a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptDone.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptDone.swift
index 9e9ff1e..766ee8d 100644
--- a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptDone.swift
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptDone.swift
@@ -8,54 +8,61 @@ import SymLog
 
 struct WithdrawAcceptDone: View {
     private let symLog = SymLogV()
-    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+    let navTitle = String(localized: "Confirm with Bank")
 
-    let confirmTransferUrl: String?
+    let exchangeBaseUrl: String
+    let model: WithdrawModel?
+    let url: URL
 
-    let navTitle = String(localized: "Confirm with Bank")
+    @State private var confirmTransferUrl: String? = nil
+    @State private var transaction: Transaction? = nil
+
+    func reloadOneAction(_ transactionId: String) async throws -> Transaction {
+        if let model {
+            return try await model.getTransactionByIdT(transactionId)
+        } else {
+            throw WalletBackendError.walletCoreError
+        }
+    }
 
     var body: some View {
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
         VStack {
-            Text(navTitle)
-                .font(.title)
-                .padding()
-            List {
-                if let confirmTransferUrl {
-                    let destination = URL(string: confirmTransferUrl)!
-                    // Show Hint that User should Confirm on bank website
-                    Text("Waiting for bank confirmation")
-                        .listRowSeparator(.hidden)
-                    
-                    
-                    Link("Confirm with bank", destination: destination)
-                        .buttonStyle(TalerButtonStyle(type: .prominent, 
narrow: false, aligned: .center))
-                    // balances will be updated by TransactionStateTransition
-                }
+            if let transaction {
+                TransactionDetailView(transaction: transaction,
+                                     reloadAction: reloadOneAction,
+                                       doneAction: { dismissTop() })
+                .navigationBarBackButtonHidden(true)
+                .interactiveDismissDisabled()           // can only use "Done" 
button to dismiss
+                .navigationTitle(navTitle)
+            } else {
+                WithdrawProgressView(message: "Bank Confirmation")
+                    .navigationTitle("Loading...")
             }
-            .listStyle(myListStyle.style).anyView
-
-        }
-        .interactiveDismissDisabled()       // can only use "Done" button to 
dismiss
-        .navigationBarHidden(true)          // no back button, no title
-        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
-        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
-
-        .safeAreaInset(edge: .bottom) {
-            Button("Done", action: { dismissTop() })
-                .lineLimit(2)
-                .disabled(false)
-                .buttonStyle(TalerButtonStyle(type: .bordered, narrow: false, 
aligned: .center))
-                .padding()
-        }
-        .navigationTitle(navTitle)
-        .onAppear() {
+        }.onAppear() {
+            symLog.log("onAppear")
             DebugViewC.shared.setSheetID(SHEET_WITHDRAW_CONFIRM)
+        }.task {
+            do {
+                if let model {
+                    let result = try await 
model.sendAcceptIntWithdrawalM(exchangeBaseUrl, withdrawURL: url.absoluteString)
+                    confirmTransferUrl = result!.confirmTransferUrl
+                    transaction = try await 
model.getTransactionByIdT(result!.transactionId)
+                }
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+            }
         }
     }
 }
 // MARK: -
 struct WithdrawAcceptDone_Previews: PreviewProvider {
     static var previews: some View {
-        WithdrawAcceptDone(confirmTransferUrl: DEMOBANK)
+        WithdrawAcceptDone(exchangeBaseUrl: DEMOEXCHANGE,
+                           model: nil,
+                           url: URL(string: DEMOSHOP)!)
     }
 }
diff --git a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift
index 630f33d..ada134f 100644
--- a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift
@@ -8,23 +8,26 @@ import SymLog
 
 struct WithdrawAcceptView: View {
     private let symLog = SymLogV()
-    let url: URL
-    var model: WithdrawModel?
-
     let navTitle = String(localized: "Accept Withdrawal")
-    let detailsForAmount: ManualWithdrawalDetails
-    let baseURL: String
+
+    let exchangeBaseUrl: String
+    let model: WithdrawModel?
+    let amount: Amount?
+    let url: URL
 
     @State private var buttonSelected: Int? = nil
     @State private var confirmTransferUrl: String? = nil
+    @State private var transactionId: String? = nil
+    @State var manualWithdrawalDetails: ManualWithdrawalDetails?
 
     func acceptAction() -> () {
         Task {
             do {
                 if let model {
-                    if let transferUrl = try await 
model.sendAcceptIntWithdrawalM(baseURL, withdrawURL: url.absoluteString) {
-                        symLog.log(transferUrl)
-                        confirmTransferUrl = transferUrl
+                    if let acceptWithdrawalResponse = try await 
model.sendAcceptIntWithdrawalM(exchangeBaseUrl, withdrawURL: 
url.absoluteString) {
+                        confirmTransferUrl = 
acceptWithdrawalResponse.confirmTransferUrl
+                        transactionId = acceptWithdrawalResponse.transactionId
+                        symLog.log(confirmTransferUrl ?? "❗️Yikes: No 
confirmTransferUrl")
                         buttonSelected = 1      // trigger NavigationLink
                     } else {
                         // TODO: error sendAcceptIntWithdrawal failed
@@ -37,35 +40,37 @@ struct WithdrawAcceptView: View {
     }
 
     var body: some View {
-        List {
-            let raw = detailsForAmount.amountRaw
-            let effective = detailsForAmount.amountEffective
-            let fee = try! Amount.diff(raw, effective)      // TODO: different 
currencies
-            let outColor = WalletColors().transactionColor(false)
-            let inColor = WalletColors().transactionColor(true)
+        VStack {
+            if let manualWithdrawalDetails {
+                List {
 
-            HStack(spacing: 0) {
-                NavigationLink(destination: LazyView {
-                    WithdrawAcceptDone(confirmTransferUrl: confirmTransferUrl)
-                }, tag: 1, selection: $buttonSelected
-                ) { EmptyView() }.frame(width: 0).opacity(0).hidden()
-                
-                ThreeAmountsView(topTitle: String(localized: "Chosen amount to 
withdraw:"),
-                                 topAmount: raw, fee: fee,
-                                 bottomTitle: String(localized: "Coins to be 
withdrawn:"),
-                                 bottomAmount: effective,
-                                 large: false, pending: false, incoming: true,
-                                 baseURL: baseURL)
+                    HStack(spacing: 0) {
+                        NavigationLink(destination: LazyView {
+                            WithdrawAcceptDone(model: model, 
confirmTransferUrl: confirmTransferUrl, transactionId: transactionId)
+                        }, tag: 1, selection: $buttonSelected
+                        ) { EmptyView() }.frame(width: 0).opacity(0).hidden()
+
+                        ThreeAmountsView(topTitle: String(localized: "Chosen 
amount to withdraw:"),
+                                         topAmount: raw, fee: fee,
+                                         bottomTitle: String(localized: "Coins 
to be withdrawn:"),
+                                         bottomAmount: effective,
+                                         large: false, pending: false, 
incoming: true,
+                                         baseURL: exchangeBaseUrl)
+                    }
+                }
+                .safeAreaInset(edge: .bottom) {
+                    Button("Confirm Withdrawal", action: acceptAction)
+                        .lineLimit(2)
+                        .disabled(false)
+                        .buttonStyle(TalerButtonStyle(type: .prominent, 
narrow: false, aligned: .center))
+                        .padding()
+                }
+                .navigationTitle(navTitle)
+            } else {
+                WithdrawProgressView(message: exchangeBaseUrl.trimURL())
+                    .navigationTitle("Found Exchange")
             }
         }
-        .safeAreaInset(edge: .bottom) {
-            Button("Confirm Withdrawal", action: acceptAction)
-                .lineLimit(2)
-                .disabled(false)
-                .buttonStyle(TalerButtonStyle(type: .prominent, narrow: false, 
aligned: .center))
-                .padding()
-        }
-        .navigationTitle(navTitle)
 //        .overlay {
 //            VStack {
 //                ErrorView(errortext: "unknown state")       // TODO: Error
@@ -75,18 +80,32 @@ struct WithdrawAcceptView: View {
         .onAppear() {
             DebugViewC.shared.setSheetID(SHEET_WITHDRAW_ACCEPT)
         }
+        .task { if let amount, let model {
+            do { // TODO: cancelled
+                symLog.log(".task")
+                if exchangeBaseUrl.hasPrefix(HTTPS) {
+                    symLog.log("amount: \(amount), baseURL: 
\(String(describing: exchangeBaseUrl))")
+                    // TODO: let user choose exchange from list
+                    manualWithdrawalDetails = try await 
model.loadWithdrawalDetailsForAmountM(exchangeBaseUrl, amount: amount)
+                    symLog.log("raw: \(manualWithdrawalDetails!.amountRaw), 
effective: \(manualWithdrawalDetails!.amountEffective)")
+                } else {
+                    // TODO: error no exchange!
+                }
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+            }
+        } else {
+            // TODO: error no amount!
+        } }
     }
 }
 // MARK: -
 struct WithdrawAcceptView_Previews: PreviewProvider {
     static var previews: some View {
-        let details = ManualWithdrawalDetails(amountRaw:  try! 
Amount(fromString: LONGCURRENCY + ":2.4"),
-                                              amountEffective:  try! 
Amount(fromString: LONGCURRENCY + ":2.2"),
-                                              paytoUris: [],
-                                              tosAccepted: true)
-        WithdrawAcceptView(url: URL(string: DEMOSHOP)!,
-                           model: nil,
-                           detailsForAmount: details,
-                           baseURL: DEMOEXCHANGE)
+        let amount = try! Amount(fromString: LONGCURRENCY + ":2.4")
+        WithdrawAcceptView(exchangeBaseUrl: DEMOEXCHANGE,
+                                     model: nil,
+                                    amount: amount,
+                                       url: URL(string: DEMOSHOP)!)
     }
 }
diff --git 
a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift
index 3599a9b..f623c3a 100644
--- a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift
@@ -8,12 +8,16 @@ struct WithdrawProgressView: View {
     let message: String
 
     var body: some View {
-        Form {
+        VStack {
             Spacer()
             ProgressView()
             Spacer()
-            Text(message)
-                .font(.title)
+            HStack {
+                Spacer()
+                Text(message)
+                    .font(.title)
+                Spacer()
+            }
             Spacer()
             Spacer()
         }
diff --git a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift
index 5a51dfb..6f851ce 100644
--- a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift
@@ -3,6 +3,7 @@
  * 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 bank's website
@@ -10,41 +11,67 @@ import SymLog
 // after the user confirmed the withdrawal, we remind them to return to the 
bank website to confirm there, too
 struct WithdrawURIView: View {
     private let symLog = SymLogV()
+    let navTitle = String(localized: "Accept Withdrawal")
+
     // the URL from the bank website
-    var url: URL
+    let url: URL
 
     let model = WithdrawModel.model(baseURL: "global")      // TODO: get 
baseURL from URL
 
     // the exchange used for this withdrawal.
-    @State var exchangeBaseUrl: String = ""
-    @State var manualWithdrawalDetails: ManualWithdrawalDetails?
-    @State var didAcceptTOS: Bool = false
+    @State private var exchangeBaseUrl: String? = nil
+    @State private var manualWithdrawalDetails: ManualWithdrawalDetails?
 
     var body: some View {
         let badURL = "Error in URL: \(url)"
         VStack {
-            if !didAcceptTOS {      // user must accept ToS first
-                WithdrawTOSView(exchangeBaseUrl: exchangeBaseUrl,
-                                          model: model,
-                                         viewID: SHEET_WITHDRAW_TOS) {
-                    didAcceptTOS = true
+            if let manualWithdrawalDetails, let exchangeBaseUrl {
+                List {
+                    let raw = manualWithdrawalDetails.amountRaw
+                    let effective = manualWithdrawalDetails.amountEffective
+                    let currency = raw.currencyStr
+                    let fee = try! Amount.diff(raw, effective)
+                    let outColor = WalletColors().transactionColor(false)
+                    let inColor = WalletColors().transactionColor(true)
+
+                    ThreeAmountsView(topTitle: String(localized: "Chosen 
amount to withdraw:"),
+                                    topAmount: raw, fee: fee,
+                                  bottomTitle: String(localized: "\(currency) 
to be withdrawn:"),
+                                 bottomAmount: effective,
+                                        large: false, pending: false, 
incoming: true,
+                                      baseURL: exchangeBaseUrl)
+                    let someCoins = SomeCoins(details: manualWithdrawalDetails)
+                    QuiteSomeCoins(someCoins: someCoins, shouldShowFee: false,
+                                   currency: raw.currencyStr, amountEffective: 
effective)
                 }
-            } else {                // show Amount details and let user accept
-                WithdrawAcceptView(url: url, model: model,
-                                   detailsForAmount: manualWithdrawalDetails!,
-                                   baseURL: exchangeBaseUrl)
-            }
-        }
-        .overlay {
-            if !exchangeBaseUrl.hasPrefix(HTTPS) {
-                WithdrawProgressView(message: url.host ?? badURL)
-                    .navigationTitle("Contacting Exchange")
-            } else if manualWithdrawalDetails == nil {
-                WithdrawProgressView(message: exchangeBaseUrl.trimURL())
-                    .navigationTitle("Found Exchange")
+                .navigationTitle(navTitle)
+                let tosAccepted = manualWithdrawalDetails.tosAccepted
+                if tosAccepted {
+                    NavigationLink(destination: LazyView {
+                        WithdrawAcceptDone(exchangeBaseUrl: exchangeBaseUrl, 
model: model, url: url)
+                    }) {
+                        Text("Confirm Withdrawal")      // 
SHEET_WITHDRAW_ACCEPT
+                    }.buttonStyle(TalerButtonStyle(type: .prominent))
+                        .padding()
+                } else {
+                    NavigationLink(destination: LazyView {
+                        WithdrawTOSView(exchangeBaseUrl: exchangeBaseUrl,
+                                                  model: model,
+                                                 viewID: SHEET_WITHDRAW_TOS,
+                                           acceptAction: nil)         // pop 
back to here
+                    }) {
+                        Text("Check Terms of Service")  // VIEW_WITHDRAW_TOS
+                    }.buttonStyle(TalerButtonStyle(type: .prominent))
+                        .padding()
+                }
+            } else {
+                // Yikes no details or no baseURL
+//                WithdrawProgressView(message: url.host ?? badURL)
+//                    .navigationTitle("Contacting Exchange")
             }
         }
         .onAppear() {
+            symLog.log("onAppear")
             DebugViewC.shared.setSheetID(SHEET_WITHDRAWAL)
         }
         .task {
@@ -57,19 +84,17 @@ struct WithdrawURIView: View {
                 } else if let first = withdrawUriInfo.possibleExchanges.first {
                     exchangeBaseUrl = first.exchangeBaseUrl
                 }
-                if exchangeBaseUrl.hasPrefix(HTTPS) {
-                    symLog.log("amount: \(amount), baseURL: 
\(String(describing: exchangeBaseUrl))")
-                    // TODO: let user choose exchange from list
-                    manualWithdrawalDetails = try await 
model.loadWithdrawalDetailsForAmountM(exchangeBaseUrl, amount: amount)
-                    symLog.log("raw: \(manualWithdrawalDetails!.amountRaw), 
effective: \(manualWithdrawalDetails!.amountEffective)")
-                    if manualWithdrawalDetails!.tosAccepted {
-                        didAcceptTOS = true
-                    }
-                } else {
-                    // TODO: error no exchange!
+                if let exchangeBaseUrl {
+                    let details = try await 
model.loadWithdrawalDetailsForAmountM(exchangeBaseUrl, amount: amount)
+                    manualWithdrawalDetails = details
+//                  agePicker.setAges(ages: details?.ageRestrictionOptions)
+                } else {    // TODO: error
+                    symLog.log("no exchangeBaseUrl")
+                    manualWithdrawalDetails = nil
                 }
             } catch {    // TODO: error
                 symLog.log(error.localizedDescription)
+                manualWithdrawalDetails = nil
             }
         }
     }

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]