gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-ios] 07/54: Big Model update, removed unneccessary thread-s


From: gnunet
Subject: [taler-taler-ios] 07/54: Big Model update, removed unneccessary thread-safety code
Date: Fri, 30 Jun 2023 22:33:39 +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 2fbfa381a92830d15b953ab87da5391981b82005
Author: Marc Stibane <marc@taler.net>
AuthorDate: Sat Jun 17 14:53:31 2023 +0200

    Big Model update, removed unneccessary thread-safety code
---
 TalerWallet.xcodeproj/project.pbxproj              |   8 ++
 TalerWallet1/Controllers/Controller.swift          |   4 +-
 TalerWallet1/Model/BalancesModel.swift             |  12 +-
 TalerWallet1/Model/ExchangeModel.swift             |  31 ++--
 TalerWallet1/Model/PaymentURIModel.swift           |  38 +----
 TalerWallet1/Model/Peer2peerModel.swift            |  11 +-
 TalerWallet1/Model/PendingModel.swift              |  12 +-
 TalerWallet1/Model/SettingsModel.swift             |  58 ++++----
 TalerWallet1/Model/TransactionsModel.swift         |  58 +++-----
 TalerWallet1/Model/WalletInitModel.swift           |  15 +-
 TalerWallet1/Model/WalletModel.swift               |  38 ++---
 TalerWallet1/Model/WithdrawModel.swift             |  98 +++----------
 TalerWallet1/Views/Balances/BalancesListView.swift |  55 ++++----
 .../Views/Balances/BalancesSectionView.swift       |  52 ++++---
 TalerWallet1/Views/Exchange/ExchangeListView.swift | 157 +++++++++++----------
 .../Views/Exchange/ExchangeSectionView.swift       |  15 +-
 TalerWallet1/Views/Exchange/ManualWithdraw.swift   |  12 +-
 .../Views/Exchange/ManualWithdrawDone.swift        |  19 +--
 .../Views/HelperViews/QRCodeDetailView.swift       |  59 ++++++++
 TalerWallet1/Views/Main/WalletEmptyView.swift      |   8 +-
 TalerWallet1/Views/Payment/PaymentAcceptView.swift |  85 +++++------
 TalerWallet1/Views/Payment/PaymentURIView.swift    |  70 ++++++---
 TalerWallet1/Views/Peer2peer/ReceivePurpose.swift  |   4 +-
 TalerWallet1/Views/Peer2peer/RequestPayment.swift  |   3 +-
 TalerWallet1/Views/Peer2peer/SendAmount.swift      |  70 ++++++---
 TalerWallet1/Views/Peer2peer/SendNow.swift         |  41 +++---
 TalerWallet1/Views/Peer2peer/SendPurpose.swift     |  34 ++---
 .../Settings/Pending/PendingOpsListView.swift      |  28 ++--
 TalerWallet1/Views/Settings/SettingsView.swift     |  25 ++--
 TalerWallet1/Views/Sheets/URLSheet.swift           |   5 +-
 .../Views/Transactions/TransactionDetailView.swift |   6 +-
 .../Views/Transactions/TransactionsEmptyView.swift |   6 +-
 .../Views/Transactions/TransactionsListView.swift  |  10 +-
 .../WithdrawAcceptDone.swift                       |  61 ++++++++
 .../WithdrawAcceptView.swift                       |  94 +++++++-----
 .../WithdrawProgressView.swift                     |   3 +-
 .../WithdrawBankIntegrated/WithdrawTOSView.swift   |  55 ++++----
 .../WithdrawBankIntegrated/WithdrawURIView.swift   |  84 +++++------
 38 files changed, 751 insertions(+), 693 deletions(-)

diff --git a/TalerWallet.xcodeproj/project.pbxproj 
b/TalerWallet.xcodeproj/project.pbxproj
index 445de65..d570d56 100644
--- a/TalerWallet.xcodeproj/project.pbxproj
+++ b/TalerWallet.xcodeproj/project.pbxproj
@@ -15,6 +15,8 @@
                4E40E0BE29F25ABB00B85369 /* SendAmount.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E40E0BD29F25ABB00B85369 /* SendAmount.swift */; 
};
                4E50B3502A1BEE8000F9F01C /* ManualWithdraw.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E50B34F2A1BEE8000F9F01C /* 
ManualWithdraw.swift */; };
                4E53A33729F50B7B00830EC2 /* CurrencyField.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E53A33629F50B7B00830EC2 /* CurrencyField.swift 
*/; };
+               4E5A88F52A38A4FD00072618 /* QRCodeDetailView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4E5A88F42A38A4FD00072618 /* 
QRCodeDetailView.swift */; };
+               4E5A88F72A3B9E5B00072618 /* WithdrawAcceptDone.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4E5A88F62A3B9E5B00072618 /* 
WithdrawAcceptDone.swift */; };
                4E6EDD852A3615BE0031D520 /* ManualDetails.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E6EDD842A3615BE0031D520 /* ManualDetails.swift 
*/; };
                4E6EDD872A363D8D0031D520 /* ListStyle.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E6EDD862A363D8D0031D520 /* ListStyle.swift */; 
};
                4E753A062A0952F8002D9328 /* DebugViewC.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E753A052A0952F7002D9328 /* DebugViewC.swift */; 
};
@@ -140,6 +142,8 @@
                4E40E0BD29F25ABB00B85369 /* SendAmount.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name 
= SendAmount.swift; path = TalerWallet1/Views/Peer2peer/SendAmount.swift; 
sourceTree = SOURCE_ROOT; };
                4E50B34F2A1BEE8000F9F01C /* ManualWithdraw.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ManualWithdraw.swift; sourceTree = "<group>"; };
                4E53A33629F50B7B00830EC2 /* CurrencyField.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= CurrencyField.swift; sourceTree = "<group>"; };
+               4E5A88F42A38A4FD00072618 /* QRCodeDetailView.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= QRCodeDetailView.swift; sourceTree = "<group>"; };
+               4E5A88F62A3B9E5B00072618 /* WithdrawAcceptDone.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = WithdrawAcceptDone.swift; sourceTree = "<group>"; };
                4E6EDD842A3615BE0031D520 /* ManualDetails.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ManualDetails.swift; sourceTree = "<group>"; };
                4E6EDD862A363D8D0031D520 /* ListStyle.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ListStyle.swift; sourceTree = "<group>"; };
                4E753A042A08E720002D9328 /* transactions.json */ = {isa = 
PBXFileReference; lastKnownFileType = text.json; path = transactions.json; 
sourceTree = "<group>"; };
@@ -433,6 +437,7 @@
                        children = (
                                4EB0953C2989CBFE0043A8A1 /* 
WithdrawURIView.swift */,
                                4EB0953E2989CBFE0043A8A1 /* 
WithdrawAcceptView.swift */,
+                               4E5A88F62A3B9E5B00072618 /* 
WithdrawAcceptDone.swift */,
                                4EB0953F2989CBFE0043A8A1 /* 
WithdrawProgressView.swift */,
                                4EB095402989CBFE0043A8A1 /* 
WithdrawTOSView.swift */,
                        );
@@ -459,6 +464,7 @@
                                4E53A33629F50B7B00830EC2 /* CurrencyField.swift 
*/,
                                4EA551242A2C923600FEC9A8 /* 
CurrencyInputView.swift */,
                                4EEC157229F8242800D46A03 /* 
QRGeneratorView.swift */,
+                               4E5A88F42A38A4FD00072618 /* 
QRCodeDetailView.swift */,
                                4E6EDD862A363D8D0031D520 /* ListStyle.swift */,
                                4EB095482989CBFE0043A8A1 /* 
TextFieldAlert.swift */,
                                4EB095492989CBFE0043A8A1 /* AmountView.swift */,
