gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-ios] branch master updated (8475ec0 -> 9453c61)


From: gnunet
Subject: [taler-taler-ios] branch master updated (8475ec0 -> 9453c61)
Date: Thu, 29 Feb 2024 17:19:07 +0100

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

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

    from 8475ec0  Bump version to 0.9.4 (7)
     new f7f015c  Accessibility: don't automatically show keyboard when 
VoiceOver is on
     new cce5bdd  Prepare HTML ToS
     new d6a3539  fake Swiss Francs
     new 0303ad6  fix JSON keys for ExchangeUpdateStatus
     new 4e96de0  cleanup
     new dbb3a3e  ViewIDs
     new 7927b34  Error, Status
     new df19d03  Use HTTP lib of quickjs-tart, still with curl
     new 0ce3022  Helpers
     new d6e2f84  Debugging
     new f39b4ca  Native Networking via URLSession.dataTask
     new db603ba  Bump version to 0.9.4 (10)
     new 23f77fb  Preparation for Builtin
     new c4dfcb2  Empty Exchanges
     new 15d533a  Empty Wallet
     new 9453c61  Bump version to 0.9.4 (11)

The 16 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 TalerWallet.xcodeproj/project.pbxproj              |  26 +++-
 TalerWallet1/Backend/WalletBackendError.swift      |   9 +-
 TalerWallet1/Backend/WalletCore.swift              |  11 +-
 TalerWallet1/Controllers/Controller.swift          |   3 +-
 TalerWallet1/Controllers/DebugViewC.swift          |  20 +--
 TalerWallet1/Controllers/PublicConstants.swift     |   1 +
 .../{Binding+onChange.swift => Atomic.swift}       |  63 ++++++---
 TalerWallet1/Helper/CStringArray.swift             |  74 ++++++++++
 TalerWallet1/Helper/CurrencySpecification.swift    |  12 ++
 TalerWallet1/Model/Model+Exchange.swift            |   9 +-
 TalerWallet1/Model/Model+Withdraw.swift            |  32 +++--
 TalerWallet1/Model/WalletModel.swift               |   7 +
 TalerWallet1/Quickjs/QuickDataTask.swift           | 156 +++++++++++++++++++++
 TalerWallet1/Quickjs/quickjs.swift                 |  92 ++++++++----
 TalerWallet1/Views/Balances/BalanceRowView.swift   |  15 +-
 TalerWallet1/Views/Balances/TwoRowButtons.swift    |  39 +++---
 TalerWallet1/Views/Banking/DepositAmountV.swift    |   2 +-
 TalerWallet1/Views/Banking/DepositIbanV.swift      |  12 +-
 TalerWallet1/Views/Banking/ExchangeListView.swift  |  22 ++-
 TalerWallet1/Views/Banking/ExchangeRowView.swift   |  22 +--
 TalerWallet1/Views/Banking/ManualWithdraw.swift    |  22 ++-
 TalerWallet1/Views/HelperViews/AmountInputV.swift  |   4 +-
 TalerWallet1/Views/HelperViews/Buttons.swift       |   4 +-
 .../Views/HelperViews/CurrencyInputView.swift      |   2 +-
 TalerWallet1/Views/HelperViews/SelectDays.swift    |   9 +-
 TalerWallet1/Views/HelperViews/SubjectInputV.swift |  12 +-
 TalerWallet1/Views/Main/WalletEmptyView.swift      |  18 ++-
 TalerWallet1/Views/Peer2peer/P2PSubjectV.swift     |  14 +-
 TalerWallet1/Views/Peer2peer/RequestPayment.swift  |   2 +-
 TalerWallet1/Views/Settings/AboutView.swift        |   7 +-
 .../WithdrawBankIntegrated/WithdrawTOSView.swift   |   2 +-
 .../WithdrawBankIntegrated/WithdrawURIView.swift   |   9 +-
 TalerWallet1/Views/Sheets/WithdrawExchangeV.swift  |   7 +-
 .../Views/Transactions/ThreeAmountsV.swift         |   1 +
 .../Views/Transactions/TransactionSummaryV.swift   |  14 +-
 .../Views/Transactions/TransactionsEmptyView.swift |   7 +-
 TestFlight/WhatToTest.en-US.txt                    |  23 +++
 37 files changed, 600 insertions(+), 184 deletions(-)
 copy TalerWallet1/Helper/{Binding+onChange.swift => Atomic.swift} (55%)
 create mode 100644 TalerWallet1/Helper/CStringArray.swift
 create mode 100644 TalerWallet1/Quickjs/QuickDataTask.swift

diff --git a/TalerWallet.xcodeproj/project.pbxproj 
b/TalerWallet.xcodeproj/project.pbxproj
index a2faa33..31b80f7 100644
--- a/TalerWallet.xcodeproj/project.pbxproj
+++ b/TalerWallet.xcodeproj/project.pbxproj
@@ -240,6 +240,12 @@
                4ECB62802A0BA6DF004ABBB7 /* Model+P2P.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4ECB627F2A0BA6DF004ABBB7 /* Model+P2P.swift */; 
};
                4ECB62822A0BB01D004ABBB7 /* SelectDays.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */; 
};
                4ED2F94B2A278F5100453B40 /* ThreeAmountsV.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4ED2F94A2A278F5100453B40 /* ThreeAmountsV.swift 
*/; };
+               4ED80E882B8F5FB8008BD576 /* CStringArray.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4ED80E872B8F5FB8008BD576 /* CStringArray.swift 
*/; };
+               4ED80E892B8F5FB8008BD576 /* CStringArray.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4ED80E872B8F5FB8008BD576 /* CStringArray.swift 
*/; };
+               4ED80E8B2B8F60E7008BD576 /* Atomic.swift in Sources */ = {isa = 
PBXBuildFile; fileRef = 4ED80E8A2B8F60E7008BD576 /* Atomic.swift */; };
+               4ED80E8C2B8F60E7008BD576 /* Atomic.swift in Sources */ = {isa = 
PBXBuildFile; fileRef = 4ED80E8A2B8F60E7008BD576 /* Atomic.swift */; };
+               4ED80E8E2B8F6212008BD576 /* QuickDataTask.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4ED80E8D2B8F6212008BD576 /* QuickDataTask.swift 
*/; };
+               4ED80E8F2B8F6212008BD576 /* QuickDataTask.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4ED80E8D2B8F6212008BD576 /* QuickDataTask.swift 
*/; };
                4EDBDCD92AB787CB00925C02 /* CallStack.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EDBDCD82AB787CB00925C02 /* CallStack.swift */; 
};
                4EDBDCDA2AB787CB00925C02 /* CallStack.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EDBDCD82AB787CB00925C02 /* CallStack.swift */; 
};
                4EE171882B49635800BF9FF5 /* MarkdownUI in Frameworks */ = {isa 
= PBXBuildFile; productRef = 4EE171872B49635800BF9FF5 /* MarkdownUI */; };
@@ -425,6 +431,9 @@
                4ECB627F2A0BA6DF004ABBB7 /* Model+P2P.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "Model+P2P.swift"; sourceTree = "<group>"; };
                4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SelectDays.swift; sourceTree = "<group>"; };
                4ED2F94A2A278F5100453B40 /* ThreeAmountsV.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= ThreeAmountsV.swift; sourceTree = "<group>"; };
+               4ED80E872B8F5FB8008BD576 /* CStringArray.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= CStringArray.swift; sourceTree = "<group>"; };
+               4ED80E8A2B8F60E7008BD576 /* Atomic.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= Atomic.swift; sourceTree = "<group>"; };
+               4ED80E8D2B8F6212008BD576 /* QuickDataTask.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= QuickDataTask.swift; sourceTree = "<group>"; };
                4EDBDCD82AB787CB00925C02 /* CallStack.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= CallStack.swift; sourceTree = "<group>"; };
                4EEC118C2B83DE4700146CFF /* AmountInputV.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= AmountInputV.swift; sourceTree = "<group>"; };
                4EEC11922B83FB7A00146CFF /* SubjectInputV.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SubjectInputV.swift; sourceTree = "<group>"; };
@@ -600,7 +609,9 @@
                        children = (
                                4E363CBD2A23CB2100D7E98C /* 
AnyTransition+backslide.swift */,
                                4E3327B92AD1635100BF5AD6 /* 
AsyncSemaphore.swift */,
+                               4ED80E8A2B8F60E7008BD576 /* Atomic.swift */,
                                4EDBDCD82AB787CB00925C02 /* CallStack.swift */,
+                               4ED80E872B8F5FB8008BD576 /* CStringArray.swift 
*/,
                                4E16E12229F3BB99008B9C86 /* 
CurrencySpecification.swift */,
                                4EAD117529F672FA008EDD0B /* 
KeyboardResponder.swift */,
                                4E363CC12A2621C200D7E98C /* 
LocalizedAlertError.swift */,
@@ -626,6 +637,7 @@
                        isa = PBXGroup;
                        children = (
                                4EB0950D2989CB9A0043A8A1 /* quickjs.swift */,
+                               4ED80E8D2B8F6212008BD576 /* QuickDataTask.swift 
*/,
                        );
                        path = Quickjs;
                        sourceTree = "<group>";