@@ -697,6 +703,7 @@
                                4EB0956A2989CBFE0043A8A1 /* Buttons.swift in 
Sources */,
                                4EB095602989CBFE0043A8A1 /* 
BalancesSectionView.swift in Sources */,
                                4EEC157329F8242800D46A03 /* 
QRGeneratorView.swift in Sources */,
+                               4E5A88F72A3B9E5B00072618 /* 
WithdrawAcceptDone.swift in Sources */,
                                4EB095222989CBCB0043A8A1 /* Transaction.swift 
in Sources */,
                                4E9320432A14F6EA00A87B0E /* WalletColors.swift 
in Sources */,
                                4EB0955D2989CBFE0043A8A1 /* 
BalancesListView.swift in Sources */,
@@ -707,6 +714,7 @@
                                4EB095632989CBFE0043A8A1 /* 
WithdrawAcceptView.swift in Sources */,
                                4EB0956D2989CBFE0043A8A1 /* LoadingView.swift 
in Sources */,
                                4E50B3502A1BEE8000F9F01C /* 
ManualWithdraw.swift in Sources */,
+                               4E5A88F52A38A4FD00072618 /* 
QRCodeDetailView.swift in Sources */,
                                4E87C8732A31CB7F001C6406 /* 
TransactionsEmptyView.swift in Sources */,
                                4E87C8752A34B411001C6406 /* 
UncompletedRowView.swift in Sources */,
                                4E40E0BE29F25ABB00B85369 /* SendAmount.swift in 
Sources */,
diff --git a/TalerWallet1/Controllers/Controller.swift 
b/TalerWallet1/Controllers/Controller.swift
index 9a3db72..9e93489 100644
--- a/TalerWallet1/Controllers/Controller.swift
+++ b/TalerWallet1/Controllers/Controller.swift
@@ -43,8 +43,8 @@ class Controller: ObservableObject {
                 WalletCore.shared.versionInfo = versionInfo
                 backendState = .ready                       // dismiss the 
launch animation
             } catch {       // rethrows
-                symLog.log(error.localizedDescription)
-                backendState = .error                       // TODO: ❗️Yikes 
app cannot continue
+                symLog.log(error.localizedDescription)      // TODO: .error
+                backendState = .error                       // ❗️Yikes app 
cannot continue
                 throw error
             }
         } else {
diff --git a/TalerWallet1/Model/BalancesModel.swift 
b/TalerWallet1/Model/BalancesModel.swift
index 525aabf..b538915 100644
--- a/TalerWallet1/Model/BalancesModel.swift
+++ b/TalerWallet1/Model/BalancesModel.swift
@@ -8,10 +8,8 @@ fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 
seconds for debugging
 
 // MARK: -
 class BalancesModel: WalletModel {
-    @Published var balances: [Balance]
 
-    override init(_ symbol: Int = -1) {
-        balances = []                        // empty, but not nil
+    override init(_ symbol: Int = -1) {     // init with 0 to disable logging 
for this class
         super.init(symbol)
     }
 }
@@ -57,13 +55,13 @@ struct Balance: Decodable, Hashable {
 extension BalancesModel {
     /// fetch Balances from Wallet-Core. No networking involved
     @MainActor func fetchBalancesM()
-      async {          // M for MainActor
+      async -> [Balance] {          // M for MainActor
         do {
             let request = GetBalances()
-            let response = try await sendRequestM(request, ASYNCDELAY)
-            balances = response.balances                // trigger view update 
in BalancesListView
+            let response = try await sendRequest(request, ASYNCDELAY)
+            return response.balances                // trigger view update in 
BalancesListView
         } catch {
-            balances = []
+            return []
         }
     }
 }
diff --git a/TalerWallet1/Model/ExchangeModel.swift 
b/TalerWallet1/Model/ExchangeModel.swift
index fd2a5d5..17169d8 100644
--- a/TalerWallet1/Model/ExchangeModel.swift
+++ b/TalerWallet1/Model/ExchangeModel.swift
@@ -8,10 +8,7 @@ import SymLog
 fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
 
 class ExchangeModel: WalletModel {
-    @Published var exchanges: [Exchange]
-
     override init(_ symbol: Int = -1) {
-        exchanges = []                        // empty, but not nil
         super.init(symbol)
     }
 }
@@ -88,30 +85,22 @@ fileprivate struct AddExchange: 
WalletBackendFormattedRequest {
 // MARK: -
 extension ExchangeModel {
     /// ask wallet-core for its list of known exchanges
-    @MainActor func updateListM()
-      async throws {       // M for MainActor
+    @MainActor func listExchangesM()
+      async -> [Exchange] {         // M for MainActor
         do {
             let request = ListExchanges()
-            let response = try await sendRequestM(request, ASYNCDELAY)
-            exchanges = response.exchanges              // trigger view update 
in ExchangeListView
-        } catch {       // rethrows
-            symLog?.log(error.localizedDescription)
-            throw error
+            let response = try await sendRequest(request, ASYNCDELAY)
+            return response.exchanges
+        } catch {
+            return []               // empty, but not nil
         }
     }
 
     /// add a new exchange with URL to the wallet's list of known exchanges
-    func add(url: String) async throws  {
-        do {
-            symLog?.log("adding exchange: \(url)")       // TODO: notice
-            let request = AddExchange(exchangeBaseUrl: url)
-            _ = try await sendRequestT(request)         // TODO: MainActor?
-            symLog?.log("added exchange: \(url)")
-            try await updateListM()
-        } catch {       // rethrows
-            symLog?.log(error.localizedDescription)
-            throw error
-        }
+    func addExchange(url: String) async throws  {
+        symLog?.log("adding exchange: \(url)")       // TODO: .notice
+        let request = AddExchange(exchangeBaseUrl: url)
+        _ = try await sendRequest(request)
     }
 }
 
diff --git a/TalerWallet1/Model/PaymentURIModel.swift 
b/TalerWallet1/Model/PaymentURIModel.swift
index dea406c..73f7036 100644
--- a/TalerWallet1/Model/PaymentURIModel.swift
+++ b/TalerWallet1/Model/PaymentURIModel.swift
@@ -8,22 +8,12 @@ import AnyCodable
 //import SymLog
 fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
 
-enum PaymentState {
-    case error
-    case waitingForUriDetails
-    case receivedUriDetails
-    case waitingForPaymentAck
-    case receivedPaymentAck
-}
-
 class PaymentURIModel: WalletModel {
-    @Published var paymentState: PaymentState?
+
     override init(_ symbol: Int = -1) {     // init with 0 to disable logging 
for this class
         super.init(symbol)
     }
 }
-
-
 // MARK: - ContractTerms
 struct ContractTerms: Codable {
     let amount: Amount
@@ -149,30 +139,16 @@ extension PaymentURIModel {
     @MainActor
     func preparePayForUriM(_ talerPayUri: String)       // M for MainActor
       async throws -> PaymentDetailsForUri {
-        do {
-            paymentState = .waitingForUriDetails
-            let request = PreparePayForUri(talerPayUri: talerPayUri)
-            let response = try await sendRequestM(request, ASYNCDELAY)      // 
TODO: MainActor ?
-            paymentState = .receivedUriDetails
-            return response
-        } catch {       // rethrows
-            paymentState = .error
-            throw error
-        }
+          let request = PreparePayForUri(talerPayUri: talerPayUri)
+          let response = try await sendRequest(request, ASYNCDELAY)
+          return response
     }
     @MainActor
     func confirmPayM(_ proposalId: String)              // M for MainActor
       async throws -> ConfirmPayResult {
-        do {
-            paymentState = .waitingForPaymentAck
-            let request = confirmPayForUri(proposalId: proposalId)
-            let response = try await sendRequestM(request, ASYNCDELAY)      // 
TODO: MainActor ?
-            paymentState = .receivedPaymentAck
-            return response
-        } catch {       // rethrows
-            paymentState = .error
-            throw error
-        }
+          let request = confirmPayForUri(proposalId: proposalId)
+          let response = try await sendRequest(request, ASYNCDELAY)
+          return response
     }
 }
 
diff --git a/TalerWallet1/Model/Peer2peerModel.swift 
b/TalerWallet1/Model/Peer2peerModel.swift
index 490915c..b8ef429 100644
--- a/TalerWallet1/Model/Peer2peerModel.swift
+++ b/TalerWallet1/Model/Peer2peerModel.swift
@@ -22,9 +22,10 @@ struct PeerContractTerms: Codable {
 // MARK: -
 /// The result from CheckPeerPushDebit
 struct CheckPeerPushDebitResponse: Codable {
-    let amountEffective: Amount
+    let exchangeBaseUrl: String
     let amountRaw: Amount
-//    let maxExpirationDate: Timestamp          // TODO: limit expiration (30 
days or 7 days)
+    let amountEffective: Amount
+    let maxExpirationDate: Timestamp?          // TODO: limit expiration (30 
days or 7 days)
 }
 /// A request to check fees before sending coins to another wallet.
 fileprivate struct CheckPeerPushDebit: WalletBackendFormattedRequest {
@@ -91,7 +92,7 @@ extension Peer2peerModel {
     func checkPeerPushDebitM(_ amount: Amount)       // M for MainActor
       async throws -> CheckPeerPushDebitResponse {
           let request = CheckPeerPushDebit(amount: amount)
-          let response = try await sendRequestM(request, ASYNCDELAY)
+          let response = try await sendRequest(request, ASYNCDELAY)
           return response
     }
     /// query exchange for fees (invoice coins). Networking involved
@@ -99,7 +100,7 @@ extension Peer2peerModel {
     func checkPeerPullCreditM(_ amount: Amount, exchangeBaseUrl: String?)      
 // M for MainActor
     async throws -> CheckPeerPullCreditResponse {
         let request = CheckPeerPullCredit(exchangeBaseUrl: exchangeBaseUrl, 
amount: amount)
-        let response = try await sendRequestM(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY)
         return response
     }
     /// generate peer-push. Networking involved
@@ -108,7 +109,7 @@ extension Peer2peerModel {
       async throws -> PeerPushResponse {
         let request = InitiatePeerPushDebit(exchangeBaseUrl: baseURL,
                                        partialContractTerms: terms)
-        let response = try await sendRequestM(request, ASYNCDELAY)
+        let response = try await sendRequest(request, ASYNCDELAY)
         return response
     }
 }
diff --git a/TalerWallet1/Model/PendingModel.swift 
b/TalerWallet1/Model/PendingModel.swift
index 764f799..990b10d 100644
--- a/TalerWallet1/Model/PendingModel.swift
+++ b/TalerWallet1/Model/PendingModel.swift
@@ -9,10 +9,8 @@ import SymLog
 fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
 
 class PendingModel: WalletModel {
-    @Published var pendingOperations: [PendingOperation]
 
     override init(_ symbol: Int = -1) {
-        pendingOperations = []                // empty, but not nil
         super.init(symbol)
     }
 }
@@ -51,12 +49,14 @@ struct PendingOperation: Codable, Hashable {
 }
 // MARK: -
 extension PendingModel {
-    @MainActor func updateM()
-      async throws {   // M for MainActor
+    @MainActor func getPendingOperationsM()
+      async -> [PendingOperation] {   // M for MainActor
         do {
             let request = GetPendingOperations()
-            let response = try await sendRequestM(request, ASYNCDELAY)
-            pendingOperations = response.pendingOperations
+            let response = try await sendRequest(request, ASYNCDELAY)
+            return response.pendingOperations
+        } catch {
+            return []
         }
     }
 }
diff --git a/TalerWallet1/Model/SettingsModel.swift 
b/TalerWallet1/Model/SettingsModel.swift
index 0172cf6..10d35c0 100644
--- a/TalerWallet1/Model/SettingsModel.swift
+++ b/TalerWallet1/Model/SettingsModel.swift
@@ -7,10 +7,10 @@ import taler_swift
 import SymLog
 fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
 
-fileprivate let DEMO_EXCHANGEBASEURL = DEMOEXCHANGE     // 
"https://exchange.demo.taler.net/";
-fileprivate let DEMO_BANKBASEURL     = DEMOBANK         // 
"https://bank.demo.taler.net/";
+fileprivate let DEMO_EXCHANGEBASEURL = DEMOEXCHANGE
+fileprivate let DEMO_BANKBASEURL     = DEMOBANK
 fileprivate let DEMO_BANKAPIBASEURL  = DEMOBANK + 
"/demobanks/default/access-api/"
-fileprivate let DEMO_MERCHANTBASEURL = "https://backend.demo.taler.net/";
+fileprivate let DEMO_MERCHANTBASEURL = DEMOBACKEND
 fileprivate let DEMO_MERCHANTAUTHTOKEN = "secret-token:sandbox"
 
 // MARK: -
@@ -23,42 +23,32 @@ class SettingsModel: WalletModel {
 extension SettingsModel {
     @MainActor func loadTestKudosM()
       async throws {          // M for MainActor
-        do {
-            let amount = Amount(currency: DEMOCURRENCY, integer: 11, fraction: 
0)
-            let request = WalletBackendWithdrawTestBalance(amount: amount,
-                                                      bankBaseUrl: 
DEMO_BANKBASEURL,
-                                                  exchangeBaseUrl: 
DEMO_EXCHANGEBASEURL,
-                                             bankAccessApiBaseUrl: 
DEMO_BANKAPIBASEURL)
-            let response = try await sendRequestM(request, ASYNCDELAY)
-            symLog?.log("received: \(response)")
-        } catch {       // rethrows
-            symLog?.log(error.localizedDescription)
-            throw error
-        }
+        let amount = Amount(currency: DEMOCURRENCY, integer: 11, fraction: 0)
+        let request = WalletBackendWithdrawTestBalance(amount: amount,
+                                                  bankBaseUrl: 
DEMO_BANKBASEURL,
+                                              exchangeBaseUrl: 
DEMO_EXCHANGEBASEURL,
+                                         bankAccessApiBaseUrl: 
DEMO_BANKAPIBASEURL)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        symLog?.log("received: \(response)")
     }
 
     @MainActor func runIntegrationTestM(newVersion: Bool)
       async throws {               // M for MainActor
-        do {
-            let amountW = Amount(currency: DEMOCURRENCY, integer: 3, fraction: 
0)
-            let amountS = Amount(currency: DEMOCURRENCY, integer: 1, fraction: 
0)
-            let request = WalletBackendRunIntegration(newVersion: newVersion,
-                                                amountToWithdraw: amountW,
-                                                   amountToSpend: amountS,
-                                                     bankBaseUrl: 
DEMO_BANKAPIBASEURL,
-                                            bankAccessApiBaseUrl: 
DEMO_BANKAPIBASEURL,
-                                                 exchangeBaseUrl: 
DEMO_EXCHANGEBASEURL,
-                                                 merchantBaseUrl: 
DEMO_MERCHANTBASEURL,
-                                               merchantAuthToken: 
DEMO_MERCHANTAUTHTOKEN)
-            let _ = try await sendRequestT(request, ASYNCDELAY)
-            symLog?.log("runIntegrationTest finished")
-        } catch {       // rethrows
-            symLog?.log(error.localizedDescription)
-            throw error
-        }
+        let amountW = Amount(currency: DEMOCURRENCY, integer: 3, fraction: 0)
+        let amountS = Amount(currency: DEMOCURRENCY, integer: 1, fraction: 0)
+        let request = WalletBackendRunIntegration(newVersion: newVersion,
+                                            amountToWithdraw: amountW,
+                                               amountToSpend: amountS,
+                                                 bankBaseUrl: 
DEMO_BANKAPIBASEURL,
+                                        bankAccessApiBaseUrl: 
DEMO_BANKAPIBASEURL,
+                                             exchangeBaseUrl: 
DEMO_EXCHANGEBASEURL,
+                                             merchantBaseUrl: 
DEMO_MERCHANTBASEURL,
+                                           merchantAuthToken: 
DEMO_MERCHANTAUTHTOKEN)
+        let _ = try await sendRequest(request, ASYNCDELAY)
+        symLog?.log("runIntegrationTest finished")
     }
 }
-
+// MARK: -
 /// A request to add a test balance to the wallet.
 fileprivate struct WalletBackendWithdrawTestBalance: 
WalletBackendFormattedRequest {
     typealias Response = String
@@ -80,7 +70,7 @@ fileprivate struct WalletBackendWithdrawTestBalance: 
WalletBackendFormattedReque
         var bankAccessApiBaseUrl: String
     }
 }
-
+// MARK: -
 /// A request to add a test balance to the wallet.
 fileprivate struct WalletBackendRunIntegration: WalletBackendFormattedRequest {
     struct Response: Decodable {}
diff --git a/TalerWallet1/Model/TransactionsModel.swift 
b/TalerWallet1/Model/TransactionsModel.swift
index 668f3c3..bb90bdb 100644
--- a/TalerWallet1/Model/TransactionsModel.swift
+++ b/TalerWallet1/Model/TransactionsModel.swift
@@ -9,18 +9,12 @@ fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 
seconds for debugging
 
 // MARK: -
 class TransactionsModel: WalletModel {
-    @Published var transactions: [Transaction]
 
     static func specialTransactions(_ transactions: [Transaction]) -> 
[Transaction] {
         transactions.filter { transaction in
             transaction.isSpecial
         }
     }
-    var specialTransactions: [Transaction] {
-        transactions.filter { transaction in
-            transaction.isSpecial
-        }
-    }
 
     static func completedTransactions(_ transactions: [Transaction]) -> 
[Transaction] {
         transactions.filter { transaction in
@@ -39,7 +33,6 @@ class TransactionsModel: WalletModel {
     }
 
     override init(_ symbol: Int = -1) {
-        transactions = []                // empty, but not nil
         super.init(symbol)
     }
 }
@@ -92,49 +85,42 @@ struct DeleteTransaction: WalletBackendFormattedRequest {
 // MARK: -
 extension TransactionsModel {
     /// ask wallet-core for its list of transactions filtered by searchString
-    func fetchTransactions(currency: String?) async {             // might be 
called from a background thread itself
-        await fetchTransactionsM(currency: currency, searchString: nil)
-    }
-    /// fetch transactions from Wallet-Core. No networking involved
-    @MainActor func fetchTransactionsM(currency: String? = nil, searchString: 
String? = nil)
-      async {              // M for MainActor
+    func fetchTransactionsT(currency: String? = nil, searchString: String? = 
nil)
+      async -> [Transaction] {                                          // 
might be called from a background thread itself
         do {
             let request = GetTransactions(currency: currency, search: 
searchString)
-            let response = try await sendRequestM(request, ASYNCDELAY)
-            transactions = response.transactions        // trigger view update 
in TransactionsListView
+            let response = try await sendRequest(request, ASYNCDELAY)
+            return response.transactions
         } catch {
-            transactions = []
+            return []
         }
     }
+    /// fetch transactions from Wallet-Core. No networking involved
+    @MainActor func fetchTransactionsM(currency: String? = nil, searchString: 
String? = nil)
+      async -> [Transaction] {    // M for MainActor
+        await fetchTransactionsT(currency: currency, searchString: 
searchString)
+    }
 
-    func abortTransaction(transactionId: String) async throws {        // 
might be called from a background thread itself
-        try await abortTransactionM(transactionId: transactionId)      // call 
deleteTransactionM on main thread
+    func abortTransactionT(transactionId: String)
+      async throws {                                                    // 
might be called from a background thread itself
+        let request = AbortTransaction(transactionId: transactionId)
+        let _ = try await sendRequest(request, ASYNCDELAY)
     }
     /// delete the specified transaction from Wallet-Core. No networking 
involved
     @MainActor func abortTransactionM(transactionId: String)
-    async throws {                // M for MainActor
-        do {
-            let request = AbortTransaction(transactionId: transactionId)
-            let _ = try await sendRequestT(request, ASYNCDELAY)
-        } catch {       // rethrows
-            symLog?.log(error.localizedDescription)
-            throw error
-        }
+      async throws {             // M for MainActor
+        try await abortTransactionT(transactionId: transactionId)       // 
call abortTransaction on main thread
     }
 
-    func deleteTransaction(transactionId: String) async throws {        // 
might be called from a background thread itself
-        try await deleteTransactionM(transactionId: transactionId)      // 
call deleteTransactionM on main thread
+    func deleteTransactionT(transactionId: String)
+      async throws {                                                    // 
might be called from a background thread itself
+        let request = DeleteTransaction(transactionId: transactionId)
+        let _ = try await sendRequest(request, ASYNCDELAY)
     }
     /// delete the specified transaction from Wallet-Core. No networking 
involved
     @MainActor func deleteTransactionM(transactionId: String)
-    async throws {                // M for MainActor
-        do {
-            let request = DeleteTransaction(transactionId: transactionId)
-            let _ = try await sendRequestT(request, ASYNCDELAY)
-        } catch {       // rethrows
-            symLog?.log(error.localizedDescription)
-            throw error
-        }
+      async throws {              // M for MainActor
+        try await deleteTransactionT(transactionId: transactionId)      // 
call deleteTransaction on main thread
     }
 }
 
diff --git a/TalerWallet1/Model/WalletInitModel.swift 
b/TalerWallet1/Model/WalletInitModel.swift
index d977890..8ce0bf4 100644
--- a/TalerWallet1/Model/WalletInitModel.swift
+++ b/TalerWallet1/Model/WalletInitModel.swift
@@ -49,16 +49,11 @@ extension WalletInitModel {
     /// initalize Wallet-Core. Will do networking
     func initWalletT()              // T for any Thread
       async throws -> VersionInfo? {
-        do {
-            let docPath = try docPath()
-            let request = WalletBackendInitRequest(persistentStoragePath: 
docPath)
-            symLog?.log("info: not main thread")
-            let response = try await sendRequestT(request, 0)    // no Delay
-            return response.versionInfo
-        } catch {       // rethrows
-            symLog?.log("error: \(error)")
-            throw error
-        }
+          let docPath = try docPath()
+          let request = WalletBackendInitRequest(persistentStoragePath: 
docPath)
+          symLog?.log("info: not main thread")
+          let response = try await sendRequest(request, 0)    // no Delay
+          return response.versionInfo
     }
 
     private func docPath () throws -> String {
diff --git a/TalerWallet1/Model/WalletModel.swift 
b/TalerWallet1/Model/WalletModel.swift
index 9fb0622..34626ca 100644
--- a/TalerWallet1/Model/WalletModel.swift
+++ b/TalerWallet1/Model/WalletModel.swift
@@ -10,30 +10,15 @@ fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 
seconds for debugging
 
 // MARK: -
 /// The "virtual" base class for all models
-class WalletModel: ObservableObject {
+class WalletModel {
     static func className() -> String {"\(self)"}
     var symLog: SymLogC?
 
-    @Published var loading: Bool = false                // update view
-
     init(_ symbol: Int) {                               // init with 0 to 
disable logging for this class
         self.symLog = SymLogC(symbol == 0 ? 0 : -1, funcName: Self.className())
     }
 
-    @MainActor func sendRequestM<T: WalletBackendFormattedRequest> (_ request: 
T, _ delay: UInt = 0)
-      async throws -> T.Response {  // M for MainActor
-        loading = true                                  // enter progressView
-        do {
-            let response = try await sendRequestT(request, delay)
-            loading = false                             // exit progressView
-            return response
-        } catch {       // rethrows
-            loading = false                             // exit progressView
-            throw error
-        }
-    }
-
-    func sendRequestT<T: WalletBackendFormattedRequest> (_ request: T, _ 
delay: UInt = 0)
+    func sendRequest<T: WalletBackendFormattedRequest> (_ request: T, _ delay: 
UInt = 0)
     async throws -> T.Response {    // T for any Thread
         let sendTime = Date.now
         do {
@@ -56,24 +41,21 @@ class WalletModel: ObservableObject {
         }
     }
 
-    func getTransactionById(transactionId: String) async throws -> Transaction 
{        // might be called from a background thread itself
-        return try await getTransactionByIdM(transactionId: transactionId)     
 // call deleteTransactionM on main thread
+    func getTransactionByIdT(_ transactionId: String)
+      async throws -> Transaction {              // T for any Thread
+        // might be called from a background thread itself
+        let request = GetTransactionById(transactionId: transactionId)
+        return try await sendRequest(request, ASYNCDELAY)
     }
     /// get the specified transaction from Wallet-Core. No networking involved
-    @MainActor func getTransactionByIdM(transactionId: String)
+    @MainActor func getTransactionByIdM(_ transactionId: String)
     async throws -> Transaction {                // M for MainActor
-        do {
-            let request = GetTransactionById(transactionId: transactionId)
-            let response = try await sendRequestT(request, ASYNCDELAY)
-            return response
-        } catch {       // rethrows
-            throw error
-        }
+        return try await getTransactionByIdT(transactionId)      // call 
GetTransactionById on main thread
     }
 }
 // MARK: -
 /// A request to get a wallet transaction by ID.
-struct GetTransactionById: WalletBackendFormattedRequest {
+fileprivate struct GetTransactionById: WalletBackendFormattedRequest {
     typealias Response = Transaction
     func operation() -> String { return "getTransactionById" }
     func args() -> Args { return Args(transactionId: transactionId) }
diff --git a/TalerWallet1/Model/WithdrawModel.swift 
b/TalerWallet1/Model/WithdrawModel.swift
index b3a2161..96fc31a 100644
--- a/TalerWallet1/Model/WithdrawModel.swift
+++ b/TalerWallet1/Model/WithdrawModel.swift
@@ -7,22 +7,8 @@ import taler_swift
 import SymLog
 fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
 
-enum WithdrawState {
-    case error
-    case waitingForUriDetails
-    case receivedUriDetails
-    case waitingForAmountDetails
-    case receivedAmountDetails
-    case waitingForTOS
-    case receivedTOS
-    case waitingForTOSAck
-    case receivedTOSAck
-    case waitingForWithdrAck
-    case receivedWithdrAck
-}
-
 class WithdrawModel: WalletModel {
-    @Published var withdrawState: WithdrawState?
+
     override init(_ symbol: Int = -1) {     // init with 0 to disable logging 
for this class
         super.init(symbol)
     }
@@ -166,87 +152,45 @@ extension WithdrawModel {
     @MainActor
     func loadWithdrawalDetailsForUriM(_ talerWithdrawUri: String)              
 // M for MainActor
       async throws -> WithdrawUriInfoResponse {
-        do {
-            withdrawState = .waitingForUriDetails
-            let request = GetWithdrawalDetailsForURI(talerWithdrawUri: 
talerWithdrawUri)
-            let response = try await sendRequestM(request, ASYNCDELAY)         
 // TODO: MainActor ?
-            withdrawState = .receivedUriDetails
-            return response
-        } catch {       // rethrows
-            withdrawState = .error
-            throw error
-        }
+        let request = GetWithdrawalDetailsForURI(talerWithdrawUri: 
talerWithdrawUri)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
     }
     @MainActor
     func loadWithdrawalDetailsForAmountM(_ exchangeBaseUrl: String, amount: 
Amount)  // M for MainActor
       async throws -> ManualWithdrawalDetails {
-        do {
-            withdrawState = .waitingForAmountDetails
-            let request = GetWithdrawalDetailsForAmount(exchangeBaseUrl: 
exchangeBaseUrl,
-                                                                 amount: 
amount)
-            let response = try await sendRequestM(request, ASYNCDELAY)         
 // TODO: MainActor ?
-            withdrawState = .receivedAmountDetails
-            return response
-        } catch {       // rethrows
-            withdrawState = .error
-            throw error
-        }
+        let request = GetWithdrawalDetailsForAmount(exchangeBaseUrl: 
exchangeBaseUrl,
+                                                             amount: amount)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
     }
     @MainActor
     func loadExchangeTermsOfServiceM(_ exchangeBaseUrl: String)             // 
M for MainActor
       async throws -> ExchangeTermsOfService {
-        do {
-            withdrawState = .waitingForTOS
-            let request = GetExchangeTermsOfService(exchangeBaseUrl: 
exchangeBaseUrl)
-            let response = try await sendRequestM(request, ASYNCDELAY)         
 // TODO: MainActor ?
-            withdrawState = .receivedTOS
-            return response
-        } catch {       // rethrows
-            withdrawState = .error
-            throw error
-        }
+        let request = GetExchangeTermsOfService(exchangeBaseUrl: 
exchangeBaseUrl)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
     }
     @MainActor
     func setExchangeTOSAcceptedM(_ exchangeBaseUrl: String, etag: String)      
 // M for MainActor
       async throws -> Decodable {
-        do {
-            withdrawState = .waitingForTOSAck
-            let request = SetExchangeTOSAccepted(exchangeBaseUrl: 
exchangeBaseUrl, etag: etag)
-            let response = try await sendRequestM(request, ASYNCDELAY)         
 // TODO: MainActor ?
-            withdrawState = .receivedTOSAck
-            return response
-        } catch {       // rethrows
-            withdrawState = .error
-            throw error
-        }
+        let request = SetExchangeTOSAccepted(exchangeBaseUrl: exchangeBaseUrl, 
etag: etag)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
     }
     @MainActor
     func sendAcceptIntWithdrawalM(_ exchangeBaseUrl: String, withdrawURL: 
String)   // M for MainActor
       async throws -> String? {
-        do {
-            withdrawState = .waitingForWithdrAck
-            let request = AcceptBankIntegratedWithdrawal(talerWithdrawUri: 
withdrawURL, exchangeBaseUrl: exchangeBaseUrl)
-            let response = try await sendRequestM(request, ASYNCDELAY)         
 // TODO: MainActor ?
-            withdrawState = .receivedWithdrAck
-            return response.confirmTransferUrl
-        } catch {       // rethrows
-            withdrawState = .error
-            throw error
-        }
+        let request = AcceptBankIntegratedWithdrawal(talerWithdrawUri: 
withdrawURL, exchangeBaseUrl: exchangeBaseUrl)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response.confirmTransferUrl
     }
     @MainActor
     func sendAcceptManualWithdrawalM(_ exchangeBaseUrl: String, amount: 
Amount, restrictAge: Int?)   // M for MainActor
-    async throws -> AcceptManualWithdrawalResult? {
-        do {
-            withdrawState = .waitingForWithdrAck
-            let request = AcceptManualWithdrawal(exchangeBaseUrl: 
exchangeBaseUrl, amount: amount, restrictAge: restrictAge)
-            let response = try await sendRequestM(request, ASYNCDELAY)         
 // TODO: MainActor ?
-            withdrawState = .receivedWithdrAck
-            return response
-        } catch {       // rethrows
-            withdrawState = .error
-            throw error
-        }
+      async throws -> AcceptManualWithdrawalResult? {
+        let request = AcceptManualWithdrawal(exchangeBaseUrl: exchangeBaseUrl, 
amount: amount, restrictAge: restrictAge)
+        let response = try await sendRequest(request, ASYNCDELAY)
+        return response
     }
 }
 
diff --git a/TalerWallet1/Views/Balances/BalancesListView.swift 
b/TalerWallet1/Views/Balances/BalancesListView.swift
index 15d0f1c..d4b46e0 100644
--- a/TalerWallet1/Views/Balances/BalancesListView.swift
+++ b/TalerWallet1/Views/Balances/BalancesListView.swift
@@ -11,13 +11,13 @@ import AVFoundation
 
 struct BalancesListView: View {
     private let symLog = SymLogV()
-    let navTitle = String(localized: "GNU Taler")       // + Wallet
-    @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
+    let navTitle: String
 
-    @ObservedObject var model: BalancesModel
-    var hamburgerAction: () -> Void
+    @State var balances: [Balance] = []
+    let model: BalancesModel?
+    let hamburgerAction: () -> Void
 
-    @State private var centsToTransfer: UInt64 = 0        // TODO: maybe 
Decimal?
+    @State private var centsToTransfer: UInt64 = 0
     @State private var showQRScanner: Bool = false
     @State private var showCameraAlert: Bool = false
 
@@ -60,22 +60,38 @@ struct BalancesListView: View {
         })
     }
 
+    private func reloadAction() async {
+        if let model {
+            balances = await model.fetchBalancesM()
+        } else {
+            balances = []
+        }
+    }
+
     var body: some View {
 #if DEBUG
         let _ = Self._printChanges()
         let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
 #endif
-        let reloadAction = model.fetchBalancesM
-        Content(symLog: symLog, model: model, centsToTransfer: 
$centsToTransfer,
-                reloadAction: reloadAction, myListStyle: $myListStyle)
+        Content(symLog: symLog, balances: $balances, centsToTransfer: 
$centsToTransfer,
+                reloadAction: reloadAction)
             .navigationTitle(navTitle)
+            .navigationBarTitleDisplayMode(.automatic)
             .navigationBarItems(leading: HamburgerButton(action: 
hamburgerAction),
                                 trailing: QRButton(action: 
checkCameraAvailable))
+            .overlay {
+                if balances.isEmpty {
+                    WalletEmptyView()
+                }
+            }
             .alert("Scanning QR-codes requires access to the camera",
                    isPresented: $showCameraAlert,
                        actions: {   openSettingsButton
                                     dismissAlertButton },
                        message: {   Text("Please allow camera access in 
settings.") })
+            .onAppear() {
+                DebugViewC.shared.setViewID(VIEW_BALANCES)
+            }
             .sheet(isPresented: $showQRScanner) {
                 let sheet = AnyView(QRSheet())
                 Sheet(sheetView: sheet)
@@ -90,18 +106,18 @@ struct BalancesListView: View {
 extension BalancesListView {
     struct Content: View {
         let symLog: SymLogV?
-        @ObservedObject var model: BalancesModel
+        @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+        @Binding var balances: [Balance]
         @Binding var centsToTransfer: UInt64
-        var reloadAction: () async -> ()
-        @Binding var myListStyle: MyListStyle
+        var reloadAction: () async -> Void
 
         var body: some View {
 #if DEBUG
             let _ = Self._printChanges()
             let _ = symLog?.vlog()       // just to get the # to compare it 
with .onAppear & onDisappear
 #endif
-            Group {
-                List (model.balances, id: \.self) { balance in
+            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)
                 }
@@ -109,18 +125,7 @@ extension BalancesListView {
                     symLog?.log("refreshing")
                     await reloadAction()      // this closure is already 
async, no need for a Task
                 }
-                .listStyle(myListStyle.style)
-                .anyView
-            }
-            .navigationBarTitleDisplayMode(.automatic)
-            .onAppear() {
-                DebugViewC.shared.setViewID(VIEW_BALANCES)
-            }
-            .overlay {
-                if model.balances.isEmpty {
-                    WalletEmptyView()
-                        
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
-                }
+                .listStyle(myListStyle.style).anyView
             }
             // automatically fetch balances after receiving 
transaction-state-transition ...
             .onNotification(.TransactionStateTransition) { notification in
diff --git a/TalerWallet1/Views/Balances/BalancesSectionView.swift 
b/TalerWallet1/Views/Balances/BalancesSectionView.swift
index d9535c1..ce14f10 100644
--- a/TalerWallet1/Views/Balances/BalancesSectionView.swift
+++ b/TalerWallet1/Views/Balances/BalancesSectionView.swift
@@ -18,37 +18,46 @@ struct BalancesSectionView: View {
     private let symLog = SymLogV()
     var balance:Balance
     @Binding var centsToTransfer: UInt64
-    @ObservedObject var model: TransactionsModel
+    var model: TransactionsModel?
 
     @State private var isShowingDetailView = false
     @State private var buttonSelected: Int? = nil
+
+    @State private var transactions: [Transaction] = []
     @State private var completedTransactions: [Transaction] = []
     @State private var pendingTransactions: [Transaction] = []
     @State private var uncompletedTransactions: [Transaction] = []
 
+    func dummyTransaction (_ transactionId: String) async throws {}
     var body: some View {
         let currency = balance.available.currencyStr
         let reloadCompleted = {
-            await model.fetchTransactions(currency: currency)
-            completedTransactions = 
TransactionsModel.completedTransactions(model.transactions)
+            if let model {
+                transactions = await model.fetchTransactionsT(currency: 
currency)
+                completedTransactions = 
TransactionsModel.completedTransactions(transactions)
+            }
         }
         let reloadPending = {
-            await model.fetchTransactions(currency: currency)
-            pendingTransactions = 
TransactionsModel.pendingTransactions(model.transactions)
+            if let model {
+                transactions = await model.fetchTransactionsT(currency: 
currency)
+                pendingTransactions = 
TransactionsModel.pendingTransactions(transactions)
+            }
         }
         let reloadUncompleted = {
-            await model.fetchTransactions(currency: currency)
-            uncompletedTransactions = 
TransactionsModel.uncompletedTransactions(model.transactions)
+            if let model {
+                transactions = await model.fetchTransactionsT(currency: 
currency)
+                uncompletedTransactions = 
TransactionsModel.uncompletedTransactions(transactions)
+            }
         }
-        let deleteAction = model.deleteTransaction
-        let abortAction = model.abortTransaction
+        let deleteAction = model?.deleteTransactionT ?? dummyTransaction
+        let abortAction = model?.abortTransactionT ?? dummyTransaction
 
 
         Section {
-            if "KUDOS" == currency && !balance.available.isZero {
-                Text("You can spend these KUDOS in the [Demo 
Shop](https://shop.demo.taler.net), or send coins to another wallet.")
-                    .multilineTextAlignment(.leading)
-            }
+//            if "KUDOS" == currency && !balance.available.isZero {
+//                Text("You can spend these KUDOS in the [Demo 
Shop](https://shop.demo.taler.net), or send coins to another wallet.")
+//                    .multilineTextAlignment(.leading)
+//            }
             HStack(spacing: 0) {
                 NavigationLink(destination: LazyView {
                     SendAmount(amountAvailable: balance.available)
@@ -56,10 +65,8 @@ struct BalancesSectionView: View {
                 ) { EmptyView() }.frame(width: 0).opacity(0).hidden()
 
                 NavigationLink(destination: LazyView {
-                    RequestPayment(model: Peer2peerModel.model(),
-                               scopeInfo: balance.scopeInfo,
-                         centsToTransfer: $centsToTransfer)
-                        
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+                    RequestPayment(scopeInfo: balance.scopeInfo,
+                             centsToTransfer: $centsToTransfer)
                   }, tag: 2, selection: $buttonSelected
                 ) { EmptyView() }.frame(width: 0).opacity(0).hidden()
 
@@ -127,9 +134,11 @@ let _ = print("button: Uncompleted Transactions: 
\(currency)")
             Text(currency)
                 .font(.title)
         } .task {
-            await model.fetchTransactions(currency: currency)
-            pendingTransactions = 
TransactionsModel.pendingTransactions(model.transactions)
-            uncompletedTransactions = 
TransactionsModel.uncompletedTransactions(model.transactions)
+            if let model {
+                transactions = await model.fetchTransactionsT(currency: 
currency)
+                pendingTransactions = 
TransactionsModel.pendingTransactions(transactions)
+                uncompletedTransactions = 
TransactionsModel.uncompletedTransactions(transactions)
+            }
         }
     } // body
 }
@@ -137,7 +146,6 @@ let _ = print("button: Uncompleted Transactions: 
\(currency)")
 #if DEBUG
 fileprivate struct BindingViewContainer : View {
     @State var centsToTransfer: UInt64 = 333
-    let model = TransactionsModel.model(currency: LONGCURRENCY)
 
     var body: some View {
         let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, 
exchangeBaseUrl: DEMOEXCHANGE, currency: LONGCURRENCY)
@@ -148,7 +156,7 @@ fileprivate struct BindingViewContainer : View {
                       requiresUserInput: false,
                               scopeInfo: scopeInfo)
         List {
-            BalancesSectionView(balance: balance, centsToTransfer: 
$centsToTransfer, model: model)
+            BalancesSectionView(balance: balance, centsToTransfer: 
$centsToTransfer, model: nil)
         }
     }
 }
diff --git a/TalerWallet1/Views/Exchange/ExchangeListView.swift 
b/TalerWallet1/Views/Exchange/ExchangeListView.swift
index e7fd4fe..96c880e 100644
--- a/TalerWallet1/Views/Exchange/ExchangeListView.swift
+++ b/TalerWallet1/Views/Exchange/ExchangeListView.swift
@@ -9,68 +9,92 @@ import SymLog
 /// This view shows the list of exchanges
 struct ExchangeListView: View {
     private let symLog = SymLogV()
-    let navTitle = String(localized: "Exchanges")
+    let navTitle: String
 
-    @ObservedObject var model: ExchangeModel
+    var model: ExchangeModel?
     var hamburgerAction: () -> Void
 
+    @State private var exchanges: [Exchange] = []
 
-    // source of truth for the value the user enters in currencyField
-    @State private var centsToTransfer: UInt64 = 0        // TODO: different 
values for different currencies
+    // source of truth for the value the user enters in currencyField for 
exchange withdrawals
+    @State private var centsToTransfer: UInt64 = 0        // TODO: different 
values for different currencies?
+
+    func reloadAction() async -> Void {
+        if let model {
+            exchanges = await model.listExchangesM()
+        } else {
+            exchanges = []
+        }
+    }
+
+    func addExchange(_ exUrl: String) -> Void {
+        Task {
+            if let model {
+                symLog.log("adding: \(exUrl)")
+                do {
+                    try await model.addExchange(url: exUrl)
+                    symLog.log("added: \(exUrl)")
+                } catch {    // TODO: error handling - couldn't add exchangeURL
+                    symLog.log("error: \(error)")
+                }
+            } else {
+                symLog.log("no model, cannot add \(exUrl)")
+            }
+        }
+    }
+
+    @State var showAlert: Bool = false
+    @State var newExchange: String = "https://exchange-age.taler.ar/";
 
     var body: some View {
 #if DEBUG
         let _ = Self._printChanges()
         let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
 #endif
-        let reloadAction = model.updateListM
-        Content(symLog: symLog, model: model,
+        let plusAction: () -> Void = {
+//        withAnimation { showAlert = true }
+            showAlert = true
+        }
+
+        //Text("Exchanges...")
+        Content(symLog: symLog,
+                exchanges: $exchanges,
                 centsToTransfer: $centsToTransfer,
                 reloadAction: reloadAction)
-            .navigationBarItems(leading: HamburgerButton(action: 
hamburgerAction))
-            .navigationTitle(navTitle)
-            .task {
-                symLog.log(".task")
-                do {
-                    try await reloadAction()
-                } catch {    // TODO: show error
-                    symLog.log(error.localizedDescription)
-                }
+        .navigationTitle(navTitle)
+        .navigationBarTitleDisplayMode(.automatic)
+        .navigationBarItems(leading: HamburgerButton(action: hamburgerAction),
+                            trailing: PlusButton(action: plusAction))
+        .overlay {
+            if exchanges.isEmpty {
+                Text("No Exchanges yet...")
             }
+        }
+        .task {
+            symLog.log(".task")
+            await reloadAction()
+        }
+        .textFieldAlert(isPresented: $showAlert, title: "Add Exchange",
+                        doneText: "Add", text: $newExchange, action: 
addExchange)
     }
 }
 // MARK: -
-struct ExchangeAmount: Identifiable {
-    let exchange: Exchange
-    let amountAvailable: Amount
-
-    var id: String {    // needed for Identifiable
-        exchange.exchangeBaseUrl
-    }
-}
+//struct ExchangeAmount: Identifiable {
+//    let exchange: Exchange
+//    let amountAvailable: Amount
+//
+//    var id: String {    // needed for Identifiable
+//        exchange.exchangeBaseUrl
+//    }
+//}
 // MARK: -
 extension ExchangeListView {
     struct Content: View {
         let symLog: SymLogV?
-        @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
-        @ObservedObject var model: ExchangeModel
+        @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+        @Binding var exchanges: [Exchange]
         @Binding var centsToTransfer: UInt64
-        var reloadAction: () async throws -> ()
-
-        @State var showAlert: Bool = false
-        @State var newExchange: String = "https://exchange-age.taler.ar/";
-
-        func addExchange(_ exUrl: String) -> Void {
-            Task {
-                do {
-                    symLog?.log("adding: \(exUrl)")
-                    try await model.add(url: exUrl)
-                    symLog?.log("added: \(exUrl)")
-                } catch {    // TODO: error handling - couldn't add exchangeURL
-                    symLog?.log("error: \(error)")
-                }
-            }
-        }
+        var reloadAction: () async -> Void
 
         func currenciesDict(_ exchanges: [Exchange]) -> [String : [Exchange]] {
             var currencies: [String : [Exchange]] = [:]
@@ -86,44 +110,29 @@ extension ExchangeListView {
             return currencies
         }
 
-        @State private var exchangeAmount: ExchangeAmount? = nil
+//        @State private var exchangeAmount: ExchangeAmount? = nil
 
         var body: some View {
-            let plusAction: () -> Void = {
-//                withAnimation { showAlert = true }
-                showAlert = true
+            let dict = currenciesDict(exchanges)
+            let sortedDict = dict.sorted{ $0.key < $1.key}
+            Group { // necessary for .backslide transition (bug in SwiftUI)
+                List(sortedDict, id: \.key) { key, value in
+                    ExchangeSectionView(currency: key, exchanges: value, 
centsToTransfer: $centsToTransfer)
+                }
+                .refreshable {
+                    symLog?.log("refreshing")
+                    await reloadAction()
+                }
+                .listStyle(myListStyle.style).anyView
             }
-            VStack {
-                if model.exchanges.isEmpty {
-                    Text("No Exchanges yet...")
-                } else {
-                    ScrollViewReader { scrollView in    // 
-                        let dict = currenciesDict(model.exchanges)
-                        let sortedDict = dict.sorted{ $0.key < $1.key}
-                        List {
-                            ForEach(sortedDict, id: \.key) { key, value in
-                                ExchangeSectionView(currency: key, exchanges: 
value, centsToTransfer: $centsToTransfer)
-                            }
-                        }
-                        .refreshable {
-                            do {
-                                symLog?.log("refreshing")
-                                try await reloadAction()
-                            } catch {    // TODO: error
-                                symLog?.log(error.localizedDescription)
-                            }
-                        }
-                        .listStyle(myListStyle.style)
-                        .anyView
-                    }
-                } // else
-            }.onAppear() {
+            .onAppear() {
                 DebugViewC.shared.setViewID(VIEW_EXCHANGES)
             }
-            .navigationBarTitleDisplayMode(.automatic)
-            .navigationBarItems(trailing: PlusButton(action: plusAction))
-            .textFieldAlert(isPresented: $showAlert, title: "Add Exchange",
-                               doneText: "Add", text: $newExchange, action: 
addExchange)
+            .onNotification(.ExchangeAdded) { notification in
+                // doesn't need to be received on main thread because we just 
reload in the background anyway
+                symLog?.log(".onNotification(.ExchangeAdded) ==> reloading 
exchanges")
+                Task { await reloadAction() }
+            }
         } // body
     }
 }
diff --git a/TalerWallet1/Views/Exchange/ExchangeSectionView.swift 
b/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
index cdcacbe..725b6b9 100644
--- a/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
+++ b/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
@@ -18,22 +18,15 @@ struct ExchangeRowView: View {
             Text(baseURL.trimURL())
 
             NavigationLink(destination: LazyView {
-                EmptyView()
+                EmptyView()     // TODO: Deposit
             }, tag: 1, selection: $buttonSelected
-            ) {
-                EmptyView()
-            }   .frame(width: 0)
-                .opacity(0)
+            ) { EmptyView() }.frame(width: 0).opacity(0)
             NavigationLink(destination: LazyView {
                 ManualWithdraw(exchange: exchange,
                                model: model,
                                centsToTransfer: $centsToTransfer)
-                
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
             }, tag: 2, selection: $buttonSelected
-            ) {
-                EmptyView()
-            }   .frame(width: 0)
-                .opacity(0)
+            ) { EmptyView() }.frame(width: 0).opacity(0)
         }.listRowSeparator(.hidden)
 
         HStack {        // buttons just set "buttonSelected" so the 
NavigationLink will trigger
@@ -80,7 +73,7 @@ struct ExchangeSectionView: View {
 // MARK: -
 #if DEBUG
 struct ExchangeRow_Container : View {
-    @State private var centsToTransfer: UInt64 = 100        // TODO: maybe 
Decimal?
+    @State private var centsToTransfer: UInt64 = 100
 
     var body: some View {
         let exchange1 = Exchange(exchangeBaseUrl: DEMO_AGE_EXCHANGE,
diff --git a/TalerWallet1/Views/Exchange/ManualWithdraw.swift 
b/TalerWallet1/Views/Exchange/ManualWithdraw.swift
index 194fd2d..752165d 100644
--- a/TalerWallet1/Views/Exchange/ManualWithdraw.swift
+++ b/TalerWallet1/Views/Exchange/ManualWithdraw.swift
@@ -11,7 +11,7 @@ struct ManualWithdraw: View {
     let navTitle = String(localized: "Withdraw Coins")
 
     var exchange: Exchange
-    @ObservedObject var model: WithdrawModel
+    var model: WithdrawModel?
     @Binding var centsToTransfer: UInt64
 
     @State var manualWithdrawalDetails: ManualWithdrawalDetails? = nil
@@ -45,7 +45,6 @@ struct ManualWithdraw: View {
 
         ScrollView {
             Text("from \(exchange.exchangeBaseUrl.trimURL())")
-                .padding(.top)
                 .font(.title3)
             CurrencyInputView(currencyField: currencyField, title: 
String(localized: "Amount to withdraw:"))
 
@@ -119,8 +118,9 @@ let _ = print(selectedAge, restrictAge)
             Spacer()
         }
         .frame(maxWidth: .infinity, alignment: .leading)
-        .navigationTitle(navTitle)
         .padding(.horizontal)
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+        .navigationTitle(navTitle)
         .onAppear {
             symLog.log("onAppear")
             DebugViewC.shared.setViewID(VIEW_WITHDRAWAL)
@@ -128,8 +128,10 @@ let _ = print(selectedAge, restrictAge)
         .task(id: centsToTransfer) {
             let amount = Amount.amountFromCents(currency, centsToTransfer)
             do {
-                manualWithdrawalDetails = try await 
model.loadWithdrawalDetailsForAmountM(exchange.exchangeBaseUrl, amount: amount)
-                agePicker.setAges(ages: 
manualWithdrawalDetails?.ageRestrictionOptions)
+                if let model {
+                    manualWithdrawalDetails = try await 
model.loadWithdrawalDetailsForAmountM(exchange.exchangeBaseUrl, amount: amount)
+//                  agePicker.setAges(ages: 
manualWithdrawalDetails?.ageRestrictionOptions)
+                }
             } catch {    // TODO: error
                 symLog.log(error.localizedDescription)
                 manualWithdrawalDetails = nil
diff --git a/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift 
b/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
index 3d698bf..2e771ac 100644
--- a/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
+++ b/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
@@ -11,7 +11,7 @@ struct ManualWithdrawDone: View {
     let navTitle = String(localized: "Wire Transfer")
 
     var exchange: Exchange
-    @ObservedObject var model: WithdrawModel
+    var model: WithdrawModel?
     var centsToTransfer: UInt64
     var restrictAge: Int?
     @State var acceptManualWithdrawalResult: AcceptManualWithdrawalResult?
@@ -37,13 +37,15 @@ struct ManualWithdrawDone: View {
             DebugViewC.shared.setViewID(VIEW_WITHDRAW_ACCEPT)
         }.task {
             do {
-                let amount = Amount.amountFromCents(exchange.currency!, 
centsToTransfer)
-                let result = try await 
model.sendAcceptManualWithdrawalM(exchange.exchangeBaseUrl,
-                                                                          
amount: amount, restrictAge: restrictAge)
+                if let model {
+                    let amount = Amount.amountFromCents(exchange.currency!, 
centsToTransfer)
+                    let result = try await 
model.sendAcceptManualWithdrawalM(exchange.exchangeBaseUrl,
+                                                                             
amount: amount, restrictAge: restrictAge)
 print(result as Any)
-                let transaction = try await 
model.getTransactionById(transactionId: result!.transactionId)
-                withdrawalTransaction = transaction
-//                acceptManualWithdrawalResult = result
+                    let transaction = try await 
model.getTransactionByIdT(result!.transactionId)
+                    withdrawalTransaction = transaction
+//                  acceptManualWithdrawalResult = result
+                }
             } catch {    // TODO: error
                 symLog.log(error.localizedDescription)
             }
@@ -57,7 +59,6 @@ struct ManualWithdrawDone_Container : View {
     @State private var centsToTransfer: UInt64 = 510
 
     var body: some View {
-        let model = WithdrawModel.model(baseURL: DEMOEXCHANGE)
         let exchange = Exchange(exchangeBaseUrl: DEMOEXCHANGE,
                                        currency: LONGCURRENCY,
                                       paytoUris: [],
@@ -66,7 +67,7 @@ struct ManualWithdrawDone_Container : View {
                           ageRestrictionOptions: [],
                                       permanent: false)
         ManualWithdrawDone(exchange: exchange,
-                              model: model,
+                              model: nil,
                     centsToTransfer: centsToTransfer)
     }
 }
diff --git a/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift 
b/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift
new file mode 100644
index 0000000..0d5366b
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift
@@ -0,0 +1,59 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import AVFoundation
+
+
+struct QRCodeDetailView: View {
+    var talerURI: String
+
+    var body: some View {
+        if talerURI.count > 10 {
+            VStack {
+                Text("Let the payee scan this QR code to receive:")
+                    .fixedSize(horizontal: false, vertical: true)
+                    .padding(.top, 30)
+                    .font(.title3)
+
+                QRGeneratorView(text: talerURI)
+//                Text(talerURI)
+
+                Text("Alternatively, copy and send this URI:")
+                    .fixedSize(horizontal: false, vertical: true)
+                    .font(.title3)
+                    .padding(.vertical)
+
+                Text(talerURI)
+                    .padding(.bottom)
+
+                CopyShare(textToCopy: talerURI, dismissFirst: true)
+                    .disabled(false)
+            }
+        }
+    }
+}
+
+
+#if DEBUG
+fileprivate struct ContentView: View {
+    @State var isOn = false
+
+    var body: some View {
+        VStack {
+
+        }
+    }
+}
+struct QRCodeDetailView_Previews: PreviewProvider {
+
+    static var previews: some View {
+//        ContentView()
+        List {
+            QRCodeDetailView(talerURI: 
"taler://pay-push/exchange.demo.taler.net/95ZG4D1AGFGZQ7CNQ1V49D3FT18HXKA6HQT4X3XME9YSJQVFQ520")
+        }
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Main/WalletEmptyView.swift 
b/TalerWallet1/Views/Main/WalletEmptyView.swift
index a6a810a..2b41daa 100644
--- a/TalerWallet1/Views/Main/WalletEmptyView.swift
+++ b/TalerWallet1/Views/Main/WalletEmptyView.swift
@@ -10,7 +10,7 @@ import SymLog
 
 struct WalletEmptyView: View {
     private let symLog = SymLogV()
-    @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     var body: some View {
         List {
@@ -27,10 +27,10 @@ struct WalletEmptyView: View {
                 Text("Just register a test account, then withdraw some coins.")
             }
         }
-        .padding(.vertical)
+//        .padding(.vertical)
         .font(.title2)
-        .listStyle(myListStyle.style)
-        .anyView
+        .listStyle(myListStyle.style).anyView
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .onAppear() {
             DebugViewC.shared.setViewID(VIEW_EMPTY)     // 10
         }
diff --git a/TalerWallet1/Views/Payment/PaymentAcceptView.swift 
b/TalerWallet1/Views/Payment/PaymentAcceptView.swift
index 75cd0c1..fe30c78 100644
--- a/TalerWallet1/Views/Payment/PaymentAcceptView.swift
+++ b/TalerWallet1/Views/Payment/PaymentAcceptView.swift
@@ -9,53 +9,24 @@ import SymLog
 
 struct PaymentAcceptView: View {
     private let symLog = SymLogV()
-    @ObservedObject var model: PaymentURIModel
 
-    let detailsForAmount: PaymentDetailsForUri
+    let detailsForUri: PaymentDetailsForUri
+    let acceptAction: () -> Void
 
     let navTitle = String(localized: "Accept Payment")
-    @State private var confirmPayResult: ConfirmPayResult?
-
-    func playSound(success: Bool) {
-//        let url = URL(fileURLWithPath: 
"/System/Library/Audio/UISounds/PaymentReceived.caf")
-        let url = URL(fileURLWithPath: 
"/System/Library/Audio/UISounds/payment_" + (success ? "success.caf"
-                                                                               
             : "failure.caf"))
-        var soundID: SystemSoundID = 0
-        AudioServicesCreateSystemSoundID(url as CFURL, &soundID)
-        print(soundID)
-        AudioServicesPlaySystemSound(soundID);
-    }
-    func acceptAction() {
-        Task {
-            do {
-                confirmPayResult = try await 
model.confirmPayM(detailsForAmount.proposalId)
-                symLog.log(confirmPayResult as Any)
-                if confirmPayResult?.type == "done" {
-                    // TODO: Show Hints that Payment was successfull
-                    playSound(success: true)
-                } else {
-                    // TODO: show error
-                    playSound(success: false)
-                }
-            } catch {    // TODO: error
-                symLog.log(error.localizedDescription)
-            }
-            dismissTop()
-        }
-    }
 
     @State private var disabled = false
     var body: some View {
         Group {
-            let raw = detailsForAmount.amountRaw
-            let effective = detailsForAmount.amountEffective
+            let raw = detailsForUri.amountRaw
+            let effective = detailsForUri.amountEffective
             let fee = try! Amount.diff(raw, effective)      // TODO: different 
currencies
             ThreeAmountsView(topTitle: "Amount to pay:",
                              topAmount: raw, fee: fee,
                              bottomTitle: "Coins to be spent:",
                              bottomAmount: effective,
                              large: true, pending: false, incoming: false,
-                             baseURL: 
detailsForAmount.contractTerms.exchanges.first?.url)
+                             baseURL: 
detailsForUri.contractTerms.exchanges.first?.url)
                 // TODO: payment: popup with all possible exchanges, check fees
             .safeAreaInset(edge: .bottom) {
                 Button(String(localized: "Accept"), action: acceptAction)
@@ -66,10 +37,42 @@ struct PaymentAcceptView: View {
             .navigationTitle(navTitle)
     }
 }
-
-//struct PaymentAccept_Previews: PreviewProvider {
-//    static var previews: some View {
-//        let model: PaymentURIModel =
-//        PaymentAcceptView(model: <#PaymentURIModel#>, detailsForAmount: 
<#PaymentDetailsForUri#>)
-//    }
-//}
+// MARK: -
+struct PaymentAccept_Previews: PreviewProvider {
+    static var previews: some View {
+        let merchant = Merchant(name: "Merchant")
+        let extra = Extra(articleName: "articleName")
+        let product = Product(description: "description")
+        let terms = ContractTerms(amount: try! Amount(fromString: LONGCURRENCY 
+ ":2.2"),
+                                  maxFee: try! Amount(fromString: LONGCURRENCY 
+ ":0.2"),
+                                  maxWireFee: try! Amount(fromString: 
LONGCURRENCY + ":0.2"),
+                                  merchant: merchant,
+                                  extra: extra,
+                                  summary: "summary",
+                                  timestamp: Timestamp.now(),
+                                  payDeadline: Timestamp.tomorrow(),
+                                  refundDeadline: Timestamp.tomorrow(),
+                                  wireTransferDeadline: Timestamp.tomorrow(),
+                                  merchantBaseURL: "merchantBaseURL",
+                                  fulfillmentURL: "fulfillmentURL",
+                                  publicReorderURL: "publicReorderURL",
+                                  auditors: [],
+                                  exchanges: [],
+                                  orderID: "orderID",
+                                  nonce: "nonce",
+                                  merchantPub: "merchantPub",
+                                  products: [product],
+                                  hWire: "hWire",
+                                  wireMethod: "wireMethod",
+                                  wireFeeAmortization: 0)
+        let details = PaymentDetailsForUri(
+            amountRaw: try! Amount(fromString: LONGCURRENCY + ":2.2"),
+            amountEffective: try! Amount(fromString: LONGCURRENCY + ":2.4"),
+            noncePriv: "noncePriv",
+            proposalId: "proposalId",
+            contractTerms: terms,
+            contractTermsHash: "termsHash"
+        )
+        PaymentAcceptView(detailsForUri: details, acceptAction: {})
+    }
+}
diff --git a/TalerWallet1/Views/Payment/PaymentURIView.swift 
b/TalerWallet1/Views/Payment/PaymentURIView.swift
index 8815be7..22048ca 100644
--- a/TalerWallet1/Views/Payment/PaymentURIView.swift
+++ b/TalerWallet1/Views/Payment/PaymentURIView.swift
@@ -3,41 +3,65 @@
  * See LICENSE.md
  */
 import SwiftUI
+import AVFoundation
 import SymLog
 
 struct PaymentURIView: View {
     private let symLog = SymLogV()
     var url: URL
-    @ObservedObject var model: PaymentURIModel
-    @State var detailsForUri: PaymentDetailsForUri?
+    var model: PaymentURIModel?
 
-    let navTitle = String(localized: "Payment")
+    @State var detailsForUri: PaymentDetailsForUri? = nil
+
+    func playSound(success: Bool) {
+//        let url = URL(fileURLWithPath: 
"/System/Library/Audio/UISounds/PaymentReceived.caf")
+        let url = URL(fileURLWithPath: 
"/System/Library/Audio/UISounds/payment_" + (success ? "success.caf"
+                                                                               
     : "failure.caf"))
+        var soundID: SystemSoundID = 0
+        AudioServicesCreateSystemSoundID(url as CFURL, &soundID)
+        print(soundID)
+        AudioServicesPlaySystemSound(soundID);
+    }
+
+    func acceptAction() {
+        Task {
+            do {
+                if let detailsForUri {
+                    if let model {
+                        let confirmPayResult = try await 
model.confirmPayM(detailsForUri.proposalId)
+                        symLog.log(confirmPayResult as Any)
+                        if confirmPayResult.type == "done" {
+                            // TODO: Show Hints that Payment was successfull
+                            playSound(success: true)
+                        } else {
+                            // TODO: show error
+                            playSound(success: false)
+                        }
+                    }
+                }
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+            }
+            dismissTop()
+        }
+    }
 
     var body: some View {
         let badURL = "Error in URL: \(url)"
         VStack {
-            if model.paymentState == nil {
-                LoadingView(backButtonHidden: false)
-            } else { switch model.paymentState {
-                case .waitingForUriDetails:
-                    let _ = symLog.vlog("waitingForUriDetails")
-                    WithdrawProgressView(message: url.host ?? badURL)
-                        .navigationTitle("Contacting Exchange")
-                case .receivedUriDetails:
-                    let _ = symLog.vlog("waitingForUser")
-                    PaymentAcceptView(model: model, detailsForAmount: 
detailsForUri!)
-                default:
-                    Text("Payment")
-                        .navigationTitle(navTitle)
-            } }
+            if let detailsForUri {
+                PaymentAcceptView(detailsForUri: detailsForUri, acceptAction: 
acceptAction)
+                    .navigationTitle("Payment")
+            } else {
+                WithdrawProgressView(message: url.host ?? badURL)
+                    .navigationTitle("Contacting Exchange")
+            }
         }.task {
-            do { // TODO: cancelled
+            do {
                 symLog.log(".task")
-                detailsForUri = try await 
model.preparePayForUriM(url.absoluteString)
-//                print(detailsForUri?.status)
-//                print(detailsForUri?.amountRaw.description)
-//                print(detailsForUri?.amountEffective.description)
-//                print(detailsForUri?.proposalId)
+                if let model {
+                    detailsForUri = try await 
model.preparePayForUriM(url.absoluteString)
+                }
             } catch {    // TODO: error
                 symLog.log(error.localizedDescription)
             }
diff --git a/TalerWallet1/Views/Peer2peer/ReceivePurpose.swift 
b/TalerWallet1/Views/Peer2peer/ReceivePurpose.swift
index 38be62b..cbbafbf 100644
--- a/TalerWallet1/Views/Peer2peer/ReceivePurpose.swift
+++ b/TalerWallet1/Views/Peer2peer/ReceivePurpose.swift
@@ -9,7 +9,7 @@ import SymLog
 struct ReceivePurpose: View {
     private let symLog = SymLogV()
     @FocusState private var isFocused: Bool
-    @StateObject var model = Peer2peerModel.model()
+    let model = Peer2peerModel.model()
     @State var peerPullCheck: CheckPeerPullCreditResponse?
 
     var scopeInfo: ScopeInfo
@@ -78,7 +78,6 @@ struct ReceivePurpose: View {
 
                 NavigationLink(destination: LazyView {
                     SendNow(amountToSend: amount, purpose: purpose, 
expireDays: expireDays, model: model)
-                    
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
                 }) {
                     Text(buttonTitle)
                         .font(buttonFont)
@@ -92,6 +91,7 @@ struct ReceivePurpose: View {
             .padding(.horizontal)
         }
         .navigationTitle("Invoice another Wallet")
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .onAppear {
             DebugViewC.shared.setSheetID(VIEW_INVOICE_PURPOSE)
             print("❗️ ReceivePurpose onAppear")
diff --git a/TalerWallet1/Views/Peer2peer/RequestPayment.swift 
b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
index 0c37649..abb0726 100644
--- a/TalerWallet1/Views/Peer2peer/RequestPayment.swift
+++ b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
@@ -10,7 +10,6 @@ struct RequestPayment: View {
     private let symLog = SymLogV()
     let navTitle = String(localized: "Request Payment")
 
-    @ObservedObject var model: Peer2peerModel
     var scopeInfo: ScopeInfo
     @Binding var centsToTransfer: UInt64
 
@@ -43,7 +42,6 @@ struct RequestPayment: View {
                     ) {
                         deactivateAction()
                     }
-                    
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
                 }) {
                     Text("title2")
                 }
@@ -56,6 +54,7 @@ struct RequestPayment: View {
         .frame(maxWidth: .infinity, alignment: .leading)
         .navigationTitle(navTitle)
         .padding(.horizontal)
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .onAppear {   // make CurrencyField show the keyboard
             DebugViewC.shared.setViewID(VIEW_INVOICE_P2P)
             print("❗️Yikes \(navTitle) onAppear")
diff --git a/TalerWallet1/Views/Peer2peer/SendAmount.swift 
b/TalerWallet1/Views/Peer2peer/SendAmount.swift
index e95aab9..fc39f8e 100644
--- a/TalerWallet1/Views/Peer2peer/SendAmount.swift
+++ b/TalerWallet1/Views/Peer2peer/SendAmount.swift
@@ -11,48 +11,61 @@ struct SendAmount: View {
     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 amountAvailable: Amount
     let buttonFont: Font = .title2
 
-    @State private var centsToTransfer: UInt64 = 0        // TODO: maybe 
Decimal?
+    @State private var centsToTransfer: UInt64 = 0
     @State private var purpose: String = ""
     @State private var expireDays: UInt = 0
 
-    var body: some View {
-        let currencyField = CurrencyField(value: $centsToTransfer, currency: 
amountAvailable.currencyStr)
+    private func fee(ppCheck: CheckPeerPushDebitResponse?) -> String {
+        do {
+            if let p2pcheck = ppCheck {
+                let fee = try p2pcheck.amountEffective - p2pcheck.amountRaw
+                return fee.readableDescription
+            }
+        } catch {}
+        return ""
+    }
 
-        VStack(alignment: .leading, spacing: 6) {
-            CurrencyInputView(currencyField: currencyField, title: "Amount to 
send:")
 
+    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 = amountAvailable.currencyStr
+        let currencyField = CurrencyField(value: $centsToTransfer, currency: 
currency)
+
+        let fee = fee(ppCheck: peerPushCheck)
+        ScrollView {
             let available = amountAvailable.readableDescription
             Text("Available: \(available)")
+                .font(.title3)
+            CurrencyInputView(currencyField: currencyField, title: "Amount to 
send:")
 
+            Text("+ \(fee) payment fee")
+                .foregroundColor(.red)
             Text("Choose where to send to:")
                 .padding(.top)
                 .font(.title3)
             HStack {
                 let kbdShown: Bool = false  // 
keyboardResponder.keyboardHeight > 0
-                let title1 = kbdShown ? "To bank" : "To a bank\naccount"
                 let title2 = kbdShown ? "To wallet" : "To another\nwallet"
                 let disabled = centsToTransfer == 0    // TODO: check 
amountAvailable
 
-                // Left button: To bank
-                NavigationLink(destination: LazyView {
-                    Text("Bank pressed")
-                }) {
-                    Text(title1)
-                }
-                .buttonStyle(TalerButtonStyle(type: .bordered))
-                .disabled(disabled)
 
                 NavigationLink(destination: LazyView {
-                    SendPurpose(amountAvailable: amountAvailable,
-                                    centsToSend: centsToTransfer,
-                                        purpose: $purpose,
-                                     expireDays: $expireDays,
-                               deactivateAction: {})
-                    
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+                    SendPurpose(model: model,
+                      amountAvailable: amountAvailable,
+                          centsToSend: centsToTransfer,
+                                  fee: fee,
+                              purpose: $purpose,
+                           expireDays: $expireDays,
+                     deactivateAction: {})
                 }) {
                     Text(title2)
                 }
@@ -63,15 +76,28 @@ struct SendAmount: View {
             Spacer()
         }
         .frame(maxWidth: .infinity, alignment: .leading)
-        .navigationTitle(navTitle)
         .padding(.horizontal)
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+        .navigationTitle(navTitle)
         .onAppear {   // make CurrencyField show the keyboard
-            DebugViewC.shared.setSheetID(SHEET_PAY_P2P)
+            symLog.log("onAppear")
+            DebugViewC.shared.setViewID(VIEW_SEND_P2P)
             print("❗️Yikes SendAmount onAppear")
         }
         .onDisappear {
             print("❗️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)
+            } catch {    // TODO: error
+                symLog.log(error.localizedDescription)
+                peerPushCheck = nil
+            }
+        }
     }
 }
 // MARK: -
diff --git a/TalerWallet1/Views/Peer2peer/SendNow.swift 
b/TalerWallet1/Views/Peer2peer/SendNow.swift
index 628d791..3ead004 100644
--- a/TalerWallet1/Views/Peer2peer/SendNow.swift
+++ b/TalerWallet1/Views/Peer2peer/SendNow.swift
@@ -14,7 +14,7 @@ struct SendNow: View {
     var purpose: String
     var expireDays: UInt
 
-    @ObservedObject var model: Peer2peerModel
+    var model: Peer2peerModel?
     @State var peerPushResponse: PeerPushResponse?
 
     @State var talerURI: String? = nil
@@ -25,23 +25,7 @@ struct SendNow: View {
                 LoadingView(backButtonHidden: true)
             } else {
                 VStack() {
-                    Text("Let the payee scan this QR code to receive:")
-                        .fixedSize(horizontal: false, vertical: true)
-                        .padding(.top, 30)
-                        .font(.title3)
-
-                    QRGeneratorView(text: talerURI!)
-
-                    Text("Alternatively, copy and send this URI:")
-                        .fixedSize(horizontal: false, vertical: true)
-                        .font(.title3)
-                        .padding(.vertical)
-
-                    Text(talerURI!)
-                        .padding(.bottom)
-
-                    CopyShare(textToCopy: talerURI!, dismissFirst: true)
-                        .disabled(false)
+                    QRCodeDetailView(talerURI: talerURI!)
 
                     Text("The QR code can also be copied and shared from 
Transactions later")
                         .fixedSize(horizontal: false, vertical: true)
@@ -59,17 +43,20 @@ struct SendNow: View {
                 .navigationBarHidden(true)          // no back button, no title
                 .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: 
.leading)
                 .padding(.horizontal)
+                
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
             }
         }
         .task {
             symLog.log(".task")
             do {
                 // generate talerURI
-                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)
-                talerURI = peerPushResponse?.talerUri
+                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)
+                    talerURI = peerPushResponse?.talerUri
+                }
             } catch {    // TODO: error
                 symLog.log(error.localizedDescription)
             }
@@ -80,8 +67,12 @@ struct SendNow: View {
 //struct SendNow_Previews: PreviewProvider {
 //    static var previews: some View {
 //        Group {
-//            SendNow()
-//            SendNow(talerURI: "taler://pay-push/exchange.demo.taler.net")
+//            SendNow(amountToSend: <#T##Amount#>,
+//                    purpose: <#T##String#>,
+//                    expireDays: <#T##UInt#>,
+//                    model: <#T##Peer2peerModel#>,
+//                    peerPushResponse: <#T##PeerPushResponse?#>,
+//                    talerURI: 
"taler://pay-push/exchange.demo.taler.net/95ZG4D1AGFGZQ7CNQ1V49D3FT18HXKA6HQT4X3XME9YSJQVFQ520")
 //        }
 //    }
 //}
diff --git a/TalerWallet1/Views/Peer2peer/SendPurpose.swift 
b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
index cabeea8..298115e 100644
--- a/TalerWallet1/Views/Peer2peer/SendPurpose.swift
+++ b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
@@ -9,11 +9,11 @@ import SymLog
 struct SendPurpose: View {
     private let symLog = SymLogV()
     @FocusState private var isFocused: Bool
-    @StateObject var model = Peer2peerModel.model()
-    @State var peerPushCheck: CheckPeerPushDebitResponse?
+    var model: Peer2peerModel?
 
     var amountAvailable: Amount
     var centsToSend: UInt64
+    var fee: String
     @Binding var purpose: String
     @Binding var expireDays: UInt
     var deactivateAction: () -> Void
@@ -26,20 +26,9 @@ struct SendPurpose: View {
         return formatter.string(for: Decimal(centsToSend) / mag) ?? ""
     }
 
-    private func fee(ppCheck: CheckPeerPushDebitResponse?) -> String {
-        do {
-            if let p2pcheck = ppCheck {
-                let fee = try p2pcheck.amountEffective - p2pcheck.amountRaw
-                return fee.readableDescription
-            }
-        } catch {}
-        return ""
-    }
-
     var body: some View {
         let amount = Amount.amountFromCents(amountAvailable.currencyStr, 
centsToSend)
 
-        let fee = fee(ppCheck: peerPushCheck)
         VStack (spacing: 6) {
             Text(amount.readableDescription)
             Text("+ \(fee) payment fee")
@@ -79,7 +68,6 @@ struct SendPurpose: View {
 
                 NavigationLink(destination: LazyView {
                     SendNow(amountToSend: amount, purpose: purpose, 
expireDays: expireDays, model: model)
-                        
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
                 }) {
                     Text(buttonTitle)
                         .font(buttonFont)
@@ -93,8 +81,9 @@ struct SendPurpose: View {
             .padding(.horizontal)
         }
         .navigationTitle("To another Wallet")
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .onAppear {
-            DebugViewC.shared.setSheetID(VIEW_SEND_PURPOSE)
+            DebugViewC.shared.setViewID(VIEW_SEND_PURPOSE)
             print("❗️ SendPurpose onAppear")
         }
         .onDisappear {
@@ -104,7 +93,7 @@ struct SendPurpose: View {
         .task {
             symLog.log(".task")
             do {
-                peerPushCheck = try await model.checkPeerPushDebitM(amount)
+//                peerPushCheck = try await model.checkPeerPushDebitM(amount)
             } catch {    // TODO: error
                 symLog.log(error.localizedDescription)
             }
@@ -113,14 +102,21 @@ struct SendPurpose: View {
 
 }
 // MARK: -
+#if DEBUG
 struct SendPurpose_Previews: PreviewProvider {
     static var previews: some View {
         @State var purpose: String = ""
         @State var expireDays: UInt = 0
-        let amount = Amount(currency: "TaLeR", integer: 10, fraction: 0)
-        SendPurpose(amountAvailable: amount, centsToSend: 5,
-                    purpose: $purpose, expireDays: $expireDays) {
+        let amount = Amount(currency: LONGCURRENCY, integer: 10, fraction: 0)
+        SendPurpose(model: nil,
+          amountAvailable: amount,
+              centsToSend: 543,
+                      fee: "0,43",
+                  purpose: $purpose,
+               expireDays: $expireDays
+        ) {
             print("deactivateAction")
         }
     }
 }
+#endif
diff --git a/TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift 
b/TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift
index 3a35398..8f29111 100644
--- a/TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift
+++ b/TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift
@@ -9,23 +9,20 @@ struct PendingOpsListView: View {
     private let symLog = SymLogV(0)
     let navTitle = String(localized: "Pending")
 
-    @ObservedObject var model: PendingModel
+    @State var pendingOperations: [PendingOperation] = []
+    var model: PendingModel
 
     var body: some View {
 #if DEBUG
         let _ = Self._printChanges()
         let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
 #endif
-        let reloadAction = model.updateM
-        Content(symLog: symLog, model: model, reloadAction: reloadAction)
+        let reloadAction = model.getPendingOperationsM
+        Content(symLog: symLog, pendingOperations: $pendingOperations, 
reloadAction: reloadAction)
             .navigationTitle(navTitle)
             .task {
                 symLog.log(".task")
-                do {
-                    try await reloadAction()
-                } catch {    // TODO: show error
-                    symLog.log(error.localizedDescription)
-                }
+                pendingOperations = await reloadAction()
             }
     }
 }
@@ -33,16 +30,15 @@ struct PendingOpsListView: View {
 extension PendingOpsListView {
     struct Content: View {
         let symLog: SymLogV?
-        @ObservedObject var model: PendingModel
-        var reloadAction: () async throws -> ()
-
+        @Binding var pendingOperations: [PendingOperation]
+        var reloadAction: () async -> [PendingOperation]
         var body: some View {
 #if DEBUG
             let _ = Self._printChanges()
             let _ = symLog?.vlog()       // just to get the # to compare it 
with .onAppear & onDisappear
 #endif
             ScrollViewReader { scrollView in
-                List(model.pendingOperations, id: \.self) { pendingOp in
+                List(pendingOperations, id: \.self) { pendingOp in
                     PendingOpView(pendingOp: pendingOp)
                 }
                 .listStyle(SidebarListStyle())
@@ -51,12 +47,8 @@ extension PendingOpsListView {
                     DebugViewC.shared.setViewID(VIEW_PENDING)
                 }
                 .refreshable {
-                    do {
-                        symLog?.log("refreshing")
-                        try await reloadAction()
-                    } catch {    // TODO: error
-                        symLog?.log(error.localizedDescription)
-                    }
+                    symLog?.log("refreshing")
+                    pendingOperations = await reloadAction()
                 }
             }
         }
diff --git a/TalerWallet1/Views/Settings/SettingsView.swift 
b/TalerWallet1/Views/Settings/SettingsView.swift
index db63b5a..94c88cf 100644
--- a/TalerWallet1/Views/Settings/SettingsView.swift
+++ b/TalerWallet1/Views/Settings/SettingsView.swift
@@ -19,11 +19,11 @@ import SymLog
 
 struct SettingsView: View {
     private let symLog = SymLogV()
-    let navTitle = String(localized: "Settings")
+    let navTitle: String
 
     @AppStorage("developerMode") var developerMode: Bool = false
     @AppStorage("developDelay") var developDelay: Bool = false
-    @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     var hamburgerAction: () -> Void
 
@@ -50,7 +50,7 @@ struct SettingsView: View {
                     Spacer()
                     Picker(selection: $myListStyle) {
                         ForEach(MyListStyle.allCases, id: \.self) {
-                            Text($0.displayName).tag($0)
+                            Text($0.displayName.capitalized).tag($0)
                                 .font(.title2)
                         }
                     } label: {}
@@ -159,8 +159,7 @@ struct SettingsView: View {
                     }
                 }
             }
-            .listStyle(myListStyle.style)
-            .anyView
+            .listStyle(myListStyle.style).anyView
         }
         .navigationTitle(navTitle)
         .navigationBarItems(leading: HamburgerButton(action: hamburgerAction))
@@ -190,11 +189,11 @@ extension Bundle {
         return "v\(releaseVersionNumber ?? "1.0.0")"
     }
 }
-
-//struct SettingsView_Previews: PreviewProvider {
-//    static var previews: some View {
-//        SettingsView {
-//
-//        }
-//    }
-//}
+// MARK: -
+#if DEBUG
+struct SettingsView_Previews: PreviewProvider {
+    static var previews: some View {
+        SettingsView(navTitle: "Settings") { }
+    }
+}
+#endif
diff --git a/TalerWallet1/Views/Sheets/URLSheet.swift 
b/TalerWallet1/Views/Sheets/URLSheet.swift
index 342447b..249c138 100644
--- a/TalerWallet1/Views/Sheets/URLSheet.swift
+++ b/TalerWallet1/Views/Sheets/URLSheet.swift
@@ -15,9 +15,8 @@ struct URLSheet: View {
 
     var body: some View {
         Group {
-            if urlCommand == UrlCommand.withdraw {
-                let model = WithdrawModel.model(baseURL: "global")      // 
TODO: get baseURL from command
-                WithdrawURIView(url: urlToOpen, model: model)
+            if urlCommand == .withdraw {
+                WithdrawURIView(url: urlToOpen)
             } else if urlCommand == UrlCommand.pay {
                 let model = PaymentURIModel.model()
                 PaymentURIView(url: urlToOpen, model: model)
diff --git a/TalerWallet1/Views/Transactions/TransactionDetailView.swift 
b/TalerWallet1/Views/Transactions/TransactionDetailView.swift
index 7de0960..c048cc0 100644
--- a/TalerWallet1/Views/Transactions/TransactionDetailView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionDetailView.swift
@@ -8,7 +8,7 @@ import SymLog
 
 struct TransactionDetailView: View {
     private let symLog = SymLogV()
-    @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     var transaction: Transaction
     var deleteAction: ((_ transactionId: String) async throws -> Void)?
@@ -58,8 +58,7 @@ print(transition.newTxState.major)
 //                    ResumeButton(common: common, resumeAction: resumeAction)
 //                } }
             }
-            .listStyle(myListStyle.style)
-            .anyView
+            .listStyle(myListStyle.style).anyView
         }
         .navigationTitle(navTitle)
         .onAppear {
@@ -125,6 +124,7 @@ print(transition.newTxState.major)
             }
         }
     }
+
     struct QRCodeDetails: View {
         var transaction : Transaction
         var body: some View {
diff --git a/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift 
b/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift
index 1721663..eb7479f 100644
--- a/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift
@@ -10,7 +10,7 @@ import SymLog
 
 struct TransactionsEmptyView: View {
     private let symLog = SymLogV()
-    @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     let currency: String
 
@@ -22,8 +22,8 @@ struct TransactionsEmptyView: View {
         }
         .padding(.vertical)
         .font(.title2)
-        .listStyle(myListStyle.style)
-        .anyView
+        .listStyle(myListStyle.style).anyView
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .onAppear() {
             DebugViewC.shared.setViewID(VIEW_EMPTY)     // 10
         }
diff --git a/TalerWallet1/Views/Transactions/TransactionsListView.swift 
b/TalerWallet1/Views/Transactions/TransactionsListView.swift
index e2e5ad1..0404d62 100644
--- a/TalerWallet1/Views/Transactions/TransactionsListView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionsListView.swift
@@ -7,7 +7,7 @@ import SymLog
 
 struct TransactionsListView: View {
     private let symLog = SymLogV()
-    @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
     let navTitle: String
 
     let currency: String
@@ -22,11 +22,13 @@ struct TransactionsListView: View {
         let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
 #endif
         let count = transactions.count
+        // TODO: Unlock the power of grammatical agreement
 //        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)
             .navigationTitle(title)
+            .navigationBarTitleDisplayMode(.large)      // .inline
             .onAppear {
                 DebugViewC.shared.setViewID(VIEW_TRANSACTIONLIST)
             }
@@ -94,10 +96,10 @@ extension TransactionsListView {
 //                    .onDelete(perform: removeItems)     // delete this row 
from the list
                 }
                 .refreshable {
+                    symLog?.log("refreshing")
                     await reloadAction()
                 }
-                .listStyle(myListStyle.style)
-                .anyView
+                .listStyle(myListStyle.style).anyView
                 .onAppear {
                     upAction = { withAnimation { scrollView.scrollTo(0) }}
                     downAction = { withAnimation { 
scrollView.scrollTo(transactions.count - 1) }}
@@ -106,7 +108,6 @@ extension TransactionsListView {
                 .overlay {
                     if transactions.isEmpty {
                         TransactionsEmptyView(currency: currency)
-                            
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
                     }
                 }
             }
@@ -114,7 +115,6 @@ extension TransactionsListView {
                 ArrowUpButton(action: upAction)
                 ArrowDownButton(action: downAction)
             })
-            .navigationBarTitleDisplayMode(.large)      // .inline
         }
     }
 }
diff --git a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptDone.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptDone.swift
new file mode 100644
index 0000000..9e9ff1e
--- /dev/null
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptDone.swift
@@ -0,0 +1,61 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct WithdrawAcceptDone: View {
+    private let symLog = SymLogV()
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+
+    let confirmTransferUrl: String?
+
+    let navTitle = String(localized: "Confirm with Bank")
+
+    var body: some View {
+        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
+                }
+            }
+            .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() {
+            DebugViewC.shared.setSheetID(SHEET_WITHDRAW_CONFIRM)
+        }
+    }
+}
+// MARK: -
+struct WithdrawAcceptDone_Previews: PreviewProvider {
+    static var previews: some View {
+        WithdrawAcceptDone(confirmTransferUrl: DEMOBANK)
+    }
+}
diff --git a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift
index 72ce825..630f33d 100644
--- a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptView.swift
@@ -9,20 +9,27 @@ import SymLog
 struct WithdrawAcceptView: View {
     private let symLog = SymLogV()
     let url: URL
-    @ObservedObject var model: WithdrawModel
+    var model: WithdrawModel?
 
     let navTitle = String(localized: "Accept Withdrawal")
     let detailsForAmount: ManualWithdrawalDetails
     let baseURL: String
 
+    @State private var buttonSelected: Int? = nil
+    @State private var confirmTransferUrl: String? = nil
+
     func acceptAction() -> () {
         Task {
             do {
-                let confirmTransferUrl = try await 
model.sendAcceptIntWithdrawalM(baseURL, withdrawURL: url.absoluteString)
-                symLog.log(confirmTransferUrl as Any)
-                // TODO: Show Hints that User should Confirm on bank website
-                // update balances to show pending withdrawal
-                await BalancesModel.model(currency: "*").fetchBalancesM()
+                if let model {
+                    if let transferUrl = try await 
model.sendAcceptIntWithdrawalM(baseURL, withdrawURL: url.absoluteString) {
+                        symLog.log(transferUrl)
+                        confirmTransferUrl = transferUrl
+                        buttonSelected = 1      // trigger NavigationLink
+                    } else {
+                        // TODO: error sendAcceptIntWithdrawal failed
+                    }
+                }
             } catch {    // TODO: error
                 symLog.log(error.localizedDescription)
             }
@@ -30,43 +37,56 @@ struct WithdrawAcceptView: View {
     }
 
     var body: some View {
-        Group {
-            let currState = model.withdrawState
-            switch currState {
-                case .receivedAmountDetails, .receivedTOS, .receivedTOSAck:
-                    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)
-
-                    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)
-                    .safeAreaInset(edge: .bottom) {
-                        Button(String(localized: "Accept"), action: 
acceptAction)
-                            .buttonStyle(TalerButtonStyle(type: .prominent))
-                            .padding(.horizontal)
-                    }
-                case .waitingForWithdrAck, .receivedWithdrAck:
-                    // TODO: SHEET_WITHDRAW_CONFIRM
-                    Text("waiting for bank confirmation")
-                        .navigationTitle("Confirm with Bank")
-                        .onAppear() {
-                            
DebugViewC.shared.setSheetID(SHEET_WITHDRAW_CONFIRM)
-                        }
+        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)
 
-                default:
-                    let _ = symLog.vlog(currState as Any)
-                    ErrorView(errortext: "unknown state")       // TODO: Error
+            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)
             }
         }
+        .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
+//            }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, 
maxHeight: .infinity)
+//                
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+//        }
         .onAppear() {
             DebugViewC.shared.setSheetID(SHEET_WITHDRAW_ACCEPT)
         }
     }
 }
+// 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)
+    }
+}
diff --git 
a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift
index c872021..3599a9b 100644
--- a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawProgressView.swift
@@ -8,7 +8,7 @@ struct WithdrawProgressView: View {
     let message: String
 
     var body: some View {
-        VStack {
+        Form {
             Spacer()
             ProgressView()
             Spacer()
@@ -17,6 +17,7 @@ struct WithdrawProgressView: View {
             Spacer()
             Spacer()
         }
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
     }
 }
 
diff --git a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawTOSView.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawTOSView.swift
index 3d24025..4df29c3 100644
--- a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawTOSView.swift
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawTOSView.swift
@@ -7,12 +7,12 @@ import SymLog
 
 struct WithdrawTOSView: View {
     private let symLog = SymLogV()
-    @AppStorage("listStyle") var myListStyle = MyListStyle.automatic
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     let navTitle = String(localized: "Terms of Service")
 
     var exchangeBaseUrl: String
-    @ObservedObject var model: WithdrawModel
+    var model: WithdrawModel?
 
     @State var exchangeTOS: ExchangeTermsOfService?
     let viewID: Int         // either VIEW_WITHDRAW_TOS or SHEET_WITHDRAW_TOS
@@ -22,29 +22,29 @@ struct WithdrawTOSView: View {
 
     var body: some View {
         VStack {
-            switch model.withdrawState {
-                case .receivedAmountDetails, .waitingForTOS:
-                    WithdrawProgressView(message: exchangeBaseUrl.trimURL())
-                        .navigationTitle("Loading " + navTitle)
-                case .receivedTOS, .waitingForTOSAck, .receivedTOSAck:
-                    Content(symLog: symLog, exchangeTOS: exchangeTOS, 
myListStyle: $myListStyle) {
-                        Task {
-                            do {
-                                _ = try await 
model.setExchangeTOSAcceptedM(exchangeBaseUrl, etag: exchangeTOS!.currentEtag)
-                                if acceptAction != nil {
-                                    acceptAction!()
-                                } else {
-                                    
self.presentationMode.wrappedValue.dismiss()
-                                }
-                            } catch {    // TODO: Show Error
-                                symLog.log(error.localizedDescription)
+            Content(symLog: symLog, exchangeTOS: exchangeTOS, myListStyle: 
$myListStyle) {
+                Task {
+                    do {
+                        if let model {
+                            _ = try await 
model.setExchangeTOSAcceptedM(exchangeBaseUrl, etag: exchangeTOS!.currentEtag)
+                            if acceptAction != nil {
+                                acceptAction!()
+                            } else {
+                                self.presentationMode.wrappedValue.dismiss()
                             }
                         }
+                    } catch {    // TODO: Show Error
+                        symLog.log(error.localizedDescription)
                     }
-                        .navigationBarTitleDisplayMode(.large)      // .inline
-                        .navigationTitle(navTitle)
-                default:
-                    ErrorView(errortext: "unknown state")   // TODO: ???
+                }
+            }
+            .navigationBarTitleDisplayMode(.large)      // .inline
+            .navigationTitle(navTitle)
+            .overlay {
+                if exchangeTOS == nil {
+                    WithdrawProgressView(message: exchangeBaseUrl.trimURL())
+                        .navigationTitle("Loading " + navTitle)
+                }
             }
         }.onAppear() {
             if viewID > SHEET_WITHDRAWAL {
@@ -54,8 +54,10 @@ struct WithdrawTOSView: View {
             }
         }.task {
             do {
-                let someTOS = try await 
model.loadExchangeTermsOfServiceM(exchangeBaseUrl)
-                exchangeTOS = someTOS
+                if let model {
+                    let someTOS = try await 
model.loadExchangeTermsOfServiceM(exchangeBaseUrl)
+                    exchangeTOS = someTOS
+                }
             } catch {    // TODO: error
                 symLog.log(error.localizedDescription)
             }
@@ -77,12 +79,11 @@ extension WithdrawTOSView {
                 List (components, id: \.self) { term in
                     Text(term)
                 }.safeAreaInset(edge: .bottom) {
-                    Button(String(localized: "Accept"), action: acceptAction)
+                    Button(String(localized: "Accept ToS"), action: 
acceptAction)
                         .buttonStyle(TalerButtonStyle(type: .prominent))
                         .padding(.horizontal)
                 }
-                .listStyle(myListStyle.style)
-                .anyView
+                .listStyle(myListStyle.style).anyView
             } else {
                 ErrorView(errortext: String(localized: "unknown ToS"))     // 
TODO: ???
             }
diff --git a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift 
b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift
index 246daaa..5a51dfb 100644
--- a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift
+++ b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift
@@ -5,68 +5,68 @@
 import SwiftUI
 import SymLog
 
+// Will be called either by the user scanning a QR code or tapping the 
provided link, both from the bank's website
+// we show the user the withdrawal details - but first the ToS must be accepted
+// 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()
+    // the URL from the bank website
     var url: URL
-    @ObservedObject var model: WithdrawModel
 
-//    @State var withdrawUriInfo: WithdrawUriInfoResponse?
-    @State var exchangeBaseUrl: String?
+    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
 
     var body: some View {
         let badURL = "Error in URL: \(url)"
         VStack {
-            let currState = model.withdrawState
-            if currState == nil {
-                LoadingView(backButtonHidden: false)
-            } else {
-                let _ = symLog.vlog(currState as Any)
-                switch currState {
-                    case .waitingForUriDetails, .receivedUriDetails:
-                        WithdrawProgressView(message: url.host ?? badURL)
-                            .navigationTitle("Contacting Exchange")
-                    case .waitingForAmountDetails:
-                        WithdrawProgressView(message: 
exchangeBaseUrl?.trimURL() ?? badURL)
-                            .navigationTitle("Found Exchange")
-                    default:
-                    // .receivedAmountDetails, .waitingForTOS, .receivedTOS, 
.waitingForTOSAck, .receivedTOSAck
-                    // waitingForWithdrAck, receivedWithdrAck
-                        if !didAcceptTOS {
-                            // user must accept ToS first
-                            WithdrawTOSView(exchangeBaseUrl: exchangeBaseUrl!,
-                                                      model: model,
-                                                     viewID: 
SHEET_WITHDRAW_TOS) {
-                                didAcceptTOS = true
-                            }
-                        } else {
-                            // show Amount details and let user accept
-                            WithdrawAcceptView(url: url, model: model,
-                                              detailsForAmount: 
manualWithdrawalDetails!,
-                                                       baseURL: 
exchangeBaseUrl!)
-                        }
+            if !didAcceptTOS {      // user must accept ToS first
+                WithdrawTOSView(exchangeBaseUrl: exchangeBaseUrl,
+                                          model: model,
+                                         viewID: SHEET_WITHDRAW_TOS) {
+                    didAcceptTOS = true
                 }
+            } else {                // show Amount details and let user accept
+                WithdrawAcceptView(url: url, model: model,
+                                   detailsForAmount: manualWithdrawalDetails!,
+                                   baseURL: exchangeBaseUrl)
             }
-        }.onAppear() {
+        }
+        .overlay {
+            if !exchangeBaseUrl.hasPrefix(HTTPS) {
+                WithdrawProgressView(message: url.host ?? badURL)
+                    .navigationTitle("Contacting Exchange")
+            } else if manualWithdrawalDetails == nil {
+                WithdrawProgressView(message: exchangeBaseUrl.trimURL())
+                    .navigationTitle("Found Exchange")
+            }
+        }
+        .onAppear() {
             DebugViewC.shared.setSheetID(SHEET_WITHDRAWAL)
-        }.task {
+        }
+        .task {
             do { // TODO: cancelled
                 symLog.log(".task")
                 let withdrawUriInfo = try await 
model.loadWithdrawalDetailsForUriM(url.absoluteString)
                 let amount = withdrawUriInfo.amount
                 if let baseURL = withdrawUriInfo.defaultExchangeBaseUrl {
                     exchangeBaseUrl = baseURL
-                } else {
-                    exchangeBaseUrl = 
withdrawUriInfo.possibleExchanges.first?.exchangeBaseUrl
+                } else if let first = withdrawUriInfo.possibleExchanges.first {
+                    exchangeBaseUrl = first.exchangeBaseUrl
                 }
-                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
+                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!
                 }
             } catch {    // TODO: error
                 symLog.log(error.localizedDescription)

-- 
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]