@@ -1091,6 +1103,7 @@
                                4E3EAE2B2A990778009F1BE8 /* LoadingView.swift 
in Sources */,
                                4E3EAE8C2AA0933C009F1BE8 /* Font+Taler.swift in 
Sources */,
                                4E3327BA2AD1635100BF5AD6 /* 
AsyncSemaphore.swift in Sources */,
+                               4ED80E8E2B8F6212008BD576 /* QuickDataTask.swift 
in Sources */,
                                4E3EAE2C2A990778009F1BE8 /* 
ManualWithdraw.swift in Sources */,
                                4E3EAE2D2A990778009F1BE8 /* 
Model+Exchange.swift in Sources */,
                                4EBC0F012B7B3CD600C0CB19 /* DepositIbanV.swift 
in Sources */,
@@ -1162,6 +1175,7 @@
                                4E3EAE692A990778009F1BE8 /* URLSheet.swift in 
Sources */,
                                4E3EAE6A2A990778009F1BE8 /* ThreeAmountsV.swift 
in Sources */,
                                4E3EAE6B2A990778009F1BE8 /* 
Model+Withdraw.swift in Sources */,
+                               4ED80E882B8F5FB8008BD576 /* CStringArray.swift 
in Sources */,
                                4E3EAE6C2A990778009F1BE8 /* 
ExchangeSectionView.swift in Sources */,
                                4E3EAE6D2A990778009F1BE8 /* P2PSubjectV.swift 
in Sources */,
                                4E6EF56B2B65A33300AF252A /* PaymentDone.swift 
in Sources */,
@@ -1169,6 +1183,7 @@
                                4E3EAE6F2A990778009F1BE8 /* TalerStrings.swift 
in Sources */,
                                4E3EAE702A990778009F1BE8 /* 
CurrencyInputView.swift in Sources */,
                                4E3EAE712A990778009F1BE8 /* URL+id+iban.swift 
in Sources */,
+                               4ED80E8B2B8F60E7008BD576 /* Atomic.swift in 
Sources */,
                                4EEC3A712B2285A200D05F9D /* 
WithdrawExchangeV.swift in Sources */,
                                4EDBDCD92AB787CB00925C02 /* CallStack.swift in 
Sources */,
                                4E3EAE722A990778009F1BE8 /* 
RequestPayment.swift in Sources */,
@@ -1204,6 +1219,7 @@
                                4EB0956D2989CBFE0043A8A1 /* LoadingView.swift 
in Sources */,
                                4E3EAE8D2AA0933C009F1BE8 /* Font+Taler.swift in 
Sources */,
                                4E3327BB2AD1635100BF5AD6 /* 
AsyncSemaphore.swift in Sources */,
+                               4ED80E8F2B8F6212008BD576 /* QuickDataTask.swift 
in Sources */,
                                4E50B3502A1BEE8000F9F01C /* 
ManualWithdraw.swift in Sources */,
                                4E3B4BC92A42BC4800CC88B8 /* 
Model+Exchange.swift in Sources */,
                                4EBC0F022B7B3CD600C0CB19 /* DepositIbanV.swift 
in Sources */,
@@ -1275,6 +1291,7 @@
                                4EB0955A2989CBFE0043A8A1 /* URLSheet.swift in 
Sources */,
                                4ED2F94B2A278F5100453B40 /* ThreeAmountsV.swift 
in Sources */,
                                4EB095622989CBFE0043A8A1 /* 
Model+Withdraw.swift in Sources */,
+                               4ED80E892B8F5FB8008BD576 /* CStringArray.swift 
in Sources */,
                                4EC90C782A1B528B0071DC58 /* 
ExchangeSectionView.swift in Sources */,
                                4E7940DE29FC307C00A9AEA1 /* P2PSubjectV.swift 
in Sources */,
                                4E6EF56C2B65A33300AF252A /* PaymentDone.swift 
in Sources */,
@@ -1282,6 +1299,7 @@
                                4EB0950A2989CB7C0043A8A1 /* TalerStrings.swift 
in Sources */,
                                4EA551252A2C923600FEC9A8 /* 
CurrencyInputView.swift in Sources */,
                                4E363CBC2A237E0900D7E98C /* URL+id+iban.swift 
in Sources */,
+                               4ED80E8C2B8F60E7008BD576 /* Atomic.swift in 
Sources */,
                                4EEC3A722B2285A200D05F9D /* 
WithdrawExchangeV.swift in Sources */,
                                4EDBDCDA2AB787CB00925C02 /* CallStack.swift in 
Sources */,
                                4E9320452A1645B600A87B0E /* 
RequestPayment.swift in Sources */,
@@ -1335,7 +1353,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 7;
+                               CURRENT_PROJECT_VERSION = 11;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1377,7 +1395,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 7;
+                               CURRENT_PROJECT_VERSION = 11;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1536,7 +1554,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 7;
+                               CURRENT_PROJECT_VERSION = 11;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1578,7 +1596,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 7;
+                               CURRENT_PROJECT_VERSION = 11;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
diff --git a/TalerWallet1/Backend/WalletBackendError.swift 
b/TalerWallet1/Backend/WalletBackendError.swift
index 802d027..9b4cea4 100644
--- a/TalerWallet1/Backend/WalletBackendError.swift
+++ b/TalerWallet1/Backend/WalletBackendError.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import Foundation
 
 /// Errors for `WalletBackend`.
@@ -40,4 +43,8 @@ extension WalletCore {
     static func parseFailureError() -> WalletBackendResponseError {
         return WalletBackendResponseError(talerErrorCode: -3, talerErrorHint: 
"Could not parse error detail.", message: "")
     }
+
+    static func walletError() -> WalletBackendResponseError {
+        return WalletBackendResponseError(talerErrorCode: -4, talerErrorHint: 
"Error detail.", message: "")
+    }
 }
diff --git a/TalerWallet1/Backend/WalletCore.swift 
b/TalerWallet1/Backend/WalletCore.swift
index d251897..6e50295 100644
--- a/TalerWallet1/Backend/WalletCore.swift
+++ b/TalerWallet1/Backend/WalletCore.swift
@@ -102,20 +102,21 @@ extension WalletCore {
         if let walletError = decoded.error {            // wallet-core sent an 
error message
             do {
                 let jsonData = try JSONEncoder().encode(walletError)
+                logger.error("wallet-core sent back an error for request 
\(requestId, privacy: .public)")
+                symLog.log("id:\(requestId)  \(walletError)")
+                // TODO: decode jsonData to WalletBackendResponseError - or 
HTTPError
+                completion(requestId, timeSent, jsonData, 
WalletCore.walletError())
             } catch {        // JSON encoding of response.result failed / 
should never happen
                 symLog.log(decoded)
                 logger.error("cannot encode wallet-core Error")
                 // TODO: show error alert
                 completion(requestId, timeSent, nil, 
WalletCore.parseFailureError())
             }
-            // TODO: decode jsonData to WalletBackendResponseError - or 
HTTPError
-            logger.error("wallet-core sent back an error for request 
\(requestId, privacy: .public)")
-//            completion(requestId, timeSent, nil, walletError)
-            completion(requestId, timeSent, nil, 
WalletCore.parseFailureError())
-        } else {                                        // JSON decoding of 
error message failed
+        } else {             // JSON decoding of error message failed
             completion(requestId, timeSent, nil, 
WalletCore.parseFailureError())
         }
     }
+
     private func handleResponse(_ decoded: ResponseOrNotification) throws {
         guard let requestId = decoded.id else {
             logger.error("didn't find requestId in response")
diff --git a/TalerWallet1/Controllers/Controller.swift 
b/TalerWallet1/Controllers/Controller.swift
index cc8d3f0..51dd5a3 100644
--- a/TalerWallet1/Controllers/Controller.swift
+++ b/TalerWallet1/Controllers/Controller.swift
@@ -69,7 +69,8 @@ class Controller: ObservableObject {
     }
 
     func info(for currency: String) -> CurrencyInfo? {
-//        return CurrencyInfo.euro()            // FAKE EURO instead of the 
real Currency
+//        return CurrencyInfo.euro()              // Fake EUR instead of the 
real Currency
+//        return CurrencyInfo.francs()            // Fake CHF instead of the 
real Currency
         for info in currencyInfos {
             if info.scope.currency == currency {
                 return info
diff --git a/TalerWallet1/Controllers/DebugViewC.swift 
b/TalerWallet1/Controllers/DebugViewC.swift
index 0f05cab..8cd8108 100644
--- a/TalerWallet1/Controllers/DebugViewC.swift
+++ b/TalerWallet1/Controllers/DebugViewC.swift
@@ -25,23 +25,24 @@ import os.log
 
 // Numbering Scheme for Views
 // MARK: - Main View
-public let VIEW_EMPTY = 10                                          // 10 
WalletEmptyView
-public let VIEW_BALANCES = VIEW_EMPTY + 1                           // 11 
BalancesListView
-public let VIEW_EXCHANGES = VIEW_BALANCES + 1                       // 12 
ExchangeListView
-public let VIEW_SETTINGS = VIEW_EXCHANGES + 1                       // 13 
SettingsView
+public let VIEW_EMPTY_WALLET = 10                                   // 10 
WalletEmptyView
+public let VIEW_BALANCES = VIEW_EMPTY_WALLET + 1                    // 11 
BalancesListView
+public let VIEW_BANKING = VIEW_BALANCES + 1                         // 12 
ExchangeListView
+public let VIEW_SETTINGS = VIEW_BANKING + 1                         // 13 
SettingsView
 public let VIEW_ABOUT = VIEW_SETTINGS + 1                           // 14 
AboutView
 //public let VIEW_PENDING = VIEW_ABOUT + 1                            // 15 
PendingOpsListView
 
 // MARK: Transactions
-public let VIEW_TRANSACTIONLIST = VIEW_EMPTY + 10                   // 20 
TransactionsListView
-public let VIEW_TRANSACTIONSUMMARY = VIEW_TRANSACTIONLIST + 1       // 21 
TransactionSummary
-public let VIEW_TRANSACTIONDETAIL = VIEW_TRANSACTIONSUMMARY + 1     // 22 
TransactionDetail
+public let VIEW_EMPTY_HISTORY = VIEW_EMPTY_WALLET + 10              // 20 
TransactionsEmptyView
+public let VIEW_TRANSACTIONLIST = VIEW_EMPTY_HISTORY + 1            // 21 
TransactionsListView
+public let VIEW_TRANSACTIONSUMMARY = VIEW_TRANSACTIONLIST + 1       // 22 
TransactionSummary
+public let VIEW_TRANSACTIONDETAIL = VIEW_TRANSACTIONSUMMARY + 1     // 23 
TransactionDetail
 
 
 
 // MARK: - Manual Withdrawal (from Banking / ExchangeList)
 // receive coins from bank ==> shows IBAN + Purpose/Subject for manual wire 
transfer
-public let VIEW_WITHDRAWAL = VIEW_TRANSACTIONLIST + 10              // 30 
WithdrawAmount
+public let VIEW_WITHDRAWAL = VIEW_EMPTY_HISTORY + 10                // 30 
WithdrawAmount
 public let VIEW_WITHDRAW_TOS = VIEW_WITHDRAWAL + 1                  // 31 
WithdrawTOSView
 public let VIEW_WITHDRAW_ACCEPT = VIEW_WITHDRAW_TOS + 1             // 32
 
@@ -77,6 +78,9 @@ public let SHEET_WITHDRAW_TOS = SHEET_WITHDRAWAL + 1          
      // 131 Withd
 public let SHEET_WITHDRAW_ACCEPT = SHEET_WITHDRAW_TOS + 1           // 132 
WithdrawAcceptView
 public let SHEET_WITHDRAW_CONFIRM = SHEET_WITHDRAW_ACCEPT + 1       // 133 
waiting for bank confirmation
 
+public let SHEET_WITHDRAW_EXCHANGE = SHEET_WITHDRAWAL + 5           // 135 
WithdrawExchangeV
+
+
 // MARK: Merchant Payment
 // openURL (Link, NFC or scan QR) ==> pays merchant
 public let SHEET_PAYMENT = SHEET_WITHDRAWAL + 10                    // 140 Pay 
Merchant
diff --git a/TalerWallet1/Controllers/PublicConstants.swift 
b/TalerWallet1/Controllers/PublicConstants.swift
index 6fda1f3..2eb2ad8 100644
--- a/TalerWallet1/Controllers/PublicConstants.swift
+++ b/TalerWallet1/Controllers/PublicConstants.swift
@@ -51,6 +51,7 @@ public let LONGCURRENCY = "GOLDLATINUM"                       
  // 11 characters
 
 public let PLAINTEXT = "text/plain"
 public let MARKDOWN = "text/markdown"
+public let HTML = "text/html"
 
 public let EXCHANGEBASEURL = "exchangeBaseUrl"
 public let TALERURI = "talerUri"
diff --git a/TalerWallet1/Helper/Binding+onChange.swift 
b/TalerWallet1/Helper/Atomic.swift
similarity index 55%
copy from TalerWallet1/Helper/Binding+onChange.swift
copy to TalerWallet1/Helper/Atomic.swift
index 7bf8a5f..fb9604a 100644
--- a/TalerWallet1/Helper/Binding+onChange.swift
+++ b/TalerWallet1/Helper/Atomic.swift
@@ -1,5 +1,5 @@
-//  MIT License
-//  Copyright © Paul Hudson
+//
+//    Copyright 2023 Marc Stibane
 //
 //  Permission is hereby granted, free of charge, to any person obtaining a 
copy of this software
 //  and associated documentation files (the "Software"), to deal in the 
Software without restriction,
@@ -16,30 +16,47 @@
 //  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
OTHERWISE, ARISING FROM,
 //  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
THE SOFTWARE.
 //
-import SwiftUI
+import Foundation
+
+@propertyWrapper
+class Atomic<Value> where Value: BinaryInteger {
 
-/// Pass the handler directly to the Binding
-extension Binding {
-    func onChange(_ handler: @escaping (Value) -> Void) -> Binding<Value> {
-        Binding (
-            get: { self.wrappedValue },
-            set: { newValue in
-                self.wrappedValue = newValue
-                handler(newValue)
-            }
-        )
+    private let lock: NSLock
+    private var value: Value
+
+    init(default: Value) {
+        self.lock = NSLock()
+        self.value = `default`
     }
-}
 
-#if false
-// use like this:
-struct BindingView: View {
-    @State private var rating = 0.0
-    var body: some View {
-        Slider (value: $rating.onChange(sliderChanged))
+    var wrappedValue: Value {
+        get {
+            lock.lock()
+            defer { lock.unlock() }
+            return value
+        }
+        set {
+            lock.lock()
+            value = newValue
+            lock.unlock()
+        }
     }
-    func sliderChanged(_ value: Double) {
-        print ("Rating changed to \(value)")
+
+    var projectedValue: Atomic<Value> { self }
+
+    @discardableResult
+    func atomicIncrement() -> Value {
+        lock.lock()
+        defer { lock.unlock() }
+        self.value += 1
+        return value
+    }
+
+    @discardableResult
+    func atomicAdd(_ value: Value) -> Value {
+        lock.lock()
+        defer { lock.unlock() }
+        self.value += value
+        return value
     }
 }
-#endif
diff --git a/TalerWallet1/Helper/CStringArray.swift 
b/TalerWallet1/Helper/CStringArray.swift
new file mode 100644
index 0000000..62b8e96
--- /dev/null
+++ b/TalerWallet1/Helper/CStringArray.swift
@@ -0,0 +1,74 @@
+//
+//    Copyright 2020 Robert Salesas
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a 
copy of this software
+//  and associated documentation files (the "Software"), to deal in the 
Software without restriction,
+//  including without limitation the rights to use, copy, modify, merge, 
publish, distribute,
+//  sublicense, and/or sell copies of the Software, and to permit persons to 
whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in 
all copies or
+//  substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING
+//  BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
LIABLE FOR ANY CLAIM,
+//  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
THE SOFTWARE.
+//
+import Foundation
+
+/// `CStringArray` represents a C null-terminated array of pointers to C 
strings.
+///
+/// The lifetime of the C strings will correspond to the lifetime of the 
`CStringArray`
+/// instance so be careful about copying the buffer as it may contain dangling 
pointers.
+
+public struct CStringArray {
+    public let pointer: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>
+    public let count: Int
+    private var data: Data
+
+    public init(_ array: [String]) {
+        let count = array.count
+
+        // Allocate memory to hold the CStrings and a terminating nil
+        let pointer = 
UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>.allocate(capacity: count + 1)
+        pointer.initialize(repeating: nil, count: count + 1)  // Implicit 
terminating nil at the end of the array
+
+        // Populate the allocated memory with pointers to CStrings
+        var e = 0
+        array.forEach {
+            pointer[e] = strdup($0)
+            e += 1
+        }
+
+        // This uses the deallocator available on the data structure as a 
solution to the fact that structs do not have `deinit`
+        self.data = Data(bytesNoCopy: pointer, count: 
MemoryLayout<UnsafeMutablePointer<CChar>>.size * count, deallocator: 
.custom({_,_ in
+            for i in 0...count - 1 {
+                free(pointer[i])
+            }
+            pointer.deallocate()
+        }))
+
+        self.pointer = pointer
+        self.count = array.count
+    }
+
+    public subscript(index: Data.Index) -> UnsafeMutablePointer<CChar>? {
+        get {
+            precondition(index >= 0 && index < count, "Index out of range")
+            return pointer[index]
+        }
+    }
+
+    public subscript(index: Data.Index) -> String? {
+        get {
+            precondition(index >= 0 && index < count, "Index out of range")
+            if let pointee = pointer[index] {
+                return String(cString: pointee)
+            }
+
+            return nil
+        }
+    }
+}
diff --git a/TalerWallet1/Helper/CurrencySpecification.swift 
b/TalerWallet1/Helper/CurrencySpecification.swift
index 2f0319b..96a32ca 100644
--- a/TalerWallet1/Helper/CurrencySpecification.swift
+++ b/TalerWallet1/Helper/CurrencySpecification.swift
@@ -90,6 +90,18 @@ public struct CurrencyInfo {
         return CurrencyInfo(scope: scope, specs: specs, formatter: formatter)
     }
 
+    public static func francs() -> CurrencyInfo {
+        let currency = "CHF"
+        let scope = ScopeInfo(type: .global, currency: currency)
+        let specs = CurrencySpecification(name: currency,
+                                          fractionalInputDigits: 2,
+                                          fractionalNormalDigits: 2,
+                                          fractionalTrailingZeroDigits: 2,
+                                          altUnitNames: [0 : "CHF"])
+        let formatter = CurrencyFormatter.formatter(scope: scope, specs: specs)
+        return CurrencyInfo(scope: scope, specs: specs, formatter: formatter)
+    }
+
     /// returns all characters left from the decimalSeparator
     func integerPartStr(_ integerStr: String, decimalSeparator: String) -> 
String {
         if let integerIndex = integerStr.endIndex(of: decimalSeparator) {
diff --git a/TalerWallet1/Model/Model+Exchange.swift 
b/TalerWallet1/Model/Model+Exchange.swift
index f99bda1..e51caa8 100644
--- a/TalerWallet1/Model/Model+Exchange.swift
+++ b/TalerWallet1/Model/Model+Exchange.swift
@@ -1,5 +1,5 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
 import Foundation
@@ -19,12 +19,12 @@ enum ExchangeEntryStatus: String, Codable {
 
 enum ExchangeUpdateStatus: String, Codable {
     case initial
-    case initialUpdate = "initial(update)"
+    case initialUpdate = "initial-update"
     case suspended
     case failed
-    case outdatedUpdate = "outdated(update)"
+    case outdatedUpdate = "outdated-update"
     case ready
-    case readyUpdate = "ready(update)"
+    case readyUpdate = "ready-update"
 }
 // MARK: -
 /// The result from wallet-core's ListExchanges
@@ -176,6 +176,7 @@ extension WalletModel {
             let response = try await sendRequest(request, ASYNCDELAY)
             return response.exchanges
         } catch {
+            // TODO: Error
             return []               // empty, but not nil
         }
     }
diff --git a/TalerWallet1/Model/Model+Withdraw.swift 
b/TalerWallet1/Model/Model+Withdraw.swift
index a2d3ca9..3a624c4 100644
--- a/TalerWallet1/Model/Model+Withdraw.swift
+++ b/TalerWallet1/Model/Model+Withdraw.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import Foundation
 import taler_swift
 import SymLog
@@ -180,32 +183,31 @@ fileprivate struct AcceptManualWithdrawal: 
WalletBackendFormattedRequest {
 // MARK: -
 extension WalletModel {
     /// load withdraw-exchange details. Networking involved
-    @MainActor
-    func loadWithdrawalExchangeForUriM(_ talerUri: String)               // M 
for MainActor
+    @MainActor                    // M for MainActor
+    func loadWithdrawalExchangeForUriM(_ talerUri: String)
       async throws -> WithdrawExchangeResponse {
         let request = PrepareWithdrawExchange(talerUri: talerUri)
         let response = try await sendRequest(request, ASYNCDELAY)
         return response
     }
     /// load withdrawal details. Networking involved
-    @MainActor
-    func loadWithdrawalDetailsForUriM(_ talerWithdrawUri: String)              
 // M for MainActor
+    @MainActor                  // M for MainActor
+    func getWithdrawalDetailsForUriM(_ talerWithdrawUri: String)
       async throws -> WithdrawUriInfoResponse {
         let request = GetWithdrawalDetailsForURI(talerWithdrawUri: 
talerWithdrawUri)
         let response = try await sendRequest(request, ASYNCDELAY)
         return response
     }
-    @MainActor
-    func loadWithdrawalDetailsForAmountM(_ exchangeBaseUrl: String, amount: 
Amount)  // M for MainActor
+    @MainActor                     // M for MainActor
+    func getWithdrawalDetailsForAmountM(_ exchangeBaseUrl: String, amount: 
Amount)
       async throws -> WithdrawalAmountDetails {
         let request = GetWithdrawalDetailsForAmount(exchangeBaseUrl: 
exchangeBaseUrl,
                                                              amount: amount)
         let response = try await sendRequest(request, ASYNCDELAY)
         return response
     }
-    @MainActor
+    @MainActor                  // M for MainActor
     func loadExchangeTermsOfServiceM(_ exchangeBaseUrl: String, 
acceptedFormat: [String], acceptLanguage: String)
-                                // M for MainActor
       async throws -> ExchangeTermsOfService {
         let request = GetExchangeTermsOfService(exchangeBaseUrl: 
exchangeBaseUrl,
                                                  acceptedFormat: 
acceptedFormat,
@@ -213,22 +215,22 @@ extension WalletModel {
         let response = try await sendRequest(request, ASYNCDELAY)
         return response
     }
-    @MainActor
-    func setExchangeTOSAcceptedM(_ exchangeBaseUrl: String, etag: String)      
 // M for MainActor
+    @MainActor              // M for MainActor
+    func setExchangeTOSAcceptedM(_ exchangeBaseUrl: String, etag: String)
       async throws -> Decodable {
         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
+    @MainActor               // M for MainActor
+    func sendAcceptIntWithdrawalM(_ exchangeBaseUrl: String, withdrawURL: 
String)
       async throws -> AcceptWithdrawalResponse? {
         let request = AcceptBankIntegratedWithdrawal(talerWithdrawUri: 
withdrawURL, exchangeBaseUrl: exchangeBaseUrl)
         let response = try await sendRequest(request, ASYNCDELAY)
         return response
     }
-    @MainActor
-    func sendAcceptManualWithdrawalM(_ exchangeBaseUrl: String, amount: 
Amount, restrictAge: Int?)   // M for MainActor
+    @MainActor                  // M for MainActor
+    func sendAcceptManualWithdrawalM(_ exchangeBaseUrl: String, amount: 
Amount, restrictAge: Int?)
       async throws -> AcceptManualWithdrawalResult? {
         let request = AcceptManualWithdrawal(exchangeBaseUrl: exchangeBaseUrl, 
amount: amount, restrictAge: restrictAge)
         let response = try await sendRequest(request, ASYNCDELAY)
diff --git a/TalerWallet1/Model/WalletModel.swift 
b/TalerWallet1/Model/WalletModel.swift
index 86cf4e6..49ee759 100644
--- a/TalerWallet1/Model/WalletModel.swift
+++ b/TalerWallet1/Model/WalletModel.swift
@@ -92,6 +92,8 @@ fileprivate struct InitRequest: WalletBackendFormattedRequest 
{
     func operation() -> String { "init" }
     func args() -> Args {
         let testing = Testing(devModeActive: false) // true, false
+        let builtin = Builtin(exchanges: [])
+//        let config = Config(testing: testing, builtin: builtin)
         let config = Config(testing: testing)
         return Args(persistentStoragePath: persistentStoragePath,
 //                       cryptoWorkerType: "sync",
@@ -106,8 +108,13 @@ fileprivate struct InitRequest: 
WalletBackendFormattedRequest {
         var devModeActive: Bool
         // more to come...
     }
+    struct Builtin: Encodable {
+        var exchanges: [String]
+        // more to come...
+    }
     struct Config: Encodable {
         var testing: Testing
+//        var builtin: Builtin
     }
     struct Args: Encodable {
         var persistentStoragePath: String
diff --git a/TalerWallet1/Quickjs/QuickDataTask.swift 
b/TalerWallet1/Quickjs/QuickDataTask.swift
new file mode 100644
index 0000000..e4afe88
--- /dev/null
+++ b/TalerWallet1/Quickjs/QuickDataTask.swift
@@ -0,0 +1,156 @@
+/*
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
+ * See LICENSE.md
+ */
+/**
+ * @author Marc Stibane
+ */
+import Foundation
+//import "Foundation/NSURLError.h"
+import os.log
+
+import FTalerWalletcore
+
+// will be called from wallet-core for networking
+func request_create(userdata: Optional<UnsafeMutableRawPointer>,
+                 requestInfo: 
Optional<UnsafeMutablePointer<JSHttpRequestInfo>>) -> Int32 {
+    let quickjs = 
Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
+
+    if let requestInfo {
+        if let url = URL(string: String(cString: requestInfo.pointee.url)) {
+            let responseCb = requestInfo.pointee.response_cb
+            let responseCbCls = requestInfo.pointee.response_cb_cls
+            let method = String(cString: requestInfo.pointee.method)
+            let requestHeaders = requestInfo.pointee.request_headers
+                let redirect = requestInfo.pointee.redirect             // 
TODO: redirect
+            let timeoutMs = requestInfo.pointee.timeout_ms
+                let debug = requestInfo.pointee.debug                   // 
TODO: debug
+            let reqBody = requestInfo.pointee.req_body
+            let bodyLen = requestInfo.pointee.req_body_len
+
+            var request = URLRequest(url: url)
+            request.httpMethod = method
+            request.timeoutInterval = TimeInterval(timeoutMs)
+            if let reqBody {        // caller will deallocate the req_body 
after dataTask finish or cancel
+                let body = Data(bytesNoCopy: reqBody, count: Int(bodyLen), 
deallocator: .none)
+                request.httpBody = body
+            }
+            if var ptr = requestHeaders {
+                while let cString = ptr.pointee {
+                    let string = String(cString: cString)
+                    if let index = string.firstIndex(of: ":") {
+                        let headerField = string.prefix(upTo: index)
+                        let nextIndex = string.index(index, offsetBy: 1)       
 // skip the ":"
+                        let value = string.suffix(from: nextIndex)
+                        request.addValue(String(value), forHTTPHeaderField: 
String(headerField))
+                    }
+                    ptr += 1
+                }
+            }
+
+            return quickjs.reqCreate(request, responseCb, responseCbCls)
+        }
+    }
+    return 0
+}
+
+func request_cancel(userdata: Optional<UnsafeMutableRawPointer>,
+                   requestID: Int32) -> Int32 {
+    let quickjs = 
Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
+    return quickjs.reqCancel(requestID)
+}
+// MARK: -
+class QuickDataTask: NSObject {
+    let urlSession: URLSession
+    let request: URLRequest
+    let requestID: Int32
+    var requests: [Int32: QuickDataTask]
+    let responseCb: JSHttpResponseCb?
+    let responseCbCls: Optional<UnsafeMutableRawPointer>
+
+    var dataTask: URLSessionDataTask? = nil
+    var logger: Logger
+
+    init(urlSession: URLSession,
+            request: URLRequest,
+          requestID: Int32,
+           requests: inout [Int32: QuickDataTask],
+         responseCb: JSHttpResponseCb?,
+      responseCbCls: Optional<UnsafeMutableRawPointer>
+    ) {
+        self.logger = Logger(subsystem: "net.taler.gnu", category: 
"Networking")
+        self.urlSession = urlSession
+        self.request = request
+        self.requestID = requestID
+        self.requests = requests
+        self.responseCb = responseCb
+        self.responseCbCls = responseCbCls
+    }
+    func run() {
+        if let responseCb, let responseCbCls {
+            let method = self.request.httpMethod ?? "Unknown"
+            let url = self.request.url?.absoluteString ?? EMPTYSTRING
+            logger.trace("❓\(self.requestID, privacy: .public)  \(method, 
privacy: .public) \(url, privacy: .public)")
+            dataTask = urlSession.dataTask(with: request) { [self] data, 
response, error in
+                let err: Error
+                if let response = response as? HTTPURLResponse {
+                    var headerArray: [String] = []
+                    var numHeaders: Int32 = 0
+                    var status = Int32(response.statusCode)
+                    var errmsg = 
HTTPURLResponse.localizedString(forStatusCode: Int(status))
+                    var errmsg_p0 = UnsafeMutablePointer<CChar>(mutating: 
errmsg.cString(using: .utf8))
+              // Initialization of 'UnsafeMutablePointer<CChar>' (aka 
'UnsafeMutablePointer<Int8>') results in a dangling pointer
+                    let headers = response.allHeaderFields
+                    for (key,value) in headers {
+                        headerArray.append("\(key): \(value)")
+                        numHeaders += 1
+                    }
+                    let cHeaders = CStringArray(headerArray)
+
+                    if let data {
+                        var ndata:NSData = data as NSData
+                        var bodyPtr = UnsafeMutableRawPointer(mutating: 
ndata.bytes)
+                        var responseInfo = JSHttpResponseInfo(request_id: 
self.requestID,
+                                                              status: status,
+                                                              errmsg: 
errmsg_p0,
+                                                    response_headers: 
cHeaders.pointer,
+                                                num_response_headers: 
numHeaders,
+                                                                body: bodyPtr,
+                                                            body_len: 
data.count)
+                        let responseInfoPtr = 
UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
+                        // Initialization of 
'UnsafeMutablePointer<JSHttpResponseInfo>' results in a dangling pointer
+                        logger.trace("❗️ \(self.requestID, privacy: .public) 
\(url, privacy: .public)")
+                        responseCb(responseCbCls, responseInfoPtr)
+                        return
+                    } else { // data == nil
+                        logger.error("‼️\(self.requestID, privacy: .public)  
\(method, privacy: .public) \(response.statusCode, privacy: .public) \(errmsg, 
privacy: .public)")
+                        var responseInfo = JSHttpResponseInfo(request_id: 
self.requestID,
+                                                                  status: 
status,
+                                                                  errmsg: 
errmsg_p0,
+                                                        response_headers: 
cHeaders.pointer,
+                                                    num_response_headers: 
numHeaders,
+                                                                    body: nil,
+                                                                body_len: 0)
+                        let responseInfoPtr = 
UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
+                        responseCb(responseCbCls, responseInfoPtr)
+                    }
+                } else {
+                    var errmsg = "No http response from \(url)"
+                    logger.error("⁉️\(self.requestID, privacy: .public)  
\(method, privacy: .public) \(errmsg, privacy: .public)")
+                    var errmsg_p0 = UnsafeMutablePointer<CChar>(mutating: 
errmsg.cString(using: .utf8))
+                    var responseInfo = JSHttpResponseInfo(request_id: 
self.requestID,
+                                                          status: 0,
+                                                          errmsg: errmsg_p0,
+                                                          response_headers: 
nil,
+                                                          
num_response_headers: 0,
+                                                          body: nil,
+                                                          body_len: 0)
+                    let responseInfoPtr = 
UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
+                    responseCb(responseCbCls, responseInfoPtr)
+                }
+                requests[requestID] = nil
+            }
+            dataTask?.resume()
+        }
+    }
+}
diff --git a/TalerWallet1/Quickjs/quickjs.swift 
b/TalerWallet1/Quickjs/quickjs.swift
index 89728ac..dbe796d 100644
--- a/TalerWallet1/Quickjs/quickjs.swift
+++ b/TalerWallet1/Quickjs/quickjs.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import Foundation
 import os.log
 
@@ -13,40 +16,48 @@ public protocol QuickjsMessageHandler: AnyObject {
 // MARK: -
 func notification_callback(userdata: Optional<UnsafeMutableRawPointer>,
                            payload: Optional<UnsafePointer<Int8>>) {
-    let native = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
+    let quickjs = 
Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
     let string = String(cString: payload!)
-    native.internalOnNotify(payload: string)
+    quickjs.internalOnNotify(payload: string)
 }
 
 func logging_callback(userdata: Optional<UnsafeMutableRawPointer>,
                           level: TALER_WALLET_LogLevel,
                             tag: Optional<UnsafePointer<Int8>>,
                         message: Optional<UnsafePointer<Int8>>) {
-    let native = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
-    let logger = native.logger
-    let theTag = String(cString: tag!)
-    let theMessage = String(cString: message!)
+    let quickjs = 
Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
+    let logger = quickjs.logger
+    let swiftTag = String(cString: tag!)
+    let swiftMessage = String(cString: message!)
 
     switch level {
         case TALER_WALLET_LOG_ERROR:
-            logger.error("\(theTag, privacy: .public)  \(theMessage, privacy: 
.public)")
+            logger.error("\(swiftTag, privacy: .public)  \(swiftMessage, 
privacy: .public)")
         case TALER_WALLET_LOG_WARN:
-            logger.warning("\(theTag, privacy: .public)  \(theMessage, 
privacy: .public)")
+            logger.warning("\(swiftTag, privacy: .public)  \(swiftMessage, 
privacy: .public)")
         case TALER_WALLET_LOG_MESSAGE:
-            logger.notice("\(theTag, privacy: .public)  \(theMessage, privacy: 
.public)")
+            logger.notice("\(swiftTag, privacy: .public)  \(swiftMessage, 
privacy: .public)")
         case TALER_WALLET_LOG_INFO:
-            logger.info("\(theTag, privacy: .public)  \(theMessage, privacy: 
.public)")
+            logger.info("\(swiftTag, privacy: .public)  \(swiftMessage, 
privacy: .public)")
         case TALER_WALLET_LOG_TRACE:
-            logger.trace("\(theTag, privacy: .public)  \(theMessage, privacy: 
.public)")
+            logger.trace("\(swiftTag, privacy: .public)  \(swiftMessage, 
privacy: .public)")
         default: break
     }
 }
 // MARK: -
-public class Quickjs {
+public class Quickjs {      // acts as singleton, since only one instance ever 
exists
     var talerWalletInstance: OpaquePointer!
     public weak var messageHandler: QuickjsMessageHandler?
     var logger: Logger
 
+    @Atomic(default: 0)
+    private var lastRequestID: Int32
+    private var requests: [Int32: QuickDataTask] = [:]
+
+    private lazy var urlSession: URLSession = {
+        return URLSession(configuration: .default)
+    }()
+
     public init() {
         self.logger = Logger(subsystem: "net.taler.gnu", category: "QuickJS")
         self.talerWalletInstance = TALER_WALLET_create()
@@ -56,16 +67,48 @@ public class Quickjs {
         TALER_WALLET_set_log_handler(talerWalletInstance,
                                      logging_callback,
                                      Unmanaged.passUnretained(self).toOpaque())
-        TALER_WALLET_run(talerWalletInstance);
+#if USE_HTTP_CLIENT_CURL
+        let http_impl = js_curl_http_client_create()
+#else
+        let http_impl = TALER_pack_http_client_implementation(request_create, 
request_cancel,
+                                                              
Unmanaged.passUnretained(self).toOpaque())
+#endif
+        // http_impl got malloc'd, and could possibly be free'd when the app 
terminates
+        TALER_set_http_client_implementation(talerWalletInstance, http_impl)
+        // - but we never free anything on termination, thus we don't save 
http_impl here
+        TALER_WALLET_run(talerWalletInstance)
     }
 
-    deinit {
-        // FIXME: TALER_WALLET_destroy
-//        TALER_WALLET_destroy(talerWalletInstance)
+    func reqCreate(_ request: URLRequest,
+                _ responseCb: JSHttpResponseCb?,
+             _ responseCbCls: Optional<UnsafeMutableRawPointer>) -> Int32 {
+        let requestID = $lastRequestID.atomicIncrement()
+        let quickDataTask = QuickDataTask(urlSession: urlSession,
+                                             request: request,
+                                           requestID: requestID,
+                                            requests: &requests,
+                                          responseCb: responseCb,
+                                       responseCbCls: responseCbCls)
+        quickDataTask.run()
+        requests[requestID] = quickDataTask
+        return requestID
+    }
+
+    func reqCancel(_ requestID: Int32) -> Int32 {
+        if let quickDataTask = requests[requestID] {
+            if let dataTask = quickDataTask.dataTask {
+                dataTask.cancel()
+            }
+        }
+        requests[requestID] = nil
+        return 0
     }
 
+    deinit {
+        // No need to call TALER_WALLET_destroy - memory gets purged anyway
+    }
 
-    public func internalOnNotify(payload: String) {
+    func internalOnNotify(payload: String) {
         if let handler = messageHandler {
             handler.handleMessage(message: payload)
         }
@@ -84,17 +127,4 @@ public class Quickjs {
     public func sendMessage(message: String) {
         TALER_WALLET_send_request(talerWalletInstance, message)
     }
-
-    /// Note: This *must* be called before releasing the object, or else the 
thread will keep going.
-//    public func waitStopped() {
-//        scheduleNodeThreadSync {
-//            self.stopped = true
-//        }
-//        thread.cancel()
-//    }
-
-//    public func putModuleCode(modName: String, code: String) {
-//        __putModuleCodeNative(self.instance, modName.cString(using: .utf8),
-//                              code.cString(using: .utf8))
-//    }
 }
diff --git a/TalerWallet1/Views/Balances/BalanceRowView.swift 
b/TalerWallet1/Views/Balances/BalanceRowView.swift
index ac15ea2..ca0bd58 100644
--- a/TalerWallet1/Views/Balances/BalanceRowView.swift
+++ b/TalerWallet1/Views/Balances/BalanceRowView.swift
@@ -90,13 +90,14 @@ struct BalanceRowView: View {
 
             let sendTitle = minimalistic ? sendTitle0 : sendTitle1
             let requTitle = minimalistic ? requestTitle0 : requestTitle1
-            let twoRowButtons = TwoRowButtons(sendTitle: sendTitle,
-                                              recvTitle: requTitle,
-                                         fitsSideBySide: false,
-                                              lineLimit: 5,
-                                           sendDisabled: amount.isZero,
-                                             sendAction: sendAction,
-                                             recvAction: recvAction)
+            let twoRowButtons = TwoRowButtons(stack: stack.push(),
+                                          sendTitle: sendTitle,
+                                          recvTitle: requTitle,
+                                     fitsSideBySide: false,
+                                          lineLimit: 5,
+                                       sendDisabled: amount.isZero,
+                                         sendAction: sendAction,
+                                         recvAction: recvAction)
             if #available(iOS 16.0, *) {
                 ViewThatFits(in: .horizontal) {
                     HStack(spacing: HSPACING) {
diff --git a/TalerWallet1/Views/Balances/TwoRowButtons.swift 
b/TalerWallet1/Views/Balances/TwoRowButtons.swift
index f057936..d9c0713 100644
--- a/TalerWallet1/Views/Balances/TwoRowButtons.swift
+++ b/TalerWallet1/Views/Balances/TwoRowButtons.swift
@@ -6,6 +6,7 @@ import SwiftUI
 import taler_swift
 
 struct TwoRowButtons: View {
+    let stack: CallStack
     let sendTitle: String
     let recvTitle: String
     let fitsSideBySide: Bool
@@ -16,13 +17,14 @@ struct TwoRowButtons: View {
 
 //    @Environment(\.sizeCategory) var sizeCategory
     func makeCopy(fitsSideBySide: Bool) -> TwoRowButtons {
-        TwoRowButtons(sendTitle: sendTitle,
-                      recvTitle: recvTitle,
-                 fitsSideBySide: fitsSideBySide,
-                      lineLimit: lineLimit,
-                   sendDisabled: sendDisabled,
-                     sendAction: sendAction,
-                     recvAction: recvAction)
+        TwoRowButtons(stack: stack.push(),
+                  sendTitle: sendTitle,
+                  recvTitle: recvTitle,
+             fitsSideBySide: fitsSideBySide,
+                  lineLimit: lineLimit,
+               sendDisabled: sendDisabled,
+                 sendAction: sendAction,
+                 recvAction: recvAction)
     }
 
     var body: some View {
@@ -35,6 +37,7 @@ struct TwoRowButtons: View {
                 .disabled(sendDisabled)
                 .buttonStyle(TalerButtonStyle(type: .bordered,
                                             dimmed: false,
+                                          disabled: sendDisabled,
                                            aligned: .center))
             let recvButtonTitle = recvTitle.tabbed(oneLine: !fitsSideBySide)
             let recvVoiceOverTitle = recvTitle.tabbed(oneLine: true)
@@ -53,17 +56,19 @@ struct TwoRowButtons: View {
 struct TwoRowButtons_Previews: PreviewProvider {
     static var previews: some View {
         List {
-                TwoRowButtons(sendTitle: "Send " + TESTCURRENCY,
-                              recvTitle: "Request" + LONGCURRENCY,
-                          fitsSideBySide: false,
-                               lineLimit: 2, sendDisabled: true,
-                              sendAction: {}, recvAction: {})
+            TwoRowButtons(stack: CallStack("Preview"),
+                      sendTitle: "Send " + TESTCURRENCY,
+                      recvTitle: "Request " + LONGCURRENCY,
+                 fitsSideBySide: false,
+                      lineLimit: 2, sendDisabled: true,
+                     sendAction: {}, recvAction: {})
                 .listRowSeparator(.hidden)
-                TwoRowButtons(sendTitle: "Send" + DEMOCURRENCY,
-                              recvTitle: "Request" + DEMOCURRENCY,
-                          fitsSideBySide: true,
-                               lineLimit: 2, sendDisabled: true,
-                              sendAction: {}, recvAction: {})
+            TwoRowButtons(stack: CallStack("Preview"),
+                      sendTitle: "Send " + DEMOCURRENCY,
+                      recvTitle: "Request " + DEMOCURRENCY,
+                 fitsSideBySide: true,
+                      lineLimit: 2, sendDisabled: true,
+                     sendAction: {}, recvAction: {})
                 .listRowSeparator(.hidden)
         }
     }
diff --git a/TalerWallet1/Views/Banking/DepositAmountV.swift 
b/TalerWallet1/Views/Banking/DepositAmountV.swift
index 0635481..5358d0a 100644
--- a/TalerWallet1/Views/Banking/DepositAmountV.swift
+++ b/TalerWallet1/Views/Banking/DepositAmountV.swift
@@ -134,7 +134,7 @@ struct DepositAmountV: View {
                         }
                     }
                 }
-                .buttonStyle(TalerButtonStyle(type: .prominent))
+                .buttonStyle(TalerButtonStyle(type: .prominent, disabled: 
disabled || depositStarted))
                 .disabled(disabled || depositStarted)
                 .accessibilityHint(disabled ? "enabled when amount is 
non-zero, but not higher than your available amount" : EMPTYSTRING)
             }.padding(.horizontal) } // ScrollVStack
diff --git a/TalerWallet1/Views/Banking/DepositIbanV.swift 
b/TalerWallet1/Views/Banking/DepositIbanV.swift
index ef1e169..6b6fc9c 100644
--- a/TalerWallet1/Views/Banking/DepositIbanV.swift
+++ b/TalerWallet1/Views/Banking/DepositIbanV.swift
@@ -85,10 +85,12 @@ struct DepositIbanV: View {
                 .textFieldStyle(.roundedBorder)
                 .padding(minimalistic ? .bottom : .vertical)
                 .onAppear {
-                    symLog.log("dispatching kbd...")
-                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
-                        isFocused = true        // make first responder - 
raise keybord
-                        symLog.log("...kbd isFocused")
+                    if !UIAccessibility.isVoiceOverRunning {
+                        symLog.log("dispatching kbd...")
+                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
+                            isFocused = true        // make first responder - 
raise keybord
+                            symLog.log("...kbd isFocused")
+                        }
                     }
                 }
 
@@ -110,7 +112,7 @@ struct DepositIbanV: View {
             NavigationLink(destination: destination) {
                 Text(buttonTitle(amountToTransfer, currencyInfo))
             }
-                .buttonStyle(TalerButtonStyle(type: .prominent))
+            .buttonStyle(TalerButtonStyle(type: .prominent, disabled: 
disabled))
                 .disabled(disabled)
                 .accessibilityHint(disabled ? "enabled when account holder and 
IBAN are set" : EMPTYSTRING)
         }.padding(.horizontal) } // ScrollVStack
diff --git a/TalerWallet1/Views/Banking/ExchangeListView.swift 
b/TalerWallet1/Views/Banking/ExchangeListView.swift
index 7800583..44e8191 100644
--- a/TalerWallet1/Views/Banking/ExchangeListView.swift
+++ b/TalerWallet1/Views/Banking/ExchangeListView.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import taler_swift
 import SymLog
@@ -109,12 +112,23 @@ extension ExchangeListCommonV: View {
             .listStyle(myListStyle.style).anyView
         }
         .onAppear() {
-            DebugViewC.shared.setViewID(VIEW_EXCHANGES, stack: stack.push())
+            DebugViewC.shared.setViewID(VIEW_BANKING, stack: stack.push())
         }
         .overlay {
             if exchanges.isEmpty {
-                Text("No Payment Services yet...")
-                    .talerFont(.body)
+                List {
+                    Section {
+                        Text("There are no Payment Services yet.")
+                            .talerFont(.title3)
+                    }
+                    Section {
+                        Text("Use the Add button to add a service.")
+                            .talerFont(.body)
+                            .listRowSeparator(.hidden)
+                        Text("You can also scan a withdrawal QR code from your 
bank on the Balances tab to automatically add a payment service.")
+                            .talerFont(.body)
+                    }
+                }
             }
         }
         .onNotification(.ExchangeAdded) { notification in
diff --git a/TalerWallet1/Views/Banking/ExchangeRowView.swift 
b/TalerWallet1/Views/Banking/ExchangeRowView.swift
index d4af147..3edc3e8 100644
--- a/TalerWallet1/Views/Banking/ExchangeRowView.swift
+++ b/TalerWallet1/Views/Banking/ExchangeRowView.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import taler_swift
 import SymLog
@@ -65,7 +68,7 @@ struct ExchangeRowView: View {
               amountToTransfer: $amountToTransfer)
         }
         let manualWithdraw = LazyView {
-            ManualWithdraw(stack: stack.push(),
+            ManualWithdraw(stack: stack.push(), isSheet: false,
                  exchangeBaseUrl: baseURL,
                 amountToTransfer: $amountToTransfer)
         }
@@ -76,13 +79,14 @@ struct ExchangeRowView: View {
                      acceptAction: nil)         // pop back to here
         }
         let disableDeposit = false     // TODO: availableAmount.isZero
-        let twoRowButtons = TwoRowButtons(sendTitle: minimalistic ? 
depositTitle0 : depositTitle1,
-                                          recvTitle: minimalistic ? 
withdrawTitle0 : withdrawTitle1,
-                                     fitsSideBySide: false,
-                                          lineLimit: 5,
-                                       sendDisabled: disableDeposit,
-                                         sendAction: { selectAndUpdate(1) },
-                                         recvAction: { selectAndUpdate(2) })
+        let twoRowButtons = TwoRowButtons(stack: stack.push(),
+                                      sendTitle: minimalistic ? depositTitle0 
: depositTitle1,
+                                      recvTitle: minimalistic ? withdrawTitle0 
: withdrawTitle1,
+                                 fitsSideBySide: false,
+                                      lineLimit: 5,
+                                   sendDisabled: disableDeposit,
+                                     sendAction: { selectAndUpdate(1) },
+                                     recvAction: { selectAndUpdate(2) })
         Group {
             NavigationLink(destination: showToS) {
                 VStack(alignment: .leading) {
diff --git a/TalerWallet1/Views/Banking/ManualWithdraw.swift 
b/TalerWallet1/Views/Banking/ManualWithdraw.swift
index b675135..b4a3efb 100644
--- a/TalerWallet1/Views/Banking/ManualWithdraw.swift
+++ b/TalerWallet1/Views/Banking/ManualWithdraw.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import taler_swift
 import SymLog
@@ -11,6 +14,7 @@ import SymLog
 struct ManualWithdraw: View {
     private let symLog = SymLogV(0)
     let stack: CallStack
+    let isSheet: Bool
     let exchangeBaseUrl: String
     @Binding var amountToTransfer: Amount
 
@@ -72,7 +76,7 @@ struct ManualWithdraw: View {
                     NavigationLink(destination: destination) {
                         Text("Confirm Withdrawal")      // VIEW_WITHDRAW_ACCEPT
                     }
-                    .buttonStyle(TalerButtonStyle(type: .prominent))
+                    .buttonStyle(TalerButtonStyle(type: .prominent, disabled: 
disabled))
                     .disabled(disabled)
                     .padding(.top)
                 } else {
@@ -88,7 +92,11 @@ struct ManualWithdraw: View {
             
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
             .navigationTitle(navTitle)
             .onAppear {
-                DebugViewC.shared.setViewID(VIEW_WITHDRAWAL, stack: 
stack.push())
+                if isSheet {
+                    DebugViewC.shared.setSheetID(SHEET_WITHDRAW_EXCHANGE)
+                } else {
+                    DebugViewC.shared.setViewID(VIEW_WITHDRAWAL, stack: 
stack.push())
+                }
                 symLog.log("❗️ \(navTitle) onAppear")
             }
             .onDisappear {
@@ -108,7 +116,7 @@ struct ManualWithdraw: View {
             }
             if !amountToTransfer.isZero {
                 do {
-                    let details = try await 
model.loadWithdrawalDetailsForAmountM(exchangeBaseUrl,
+                    let details = try await 
model.getWithdrawalDetailsForAmountM(exchangeBaseUrl,
                                                                            
amount: amountToTransfer)
                     withdrawalAmountDetails = details
 //                    agePicker.setAges(ages: 
withdrawalAmountDetails?.ageRestrictionOptions)
@@ -124,12 +132,12 @@ struct ManualWithdraw: View {
 #if DEBUG
 struct ManualWithdraw_Previews: PreviewProvider {
     struct StateContainer : View {
-        @State private var amountToTransfer = Amount(currency: LONGCURRENCY, 
cent: 510)
+        @State private var amountToPreview = Amount(currency: LONGCURRENCY, 
cent: 510)
 
         var body: some View {
-            ManualWithdraw(stack: CallStack("Preview"),
+            ManualWithdraw(stack: CallStack("Preview"), isSheet: false,
                  exchangeBaseUrl: DEMOEXCHANGE,
-                amountToTransfer: $amountToTransfer)
+                amountToTransfer: $amountToPreview)
         }
     }
 
diff --git a/TalerWallet1/Views/HelperViews/AmountInputV.swift 
b/TalerWallet1/Views/HelperViews/AmountInputV.swift
index 80c4d98..2b542af 100644
--- a/TalerWallet1/Views/HelperViews/AmountInputV.swift
+++ b/TalerWallet1/Views/HelperViews/AmountInputV.swift
@@ -90,13 +90,13 @@ struct AmountInputV: View {
                     NavigationLink(destination: destination) {
                         Text("Next")
                     }
-                    .buttonStyle(TalerButtonStyle(type: .prominent))
+                    .buttonStyle(TalerButtonStyle(type: .prominent, disabled: 
disabled))
                     .disabled(disabled)
                 } else {
                     Button("Next") {
                         buttonAction()
                     }
-                        .buttonStyle(TalerButtonStyle(type: .prominent))
+                        .buttonStyle(TalerButtonStyle(type: .prominent, 
disabled: disabled))
                         .disabled(disabled)
                 }
             }
diff --git a/TalerWallet1/Views/HelperViews/Buttons.swift 
b/TalerWallet1/Views/HelperViews/Buttons.swift
index 6a01913..53530ca 100644
--- a/TalerWallet1/Views/HelperViews/Buttons.swift
+++ b/TalerWallet1/Views/HelperViews/Buttons.swift
@@ -92,9 +92,6 @@ struct ReloadButton : View  {
 }
 
 struct TalerButtonStyle: ButtonStyle {
-    @Environment(\.isEnabled) private var isEnabled: Bool
-    var disabled: Bool { !isEnabled }
-
     enum TalerButtonStyleType {
         case plain
         case balance
@@ -104,6 +101,7 @@ struct TalerButtonStyle: ButtonStyle {
     var type: TalerButtonStyleType = .plain
     var dimmed: Bool = false
     var narrow: Bool = false
+    var disabled: Bool = false
     var aligned: TextAlignment = .center
     var badge: String = EMPTYSTRING
 
diff --git a/TalerWallet1/Views/HelperViews/CurrencyInputView.swift 
b/TalerWallet1/Views/HelperViews/CurrencyInputView.swift
index c73b479..da78ab2 100644
--- a/TalerWallet1/Views/HelperViews/CurrencyInputView.swift
+++ b/TalerWallet1/Views/HelperViews/CurrencyInputView.swift
@@ -145,7 +145,7 @@ struct CurrencyInputView: View {
         }.onAppear {   // make CurrencyField show the keyboard after 0.4 
seconds
             if hasBeenShown {
 //                print("❗️Yikes: CurrencyInputView hasBeenShown")
-            } else {
+            } else if !UIAccessibility.isVoiceOverRunning {
                 print("❗️CurrencyInputView❗️")
                 DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
                     hasBeenShown = true
diff --git a/TalerWallet1/Views/HelperViews/SelectDays.swift 
b/TalerWallet1/Views/HelperViews/SelectDays.swift
index 3e49a07..ba8383d 100644
--- a/TalerWallet1/Views/HelperViews/SelectDays.swift
+++ b/TalerWallet1/Views/HelperViews/SelectDays.swift
@@ -47,7 +47,8 @@ struct SelectDays: View {
                     } else {
                         Text("\(ONEDAY) Day", comment: "1 Day, might get 
plural (e.g. 2..3 Days), 4 letters max., abbreviate if longer")     // TODO: 
Plural
                     }
-                }.buttonStyle(TalerButtonStyle(type: (selected == ONEDAY) ? 
.prominent : .bordered, dimmed: true))
+                }.buttonStyle(TalerButtonStyle(type: (selected == ONEDAY) ? 
.prominent : .bordered,
+                                             dimmed: true, disabled: 
!isEnabled))
                     .accessibilityAddTraits(selected == ONEDAY ? .isSelected : 
[])
                     .disabled(!isEnabled)
 
@@ -57,7 +58,8 @@ struct SelectDays: View {
                     } else {
                         Text("\(SEVENDAYS) Days", comment: "7 Days, always 
plural (3..9), 4 letters max., abbreviate if longer")
                     }
-                }.buttonStyle(TalerButtonStyle(type: (selected == SEVENDAYS) ? 
.prominent : .bordered, dimmed: true))
+                }.buttonStyle(TalerButtonStyle(type: (selected == SEVENDAYS) ? 
.prominent : .bordered, dimmed: true,
+                                              disabled: !isEnabled || 
maxExpiration < SEVENDAYS))
                     .accessibilityAddTraits(selected == SEVENDAYS ? 
.isSelected : [])
                     .disabled(!isEnabled || maxExpiration < SEVENDAYS)
 
@@ -67,7 +69,8 @@ struct SelectDays: View {
                     } else {
                         Text("\(THIRTYDAYS) Days", comment: "30 Days, always 
plural (10..30), 4 letters max., abbreviate if longer")
                     }
-                }.buttonStyle(TalerButtonStyle(type: (selected == THIRTYDAYS) 
? .prominent : .bordered, dimmed: true))
+                }.buttonStyle(TalerButtonStyle(type: (selected == THIRTYDAYS) 
? .prominent : .bordered, dimmed: true,
+                                              disabled: !isEnabled || 
maxExpiration < THIRTYDAYS))
                     .accessibilityAddTraits(selected == THIRTYDAYS ? 
.isSelected : [])
                     .disabled(!isEnabled || maxExpiration < THIRTYDAYS)
             } // 3 buttons
diff --git a/TalerWallet1/Views/HelperViews/SubjectInputV.swift 
b/TalerWallet1/Views/HelperViews/SubjectInputV.swift
index 3634226..56135f8 100644
--- a/TalerWallet1/Views/HelperViews/SubjectInputV.swift
+++ b/TalerWallet1/Views/HelperViews/SubjectInputV.swift
@@ -92,10 +92,12 @@ struct SubjectInputV<TargetView: View>: View {
             .background(WalletColors().fieldBackground)
             .textFieldStyle(.roundedBorder)
             .onAppear {
-                symLog.log("dispatching kbd...")
-                DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
-                    isFocused = true        // make first responder - raise 
keybord
-                    symLog.log("...kbd isFocused")
+                if !UIAccessibility.isVoiceOverRunning {
+                    symLog.log("dispatching kbd...")
+                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
+                        isFocused = true        // make first responder - 
raise keybord
+                        symLog.log("...kbd isFocused")
+                    }
                 }
             }
 
@@ -110,7 +112,7 @@ struct SubjectInputV<TargetView: View>: View {
                 .padding(4)
 
             NavigationLink("Next", destination: targetView)
-                .buttonStyle(TalerButtonStyle(type: .prominent))
+                .buttonStyle(TalerButtonStyle(type: .prominent, disabled: 
disabled))
                 .disabled(disabled)
 //                .accessibility(sortPriority: 0)
         }.padding(.horizontal)
diff --git a/TalerWallet1/Views/Main/WalletEmptyView.swift 
b/TalerWallet1/Views/Main/WalletEmptyView.swift
index 1ecb492..ff72980 100644
--- a/TalerWallet1/Views/Main/WalletEmptyView.swift
+++ b/TalerWallet1/Views/Main/WalletEmptyView.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import SymLog
 
@@ -18,15 +21,22 @@ struct WalletEmptyView: View {
             Section {
                 Text("There is no digital cash in your wallet.")
                     .talerFont(.title3)
+            }
+            Section {
+                Text("You can register an account in the demo bank, then 
withdraw some digital cash to experience how to pay with the money of the 
future.")
+                    .talerFont(.body)
                     .listRowSeparator(.hidden)
-                let title = String(localized: "LinkTitle_Test_Money", 
defaultValue: "Get some test money")
+                let title = String(localized: "LinkTitle_Test_Money", 
defaultValue: "Get demo money")
                 Link(title, destination: URL(string: DEMOBANK)!)
                     .buttonStyle(TalerButtonStyle(type: .prominent, narrow: 
false, aligned: .center))
                     .padding(.vertical)
                     .accessibilityHint("Will go to the demo bank website.")
             }
             Section {
-                Text("Just register a test account in the demo bank, then 
withdraw some electronic cash.")
+                Text("Use the QR code scan button to start a withdrawal if 
your bank already supports Taler payments.")
+                    .talerFont(.body)
+                    .listRowSeparator(.hidden)
+                Text("You can also add a payment service manually on the 
Banking tab.")
                     .talerFont(.body)
             }
         }
@@ -34,7 +44,7 @@ struct WalletEmptyView: View {
         .talerFont(.title2)
         .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .onAppear() {
-            DebugViewC.shared.setViewID(VIEW_EMPTY, stack: 
stack.push("onAppear"))     // 10
+            DebugViewC.shared.setViewID(VIEW_EMPTY_WALLET, stack: 
stack.push("onAppear"))     // 10
         }
     }
 }
diff --git a/TalerWallet1/Views/Peer2peer/P2PSubjectV.swift 
b/TalerWallet1/Views/Peer2peer/P2PSubjectV.swift
index 05892b0..6982490 100644
--- a/TalerWallet1/Views/Peer2peer/P2PSubjectV.swift
+++ b/TalerWallet1/Views/Peer2peer/P2PSubjectV.swift
@@ -19,7 +19,7 @@ struct P2PSubjectV: View {
     private let symLog = SymLogV(0)
     let stack: CallStack
     let feeLabel: String?
-    let feeIsNotZero: Bool?            // nil = no fees at all, false = no fee 
for this tx
+    let feeIsNotZero: Bool?             // nil = no fees at all, false = no 
fee for this tx
     let currencyInfo: CurrencyInfo
     let amountToSend: Bool
     @Binding var amountToTransfer: Amount
@@ -89,10 +89,12 @@ struct P2PSubjectV: View {
                     .background(WalletColors().fieldBackground)
                     .textFieldStyle(.roundedBorder)
                     .onAppear {
-                        symLog.log("dispatching kbd...")
-                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
-                            isFocused = true        // make first responder - 
raise keybord
-                            symLog.log("...kbd isFocused")
+                        if !UIAccessibility.isVoiceOverRunning {
+                            symLog.log("dispatching kbd...")
+                            DispatchQueue.main.asyncAfter(deadline: .now() + 
0.7) {
+                                isFocused = true        // make first 
responder - raise keybord
+                                symLog.log("...kbd isFocused")
+                            }
                         }
                     }
                 Text(verbatim: "\(summary.count)/100")                         
 // maximum 100 characters
@@ -116,7 +118,7 @@ struct P2PSubjectV: View {
                 }) {
                     Text(buttonTitle(amountToTransfer, currencyInfo))
                 }
-                .buttonStyle(TalerButtonStyle(type: .prominent))
+                .buttonStyle(TalerButtonStyle(type: .prominent, disabled: 
disabled))
                 .disabled(disabled)
                 .accessibilityHint(disabled ? "enabled when subject and 
expiration are set" : EMPTYSTRING)
         }.padding(.horizontal) } // ScrollVStack
diff --git a/TalerWallet1/Views/Peer2peer/RequestPayment.swift 
b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
index b37109f..90a5bc8 100644
--- a/TalerWallet1/Views/Peer2peer/RequestPayment.swift
+++ b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
@@ -85,7 +85,7 @@ struct RequestPayment: View {
                         currencyInfo: currencyInfo,
                      amountEffective: peerPullCheck?.amountEffective)
             NavigationLink(destination: inputDestination) { Text("Next") }
-                .buttonStyle(TalerButtonStyle(type: .prominent))
+                .buttonStyle(TalerButtonStyle(type: .prominent, disabled: 
disabled))
                 .disabled(disabled)
                 .background(NavigationLink(destination: shortcutDestination, 
isActive: $buttonSelected)
                             { EmptyView() }.frame(width: 0).opacity(0).hidden()
diff --git a/TalerWallet1/Views/Settings/AboutView.swift 
b/TalerWallet1/Views/Settings/AboutView.swift
index 03c4ce7..dc0569e 100644
--- a/TalerWallet1/Views/Settings/AboutView.swift
+++ b/TalerWallet1/Views/Settings/AboutView.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import taler_swift
 import SymLog
@@ -38,7 +41,7 @@ struct AboutView: View {
 //                    .accessibilityLabel("Progress indicator")
                     .onTapGesture(count: 1) { rotationEnabled.toggle() }
                 SettingsItem(name: "Visit the taler.net website", id1: "web",
-                             description: minimalistic ? nil : 
String(localized: "More info about Gnu Taler in general...")) { }
+                             description: minimalistic ? nil : 
String(localized: "More info about GNU Taler in general...")) { }
                     .accessibilityAddTraits(.isLink)
                     .accessibilityRemoveTraits(.isStaticText)
                     .onTapGesture() {
diff --git 
a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift 
b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift
index 3fae19d..1c9ada3 100644
--- a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift
@@ -25,7 +25,7 @@ struct WithdrawTOSView: View {
     func loadToS(_ language: String) async {
         do {
             if let exchangeBaseUrl {
-                let acceptedFormat: [String] = [MARKDOWN, PLAINTEXT]
+                let acceptedFormat: [String] = [MARKDOWN, PLAINTEXT]      // 
MARKDOWN, HTML, PLAINTEXT
                 let someTOS = try await 
model.loadExchangeTermsOfServiceM(exchangeBaseUrl,
                                                            acceptedFormat: 
acceptedFormat,
                                                            acceptLanguage: 
language)
diff --git 
a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift 
b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
index 43b04c3..04685d8 100644
--- a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import taler_swift
 import SymLog
@@ -91,13 +94,13 @@ struct WithdrawURIView: View {
         .task {
             do { // TODO: cancelled
                 symLog.log(".task")
-                let withdrawUriInfo = try await 
model.loadWithdrawalDetailsForUriM(url.absoluteString)
+                let withdrawUriInfo = try await 
model.getWithdrawalDetailsForUriM(url.absoluteString)
                 let amount = withdrawUriInfo.amount
                 let baseUrl = withdrawUriInfo.defaultExchangeBaseUrl
                            ?? 
withdrawUriInfo.possibleExchanges.first?.exchangeBaseUrl
                 if let baseUrl, let exc = await model.getExchangeByUrl(url: 
baseUrl) {
                     exchange = exc
-                    let details = try await 
model.loadWithdrawalDetailsForAmountM(baseUrl, amount: amount)
+                    let details = try await 
model.getWithdrawalDetailsForAmountM(baseUrl, amount: amount)
                     withdrawalAmountDetails = details
 //                    agePicker.setAges(ages: details?.ageRestrictionOptions)
                 } else {    // TODO: error
diff --git a/TalerWallet1/Views/Sheets/WithdrawExchangeV.swift 
b/TalerWallet1/Views/Sheets/WithdrawExchangeV.swift
index 8859513..eab5268 100644
--- a/TalerWallet1/Views/Sheets/WithdrawExchangeV.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawExchangeV.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import taler_swift
 import SymLog
@@ -24,7 +27,7 @@ struct WithdrawExchangeV: View {
 #endif
         Group {
             if let exchangeBaseUrl {
-                ManualWithdraw(stack: stack.push(),
+                ManualWithdraw(stack: stack.push(), isSheet: true,
                      exchangeBaseUrl: exchangeBaseUrl,
                     amountToTransfer: $amountToTransfer)
             } else {
diff --git a/TalerWallet1/Views/Transactions/ThreeAmountsV.swift 
b/TalerWallet1/Views/Transactions/ThreeAmountsV.swift
index c7fbbf1..6e61e21 100644
--- a/TalerWallet1/Views/Transactions/ThreeAmountsV.swift
+++ b/TalerWallet1/Views/Transactions/ThreeAmountsV.swift
@@ -110,6 +110,7 @@ struct ThreeAmountsV: View {
             }
             if let baseURL {
                 VStack(alignment: .leading) {
+                               // TODO: "Issued by" for withdrawals
                     Text(minimalistic ? "Payment provider:" : "Using payment 
service provider:")
                         .multilineTextAlignment(.leading)
                         .talerFont(.body)
diff --git a/TalerWallet1/Views/Transactions/TransactionSummaryV.swift 
b/TalerWallet1/Views/Transactions/TransactionSummaryV.swift
index 050bf40..ccfb638 100644
--- a/TalerWallet1/Views/Transactions/TransactionSummaryV.swift
+++ b/TalerWallet1/Views/Transactions/TransactionSummaryV.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import taler_swift
 import SymLog
@@ -121,13 +124,10 @@ struct TransactionSummaryV: View {
                     .listRowSeparator(.hidden)
                 VStack(alignment: .trailing) {
                     let majorState = common.txState.major.localizedState
-#if DEBUG
                     let minorState = common.txState.minor?.localizedState ?? 
nil
-                    let state = transaction.isPending ? minorState ?? 
majorState
-                                                      : majorState
-#else
-                    let state = majorState
-#endif
+                    let state = developerMode ? transaction.isPending ? 
minorState ?? majorState
+                                                                      : 
majorState
+                                              : majorState
                     HStack {
                         Text(verbatim: "|")   // only reason for this 
leading-aligned text is to get a nice full length listRowSeparator
                             .accessibilityHidden(true)
diff --git a/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift 
b/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift
index e9569d4..cd849ef 100644
--- a/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import SymLog
 
@@ -26,7 +29,7 @@ struct TransactionsEmptyView: View {
 //        .padding(.vertical)
         .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .onAppear() {
-            DebugViewC.shared.setViewID(VIEW_EMPTY, stack: stack.push())     
// 10
+            DebugViewC.shared.setViewID(VIEW_EMPTY_HISTORY, stack: 
stack.push())     // 20
         }
     }
 }
diff --git a/TestFlight/WhatToTest.en-US.txt b/TestFlight/WhatToTest.en-US.txt
index 99cdbbb..592c67e 100644
--- a/TestFlight/WhatToTest.en-US.txt
+++ b/TestFlight/WhatToTest.en-US.txt
@@ -1,3 +1,26 @@
+Version 0.9.4 (11)
+
+• New views for empty wallet (Balances) and zero payment providers (Banking)
+
+
+Version 0.9.4 (10)
+
+• New feature: Native Networking instead of curl
+
+
+Version 0.9.4 (9)
+
+• debug version WITHOUT c-ares
+
+
+Version 0.9.4 (8)
+
+• New feature: LocalConsole for GNU Taler only. Find it in Settings...
+
+also GNU Taler no longer claims the taler:// URL scheme. You can now install 
GNU Taler for debugging/testing in parallel to Taler Wallet, and all System 
events (System Camera, Mail Messages, NFC Tags, ...) will always go to Taler 
Wallet.
+Thus you need to scan a QR code from another device (laptop, tablet, phone, 
...) to withdraw into GNU Taler, and cannot use the browser on this iPhone for 
bank-integrated withdrawals, because attempting to open a taler://withdraw/ 
will open Taler Wallet and not GNU Taler.
+
+
 Version 0.9.4 (7)
 
 • New feature: Pay-Templates

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