gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-ios] branch master updated (bb640f0 -> 65313c5)


From: gnunet
Subject: [taler-taler-ios] branch master updated (bb640f0 -> 65313c5)
Date: Mon, 16 Oct 2023 00:02:58 +0200

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 bb640f0  Link -> Button
     new 2f5555f  products seem no longer mandatory
     new 4f433c4  Layout for QR-View and ThreeAmounts
     new 06ab03e  Debugging
     new 32d47d8  CallStack
     new 8a5c684  No spellout
     new 38a00b9  More CallStack for Debugging
     new 4a76e43  AsyncSemaphore
     new 6fd6cd4  cleanup
     new 2ba3266  TabBar for Taler Wallet - GNU Taler stays on SideView
     new e6ce37d  secret token
     new f7e9c2e  bankAccessApiBaseUrl -> corebankApiBaseUrl
     new 696dfc8  AsyncSemaphore to serialize Getbalances
     new 8b08e6e  width of rendered string
     new 483e5fa  fix bars
     new 586eed9  TwoRowButtons
     new b1db8f7  Source of Truth for balances
     new 608250e  getCurrencySpecification
     new c6cd080  updateExchange
     new dab7694  AccessibilityNotification.Announcement
     new fb7b78b  Preparation for Exchange deposit only with positive balance
     new 86b0ee4  cleanup
     new 18c307f  badge
     new cb6b588  fix for broken scalable font (not finished)
     new a622afd  Balance button
     new c53edd4  Bargraph after Currency
     new 4fd028f  cleanup
     new f465963  SingleAxisGeometryReader
     new d45d020  Button Layout
     new 9b2f26f  BarGraph also for Exchanges
     new f307347  font
     new 32f9a51  comment
     new 65313c5  Bump version to 0.9.3 (20)

The 32 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              |  41 +++-
 TalerWallet1/Backend/WalletBackendRequest.swift    |   2 +-
 TalerWallet1/Controllers/Controller.swift          |   2 +-
 TalerWallet1/Controllers/DebugViewC.swift          |   2 +-
 TalerWallet1/Controllers/PublicConstants.swift     |  24 +-
 TalerWallet1/Helper/AsyncSemaphore.swift           | 253 +++++++++++++++++++++
 TalerWallet1/Helper/CallStack.swift                |   2 +-
 TalerWallet1/Helper/CurrencyFormatter.swift        |   2 +-
 TalerWallet1/Helper/Font+Taler.swift               | 216 ++++++++++++------
 TalerWallet1/Helper/TalerStrings.swift             |  13 +-
 TalerWallet1/Helper/WalletColors.swift             |  12 +-
 TalerWallet1/Model/Model+Balances.swift            |  21 +-
 TalerWallet1/Model/Model+Exchange.swift            |  36 ++-
 TalerWallet1/Model/Model+Settings.swift            |  74 +++---
 TalerWallet1/Model/WalletModel.swift               |   3 +-
 .../Views/Balances/BalanceRowButtons.swift         |  56 -----
 TalerWallet1/Views/Balances/BalanceRowView.swift   | 151 +++++++-----
 TalerWallet1/Views/Balances/BalancesListView.swift | 127 +++++------
 .../Views/Balances/BalancesSectionView.swift       |  66 +++---
 TalerWallet1/Views/Balances/TwoRowButtons.swift    |  50 ++++
 TalerWallet1/Views/Exchange/ExchangeListView.swift |  28 ++-
 .../Views/Exchange/ExchangeSectionView.swift       |  65 ++++--
 TalerWallet1/Views/Exchange/ManualWithdraw.swift   |   9 +-
 .../Views/Exchange/ManualWithdrawDone.swift        |   2 +-
 TalerWallet1/Views/HelperViews/AmountView.swift    |   1 -
 TalerWallet1/Views/HelperViews/BarGraph.swift      |  56 +++--
 TalerWallet1/Views/HelperViews/Buttons.swift       |  23 +-
 .../Views/HelperViews/QRCodeDetailView.swift       |  15 +-
 .../HelperViews/SingleAxisGeometryReader.swift}    |  34 ++-
 TalerWallet1/Views/HelperViews/ToSButtonView.swift |  10 +-
 .../Views/HelperViews/View+needVStack.swift        |  32 +++
 TalerWallet1/Views/Main/MainView.swift             | 113 +++++++--
 TalerWallet1/Views/Main/SideBarView.swift          |   4 +-
 TalerWallet1/Views/Main/WalletEmptyView.swift      |   5 +-
 TalerWallet1/Views/Peer2peer/PaymentPurpose.swift  |   2 +-
 TalerWallet1/Views/Peer2peer/RequestPayment.swift  |   2 +-
 TalerWallet1/Views/Peer2peer/SendAmount.swift      |   2 +-
 TalerWallet1/Views/Peer2peer/SendPurpose.swift     |   2 +-
 .../Settings/Pending/PendingOpsListView.swift      |   6 +-
 TalerWallet1/Views/Settings/SettingsItem.swift     |   2 +-
 TalerWallet1/Views/Settings/SettingsView.swift     |  30 ++-
 .../Sheets/P2P_Sheets/P2pReceiveURIView.swift      |   7 +-
 TalerWallet1/Views/Sheets/URLSheet.swift           |   6 +-
 .../WithdrawBankIntegrated/WithdrawTOSView.swift   |   3 +-
 .../WithdrawBankIntegrated/WithdrawURIView.swift   |   7 +-
 TalerWallet1/Views/Transactions/ThreeAmounts.swift |   8 +-
 .../Views/Transactions/TransactionDetailView.swift |   4 +-
 .../Views/Transactions/TransactionsEmptyView.swift |   5 +-
 .../Views/Transactions/TransactionsListView.swift  |   4 +-
 TestFlight/WhatToTest.en-US.txt                    |  18 ++
 taler-swift/Sources/taler-swift/Amount.swift       |  34 ++-
 51 files changed, 1191 insertions(+), 501 deletions(-)
 create mode 100644 TalerWallet1/Helper/AsyncSemaphore.swift
 delete mode 100644 TalerWallet1/Views/Balances/BalanceRowButtons.swift
 create mode 100644 TalerWallet1/Views/Balances/TwoRowButtons.swift
 copy TalerWallet1/{Helper/AnyTransition+backslide.swift => 
Views/HelperViews/SingleAxisGeometryReader.swift} (50%)
 create mode 100644 TalerWallet1/Views/HelperViews/View+needVStack.swift

diff --git a/TalerWallet.xcodeproj/project.pbxproj 
b/TalerWallet.xcodeproj/project.pbxproj
index efac9bd..dbec31f 100644
--- a/TalerWallet.xcodeproj/project.pbxproj
+++ b/TalerWallet.xcodeproj/project.pbxproj
@@ -10,6 +10,8 @@
                4E16E12329F3BB99008B9C86 /* CurrencyFormatter.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4E16E12229F3BB99008B9C86 /* 
CurrencyFormatter.swift */; };
                4E2254972A822B8100E41D29 /* payment_received.m4a in Resources 
*/ = {isa = PBXBuildFile; fileRef = 4E2254952A822B8100E41D29 /* 
payment_received.m4a */; };
                4E2254982A822B8100E41D29 /* payment_sent.m4a in Resources */ = 
{isa = PBXBuildFile; fileRef = 4E2254962A822B8100E41D29 /* payment_sent.m4a */; 
};
+               4E3327BA2AD1635100BF5AD6 /* AsyncSemaphore.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E3327B92AD1635100BF5AD6 /* 
AsyncSemaphore.swift */; };
+               4E3327BB2AD1635100BF5AD6 /* AsyncSemaphore.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E3327B92AD1635100BF5AD6 /* 
AsyncSemaphore.swift */; };
                4E363CBC2A237E0900D7E98C /* URL+id+iban.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E363CBB2A237E0900D7E98C /* URL+id+iban.swift 
*/; };
                4E363CBE2A23CB2100D7E98C /* AnyTransition+backslide.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E363CBD2A23CB2100D7E98C /* 
AnyTransition+backslide.swift */; };
                4E363CC02A24754200D7E98C /* Settings.bundle in Resources */ = 
{isa = PBXBuildFile; fileRef = 4E363CBF2A24754200D7E98C /* Settings.bundle */; 
};
@@ -68,7 +70,7 @@
                4E3EAE4C2A990778009F1BE8 /* AmountView.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB095492989CBFE0043A8A1 /* AmountView.swift */; 
};
                4E3EAE4D2A990778009F1BE8 /* P2pAcceptDone.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E3B4BC22A42252300CC88B8 /* P2pAcceptDone.swift 
*/; };
                4E3EAE4E2A990778009F1BE8 /* AnyTransition+backslide.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E363CBD2A23CB2100D7E98C /* 
AnyTransition+backslide.swift */; };
-               4E3EAE4F2A990778009F1BE8 /* BalanceRowButtons.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB065432A4CD1A80039B91D /* 
BalanceRowButtons.swift */; };
+               4E3EAE4F2A990778009F1BE8 /* TwoRowButtons.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB065432A4CD1A80039B91D /* TwoRowButtons.swift 
*/; };
                4E3EAE502A990778009F1BE8 /* Model+Transactions.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB095322989CBFE0043A8A1 /* 
Model+Transactions.swift */; };
                4E3EAE512A990778009F1BE8 /* Controller+playSound.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E578E912A481D8600F21F1C /* 
Controller+playSound.swift */; };
                4E3EAE522A990778009F1BE8 /* WalletEmptyView.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EB095392989CBFE0043A8A1 /* 
WalletEmptyView.swift */; };
@@ -165,10 +167,14 @@
                4E9320452A1645B600A87B0E /* RequestPayment.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E9320442A1645B600A87B0E /* 
RequestPayment.swift */; };
                4E9320472A164BC700A87B0E /* PaymentPurpose.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E9320462A164BC700A87B0E /* 
PaymentPurpose.swift */; };
                4E9796902A3765ED006F73BC /* AgePicker.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4E97968F2A3765ED006F73BC /* AgePicker.swift */; 
};
+               4E983C292ADBDD3500FA9CC5 /* SingleAxisGeometryReader.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E983C282ADBDD3500FA9CC5 /* 
SingleAxisGeometryReader.swift */; };
+               4E983C2A2ADBDD3500FA9CC5 /* SingleAxisGeometryReader.swift in 
Sources */ = {isa = PBXBuildFile; fileRef = 4E983C282ADBDD3500FA9CC5 /* 
SingleAxisGeometryReader.swift */; };
+               4E983C2C2ADC416800FA9CC5 /* View+needVStack.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E983C2B2ADC416800FA9CC5 /* 
View+needVStack.swift */; };
+               4E983C2D2ADC416800FA9CC5 /* View+needVStack.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4E983C2B2ADC416800FA9CC5 /* 
View+needVStack.swift */; };
                4EA1ABBE29A3833A008821EA /* PublicConstants.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = 4EA1ABBD29A3833A008821EA /* 
PublicConstants.swift */; };
                4EA551252A2C923600FEC9A8 /* CurrencyInputView.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EA551242A2C923600FEC9A8 /* 
CurrencyInputView.swift */; };
                4EAD117629F672FA008EDD0B /* KeyboardResponder.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EAD117529F672FA008EDD0B /* 
KeyboardResponder.swift */; };
-               4EB065442A4CD1A80039B91D /* BalanceRowButtons.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB065432A4CD1A80039B91D /* 
BalanceRowButtons.swift */; };
+               4EB065442A4CD1A80039B91D /* TwoRowButtons.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EB065432A4CD1A80039B91D /* TwoRowButtons.swift 
*/; };
                4EB094D629896CD20043A8A1 /* TalerWalletTests.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB094D429896CD20043A8A1 /* 
TalerWalletTests.swift */; };
                4EB094D729896CD20043A8A1 /* WalletBackendTests.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = 4EB094D529896CD20043A8A1 /* 
WalletBackendTests.swift */; };
                4EB094DC29896D030043A8A1 /* TalerWalletUITestsLaunchTests.swift 
in Sources */ = {isa = PBXBuildFile; fileRef = 4EB094D929896D030043A8A1 /* 
TalerWalletUITestsLaunchTests.swift */; };
@@ -284,6 +290,7 @@
                4E16E12229F3BB99008B9C86 /* CurrencyFormatter.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= CurrencyFormatter.swift; sourceTree = "<group>"; };
                4E2254952A822B8100E41D29 /* payment_received.m4a */ = {isa = 
PBXFileReference; lastKnownFileType = file; path = payment_received.m4a; 
sourceTree = "<group>"; };
                4E2254962A822B8100E41D29 /* payment_sent.m4a */ = {isa = 
PBXFileReference; lastKnownFileType = file; path = payment_sent.m4a; sourceTree 
= "<group>"; };
+               4E3327B92AD1635100BF5AD6 /* AsyncSemaphore.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= AsyncSemaphore.swift; sourceTree = "<group>"; };
                4E363CBB2A237E0900D7E98C /* URL+id+iban.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "URL+id+iban.swift"; sourceTree = "<group>"; };
                4E363CBD2A23CB2100D7E98C /* AnyTransition+backslide.swift */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.swift; path = "AnyTransition+backslide.swift"; sourceTree = 
"<group>"; };
                4E363CBF2A24754200D7E98C /* Settings.bundle */ = {isa = 
PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = 
Settings.bundle; sourceTree = "<group>"; };
@@ -330,10 +337,12 @@
                4E9320442A1645B600A87B0E /* RequestPayment.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= RequestPayment.swift; sourceTree = "<group>"; };
                4E9320462A164BC700A87B0E /* PaymentPurpose.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= PaymentPurpose.swift; sourceTree = "<group>"; };
                4E97968F2A3765ED006F73BC /* AgePicker.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= AgePicker.swift; sourceTree = "<group>"; };
+               4E983C282ADBDD3500FA9CC5 /* SingleAxisGeometryReader.swift */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.swift; path = SingleAxisGeometryReader.swift; sourceTree = 
"<group>"; };
+               4E983C2B2ADC416800FA9CC5 /* View+needVStack.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= "View+needVStack.swift"; sourceTree = "<group>"; };
                4EA1ABBD29A3833A008821EA /* PublicConstants.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
PublicConstants.swift; sourceTree = "<group>"; };
                4EA551242A2C923600FEC9A8 /* CurrencyInputView.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
CurrencyInputView.swift; sourceTree = "<group>"; };
                4EAD117529F672FA008EDD0B /* KeyboardResponder.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= KeyboardResponder.swift; sourceTree = "<group>"; };
-               4EB065432A4CD1A80039B91D /* BalanceRowButtons.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
BalanceRowButtons.swift; sourceTree = "<group>"; };
+               4EB065432A4CD1A80039B91D /* TwoRowButtons.swift */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.swift; path = 
TwoRowButtons.swift; sourceTree = "<group>"; };
                4EB094D429896CD20043A8A1 /* TalerWalletTests.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= TalerWalletTests.swift; sourceTree = "<group>"; };
                4EB094D529896CD20043A8A1 /* WalletBackendTests.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
path = WalletBackendTests.swift; sourceTree = "<group>"; };
                4EB094D929896D030043A8A1 /* TalerWalletUITestsLaunchTests.swift 
*/ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.swift; path = TalerWalletUITestsLaunchTests.swift; sourceTree = 
"<group>"; };
@@ -558,6 +567,7 @@
                        isa = PBXGroup;
                        children = (
                                4E363CBD2A23CB2100D7E98C /* 
AnyTransition+backslide.swift */,
+                               4E3327B92AD1635100BF5AD6 /* 
AsyncSemaphore.swift */,
                                4EDBDCD82AB787CB00925C02 /* CallStack.swift */,
                                4E16E12229F3BB99008B9C86 /* 
CurrencyFormatter.swift */,
                                4EAD117529F672FA008EDD0B /* 
KeyboardResponder.swift */,
@@ -679,7 +689,7 @@
                                4EB095372989CBFE0043A8A1 /* 
BalancesListView.swift */,
                                4EB0953A2989CBFE0043A8A1 /* 
BalancesSectionView.swift */,
                                4EB095362989CBFE0043A8A1 /* 
BalanceRowView.swift */,
-                               4EB065432A4CD1A80039B91D /* 
BalanceRowButtons.swift */,
+                               4EB065432A4CD1A80039B91D /* TwoRowButtons.swift 
*/,
                                4EB095382989CBFE0043A8A1 /* 
PendingRowView.swift */,
                                4E87C8742A34B411001C6406 /* 
UncompletedRowView.swift */,
                        );
@@ -721,6 +731,8 @@
                                4EEC157229F8242800D46A03 /* 
QRGeneratorView.swift */,
                                4E5A88F42A38A4FD00072618 /* 
QRCodeDetailView.swift */,
                                4E6EDD862A363D8D0031D520 /* ListStyle.swift */,
+                               4E983C282ADBDD3500FA9CC5 /* 
SingleAxisGeometryReader.swift */,
+                               4E983C2B2ADC416800FA9CC5 /* 
View+needVStack.swift */,
                                4EB095482989CBFE0043A8A1 /* 
TextFieldAlert.swift */,
                                4EBA82AA2A3EB2CA00E5F39A /* 
TransactionButton.swift */,
                                4EB095492989CBFE0043A8A1 /* AmountView.swift */,
@@ -1023,12 +1035,14 @@
                                4E3EAE252A990778009F1BE8 /* 
WithdrawAcceptDone.swift in Sources */,
                                4E3EAE262A990778009F1BE8 /* Transaction.swift 
in Sources */,
                                4E605DB72AB05E48002FB9A7 /* 
View+flippedDirection.swift in Sources */,
+                               4E983C2C2ADC416800FA9CC5 /* 
View+needVStack.swift in Sources */,
                                4E3EAE272A990778009F1BE8 /* WalletColors.swift 
in Sources */,
                                4E3EAE282A990778009F1BE8 /* 
BalancesListView.swift in Sources */,
                                4E3EAE292A990778009F1BE8 /* 
WalletBackendError.swift in Sources */,
                                4E3EAE2A2A990778009F1BE8 /* 
PendingRowView.swift in Sources */,
                                4E3EAE2B2A990778009F1BE8 /* LoadingView.swift 
in Sources */,
                                4E3EAE8C2AA0933C009F1BE8 /* Font+Taler.swift in 
Sources */,
+                               4E3327BA2AD1635100BF5AD6 /* 
AsyncSemaphore.swift in Sources */,
                                4E3EAE2C2A990778009F1BE8 /* 
ManualWithdraw.swift in Sources */,
                                4E3EAE2D2A990778009F1BE8 /* 
Model+Exchange.swift in Sources */,
                                4E3EAE2E2A990778009F1BE8 /* 
QRCodeDetailView.swift in Sources */,
@@ -1066,7 +1080,7 @@
                                4E3EAE4D2A990778009F1BE8 /* P2pAcceptDone.swift 
in Sources */,
                                4E3EAE4E2A990778009F1BE8 /* 
AnyTransition+backslide.swift in Sources */,
                                4EFA39602AA7946B00742548 /* ToSButtonView.swift 
in Sources */,
-                               4E3EAE4F2A990778009F1BE8 /* 
BalanceRowButtons.swift in Sources */,
+                               4E3EAE4F2A990778009F1BE8 /* TwoRowButtons.swift 
in Sources */,
                                4E3EAE502A990778009F1BE8 /* 
Model+Transactions.swift in Sources */,
                                4E3EAE512A990778009F1BE8 /* 
Controller+playSound.swift in Sources */,
                                4E3EAE522A990778009F1BE8 /* 
WalletEmptyView.swift in Sources */,
@@ -1076,6 +1090,7 @@
                                4E3EAE562A990778009F1BE8 /* 
LocalizedAlertError.swift in Sources */,
                                4E3EAE572A990778009F1BE8 /* quickjs.swift in 
Sources */,
                                4E3EAE582A990778009F1BE8 /* CurrencyField.swift 
in Sources */,
+                               4E983C292ADBDD3500FA9CC5 /* 
SingleAxisGeometryReader.swift in Sources */,
                                4E3EAE592A990778009F1BE8 /* 
Model+Settings.swift in Sources */,
                                4E3EAE5A2A990778009F1BE8 /* ErrorView.swift in 
Sources */,
                                4E3EAE5B2A990778009F1BE8 /* 
View+Notification.swift in Sources */,
@@ -1125,12 +1140,14 @@
                                4E5A88F72A3B9E5B00072618 /* 
WithdrawAcceptDone.swift in Sources */,
                                4EB095222989CBCB0043A8A1 /* Transaction.swift 
in Sources */,
                                4E605DB82AB05E48002FB9A7 /* 
View+flippedDirection.swift in Sources */,
+                               4E983C2D2ADC416800FA9CC5 /* 
View+needVStack.swift in Sources */,
                                4E9320432A14F6EA00A87B0E /* WalletColors.swift 
in Sources */,
                                4EB0955D2989CBFE0043A8A1 /* 
BalancesListView.swift in Sources */,
                                4EB095212989CBCB0043A8A1 /* 
WalletBackendError.swift in Sources */,
                                4EB0955E2989CBFE0043A8A1 /* 
PendingRowView.swift in Sources */,
                                4EB0956D2989CBFE0043A8A1 /* LoadingView.swift 
in Sources */,
                                4E3EAE8D2AA0933C009F1BE8 /* Font+Taler.swift in 
Sources */,
+                               4E3327BB2AD1635100BF5AD6 /* 
AsyncSemaphore.swift in Sources */,
                                4E50B3502A1BEE8000F9F01C /* 
ManualWithdraw.swift in Sources */,
                                4E3B4BC92A42BC4800CC88B8 /* 
Model+Exchange.swift in Sources */,
                                4E5A88F52A38A4FD00072618 /* 
QRCodeDetailView.swift in Sources */,
@@ -1168,7 +1185,7 @@
                                4E3B4BC32A42252300CC88B8 /* P2pAcceptDone.swift 
in Sources */,
                                4E363CBE2A23CB2100D7E98C /* 
AnyTransition+backslide.swift in Sources */,
                                4EFA39612AA7946B00742548 /* ToSButtonView.swift 
in Sources */,
-                               4EB065442A4CD1A80039B91D /* 
BalanceRowButtons.swift in Sources */,
+                               4EB065442A4CD1A80039B91D /* TwoRowButtons.swift 
in Sources */,
                                4EB095592989CBFE0043A8A1 /* 
Model+Transactions.swift in Sources */,
                                4E578E922A481D8600F21F1C /* 
Controller+playSound.swift in Sources */,
                                4EB0955F2989CBFE0043A8A1 /* 
WalletEmptyView.swift in Sources */,
@@ -1178,6 +1195,7 @@
                                4E363CC22A2621C200D7E98C /* 
LocalizedAlertError.swift in Sources */,
                                4EB0950E2989CB9A0043A8A1 /* quickjs.swift in 
Sources */,
                                4E53A33729F50B7B00830EC2 /* CurrencyField.swift 
in Sources */,
+                               4E983C2A2ADBDD3500FA9CC5 /* 
SingleAxisGeometryReader.swift in Sources */,
                                4EB095152989CBB00043A8A1 /* 
Model+Settings.swift in Sources */,
                                4EB095692989CBFE0043A8A1 /* ErrorView.swift in 
Sources */,
                                4E3B4BC72A429F2A00CC88B8 /* 
View+Notification.swift in Sources */,
@@ -1255,7 +1273,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 18;
+                               CURRENT_PROJECT_VERSION = 20;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1295,7 +1313,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 18;
+                               CURRENT_PROJECT_VERSION = 20;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1435,6 +1453,7 @@
                                MTL_FAST_MATH = YES;
                                OTHER_LDFLAGS = "-lc++";
                                SDKROOT = iphoneos;
+                               SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
                                SWIFT_COMPILATION_MODE = wholemodule;
                                SWIFT_OPTIMIZATION_LEVEL = "-O";
                                SWIFT_STRICT_CONCURRENCY = complete;
@@ -1451,7 +1470,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 18;
+                               CURRENT_PROJECT_VERSION = 20;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1476,6 +1495,7 @@
                                SUPPORTED_PLATFORMS = "iphoneos 
iphonesimulator";
                                SUPPORTS_MACCATALYST = NO;
                                SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+                               SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG 
TABBAR";
                                SWIFT_EMIT_LOC_STRINGS = YES;
                                TARGETED_DEVICE_FAMILY = 1;
                                VALIDATE_WORKSPACE = YES;
@@ -1491,7 +1511,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 18;
+                               CURRENT_PROJECT_VERSION = 20;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1516,6 +1536,7 @@
                                SUPPORTED_PLATFORMS = "iphoneos 
iphonesimulator";
                                SUPPORTS_MACCATALYST = NO;
                                SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+                               SWIFT_ACTIVE_COMPILATION_CONDITIONS = TABBAR;
                                SWIFT_EMIT_LOC_STRINGS = YES;
                                TARGETED_DEVICE_FAMILY = 1;
                                VALIDATE_WORKSPACE = YES;
diff --git a/TalerWallet1/Backend/WalletBackendRequest.swift 
b/TalerWallet1/Backend/WalletBackendRequest.swift
index ec9d2ca..3f4cedc 100644
--- a/TalerWallet1/Backend/WalletBackendRequest.swift
+++ b/TalerWallet1/Backend/WalletBackendRequest.swift
@@ -102,7 +102,7 @@ struct OrderShortInfo: Codable {
     var merchant: Merchant
     var summary: String
 //    var summary_i18n:   ?
-    var products: [Product]
+    var products: [Product]?
     var fulfillmentUrl: String?
     var fulfillmentMessage: String?
 //    var fulfillmentMessage_i18n:    ?
diff --git a/TalerWallet1/Controllers/Controller.swift 
b/TalerWallet1/Controllers/Controller.swift
index 35c2c6a..f408084 100644
--- a/TalerWallet1/Controllers/Controller.swift
+++ b/TalerWallet1/Controllers/Controller.swift
@@ -74,7 +74,7 @@ class Controller: ObservableObject {
 
 // MARK: -
 extension Controller {
-    func openURL(_ url:URL) -> UrlCommand {
+    func openURL(_ url: URL, stack: CallStack) -> UrlCommand {
         symLog.log(url)
         guard let scheme = url.scheme else {return UrlCommand.unknown}
         var uncrypted = false
diff --git a/TalerWallet1/Controllers/DebugViewC.swift 
b/TalerWallet1/Controllers/DebugViewC.swift
index b1dee53..78ef45b 100644
--- a/TalerWallet1/Controllers/DebugViewC.swift
+++ b/TalerWallet1/Controllers/DebugViewC.swift
@@ -146,7 +146,7 @@ class DebugViewC: ObservableObject {
     @Published var viewID: Int = 0
     @Published var sheetID: Int = 0
 
-    @MainActor func setViewID(_ newID: Int) -> Void {
+    @MainActor func setViewID(_ newID: Int, stack: CallStack) -> Void {
         if developerMode {
             if viewID == 0 {
                 symLog.log("switching ON, \(newID)")
diff --git a/TalerWallet1/Controllers/PublicConstants.swift 
b/TalerWallet1/Controllers/PublicConstants.swift
index e6a807e..0271bdc 100644
--- a/TalerWallet1/Controllers/PublicConstants.swift
+++ b/TalerWallet1/Controllers/PublicConstants.swift
@@ -14,19 +14,23 @@ public let THIRTYDAYS: UInt = 30    // 10..30
 public let HTTPS = "https://";
 //public let DEMOBANK = HTTPS + "bAnK.dEmO.tAlEr.nEt"             // should be 
weird to read, but still work
 //public let DEMOEXCHANGE = HTTPS + "eXcHaNgE.dEmO.tAlEr.nEt"
-public let DEMOBANK = HTTPS + "bank.demo.taler.net"
-public let DEMOSHOP = HTTPS + "shop.demo.taler.net"
-public let DEMOBACKEND = HTTPS + "backend.demo.taler.net"
-public let DEMOEXCHANGE = HTTPS + "exchange.demo.taler.net"
-public let TESTBANK = HTTPS + "bank.test.taler.net"
-public let TESTSHOP = HTTPS + "shop.test.taler.net"
-public let TESTBACKEND = HTTPS + "backend.test.taler.net"
-public let TESTEXCHANGE = HTTPS + "exchange.test.taler.net"
+public let DEMO = ".demo.taler.net"
+public let TEST = ".test.taler.net"
+
+public let DEMOBANK = HTTPS + "bank" + DEMO
+public let DEMOSHOP = HTTPS + "shop" + DEMO
+public let DEMOBACKEND = HTTPS + "backend" + DEMO
+public let DEMOEXCHANGE = HTTPS + "exchange" + DEMO
+public let TESTBANK = HTTPS + "bank" + TEST
+public let TESTSHOP = HTTPS + "shop" + TEST
+public let TESTBACKEND = HTTPS + "backend" + TEST
+public let TESTEXCHANGE = HTTPS + "exchange" + TEST
+
 public let ARS_AGE_EXCHANGE = HTTPS + "exchange-age.taler.ar"
 public let ARS_EXP_EXCHANGE = HTTPS + "exchange-expensive.taler.ar"
 public let DEMOCURRENCY = "KUDOS"
-//public let TESTCURRENCY = "TESTKUDOS"
-public let TESTCURRENCY = "KUDOS"
+public let TESTCURRENCY = "TESTKUDOS"
+//public let TESTCURRENCY = "KUDOS"
 //public let LONGCURRENCY = "gold-pressed Latinum"                // 20 
characters, with dash and space
 public let LONGCURRENCY = "GOLDLATINUM"                         // 11 
characters, no dash, no space
 
diff --git a/TalerWallet1/Helper/AsyncSemaphore.swift 
b/TalerWallet1/Helper/AsyncSemaphore.swift
new file mode 100644
index 0000000..8ab5bdc
--- /dev/null
+++ b/TalerWallet1/Helper/AsyncSemaphore.swift
@@ -0,0 +1,253 @@
+// Copyright (C) 2022 Gwendal Roué
+//
+// 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
+
+/// An object that controls access to a resource across multiple execution
+/// contexts through use of a traditional counting semaphore.
+///
+/// You increment a semaphore count by calling the ``signal()`` method, and
+/// decrement a semaphore count by calling ``wait()`` or one of its variants.
+///
+/// ## Topics
+///
+/// ### Creating a Semaphore
+///
+/// - ``init(value:)``
+///
+/// ### Signaling the Semaphore
+///
+/// - ``signal()``
+///
+/// ### Waiting for the Semaphore
+///
+/// - ``wait()``
+/// - ``waitUnlessCancelled()``
+public final class AsyncSemaphore: @unchecked Sendable {
+    /// `Suspension` is the state of a task waiting for a signal.
+    ///
+    /// It is a class because instance identity helps `waitUnlessCancelled()`
+    /// deal with both early and late cancellation.
+    ///
+    /// We make it @unchecked Sendable in order to prevent compiler warnings:
+    /// instances are always protected by the semaphore's lock.
+    private class Suspension: @unchecked Sendable {
+        enum State {
+            /// Initial state. Next is suspendedUnlessCancelled, or cancelled.
+            case pending
+            
+            /// Waiting for a signal, with support for cancellation.
+            case suspendedUnlessCancelled(UnsafeContinuation<Void, Error>)
+            
+            /// Waiting for a signal, with no support for cancellation.
+            case suspended(UnsafeContinuation<Void, Never>)
+            
+            /// Cancelled before we have started waiting.
+            case cancelled
+        }
+        
+        var state: State
+        
+        init(state: State) {
+            self.state = state
+        }
+    }
+    
+    // MARK: - Internal State
+    
+    /// The semaphore value.
+    private var value: Int
+    
+    /// As many elements as there are suspended tasks waiting for a signal.
+    private var suspensions: [Suspension] = []
+    
+    /// The lock that protects `value` and `suspensions`.
+    ///
+    /// It is recursive in order to handle cancellation (see the implementation
+    /// of ``waitUnlessCancelled()``).
+    private let _lock = NSRecursiveLock()
+    
+    // MARK: - Creating a Semaphore
+    
+    /// Creates a semaphore.
+    ///
+    /// - parameter value: The starting value for the semaphore. Do not pass a
+    ///   value less than zero.
+    public init(value: Int) {
+        precondition(value >= 0, "AsyncSemaphore requires a value equal or 
greater than zero")
+        self.value = value
+    }
+    
+    deinit {
+        precondition(suspensions.isEmpty, "AsyncSemaphore is deallocated while 
some task(s) are suspended waiting for a signal.")
+    }
+    
+    // MARK: - Locking
+    
+    // Let's hide the locking primitive in order to avoid a compiler warning:
+    //
+    // > Instance method 'lock' is unavailable from asynchronous contexts;
+    // > Use async-safe scoped locking instead; this is an error in Swift 6.
+    //
+    // We're not sweeping bad stuff under the rug. We really need to protect
+    // our inner state (`value` and `suspension`) across the calls to
+    // `withUnsafeContinuation`. Unfortunately, this method introduces a
+    // suspension point. So we need a lock.
+    private func lock() { _lock.lock() }
+    private func unlock() { _lock.unlock() }
+    
+    // MARK: - Waiting for the Semaphore
+    
+    /// Waits for, or decrements, a semaphore.
+    ///
+    /// Decrement the counting semaphore. If the resulting value is less than
+    /// zero, this function suspends the current task until a signal occurs,
+    /// without blocking the underlying thread. Otherwise, no suspension 
happens.
+    public func wait() async {
+        lock()
+        
+        value -= 1
+        if value >= 0 {
+            unlock()
+            return
+        }
+        
+        await withUnsafeContinuation { continuation in
+            // Register the continuation that `signal` will resume.
+            let suspension = Suspension(state: .suspended(continuation))
+            suspensions.insert(suspension, at: 0) // FIFO
+            unlock()
+        }
+    }
+    
+    /// Waits for, or decrements, a semaphore, with support for cancellation.
+    ///
+    /// Decrement the counting semaphore. If the resulting value is less than
+    /// zero, this function suspends the current task until a signal occurs,
+    /// without blocking the underlying thread. Otherwise, no suspension 
happens.
+    ///
+    /// If the task is canceled before a signal occurs, this function
+    /// throws `CancellationError`.
+    public func waitUnlessCancelled() async throws {
+        lock()
+        
+        value -= 1
+        if value >= 0 {
+            defer { unlock() }
+            
+            do {
+                // All code paths check for cancellation
+                try Task.checkCancellation()
+            } catch {
+                // Cancellation is like a signal: we don't really "consume"
+                // the semaphore, and restore the value.
+                value += 1
+                throw error
+            }
+            
+            return
+        }
+        
+        // Get ready for being suspended waiting for a continuation, or for
+        // early cancellation.
+        let suspension = Suspension(state: .pending)
+        
+        try await withTaskCancellationHandler {
+            try await withUnsafeThrowingContinuation { (continuation: 
UnsafeContinuation<Void, Error>) in
+                if case .cancelled = suspension.state {
+                    // Early cancellation: waitUnlessCancelled() is called from
+                    // a cancelled task, and the `onCancel` closure below
+                    // has marked the suspension as cancelled.
+                    // Resume with a CancellationError.
+                    unlock()
+                    continuation.resume(throwing: CancellationError())
+                } else {
+                    // Current task is not cancelled: register the continuation
+                    // that `signal` will resume.
+                    suspension.state = .suspendedUnlessCancelled(continuation)
+                    suspensions.insert(suspension, at: 0) // FIFO
+                    unlock()
+                }
+            }
+        } onCancel: {
+            // withTaskCancellationHandler may immediately call this block (if
+            // the current task is cancelled), or call it later (if the task is
+            // cancelled later). In the first case, we're still holding the 
lock,
+            // waiting for the continuation. In the second case, we do not hold
+            // the lock. Being able to handle both situations is the reason why
+            // we use a recursive lock.
+            lock()
+            
+            // We're no longer waiting for a signal
+            value += 1
+            if let index = suspensions.firstIndex(where: { $0 === suspension 
}) {
+                suspensions.remove(at: index)
+            }
+            
+            if case let .suspendedUnlessCancelled(continuation) = 
suspension.state {
+                // Late cancellation: the task is cancelled while waiting
+                // from the semaphore. Resume with a CancellationError.
+                unlock()
+                continuation.resume(throwing: CancellationError())
+            } else {
+                // Early cancellation: waitUnlessCancelled() is called from
+                // a cancelled task.
+                //
+                // The next step is the `withTaskCancellationHandler`
+                // operation closure right above.
+                suspension.state = .cancelled
+                unlock()
+            }
+        }
+    }
+    
+    // MARK: - Signaling the Semaphore
+    
+    /// Signals (increments) a semaphore.
+    ///
+    /// Increment the counting semaphore. If the previous value was less than
+    /// zero, this function resumes a task currently suspended in ``wait()``
+    /// or ``waitUnlessCancelled()``.
+    ///
+    /// - returns: This function returns true if a suspended task is
+    ///   resumed. Otherwise, the result is false, meaning that no task was
+    ///   waiting for the semaphore.
+    @discardableResult
+    public func signal() -> Bool {
+        lock()
+        
+        value += 1
+        
+        switch suspensions.popLast()?.state { // FIFO
+        case let .suspendedUnlessCancelled(continuation):
+            unlock()
+            continuation.resume()
+            return true
+        case let .suspended(continuation):
+            unlock()
+            continuation.resume()
+            return true
+        default:
+            unlock()
+            return false
+        }
+    }
+}
diff --git a/TalerWallet1/Helper/CallStack.swift 
b/TalerWallet1/Helper/CallStack.swift
index fb5d0ec..8fa91ca 100644
--- a/TalerWallet1/Helper/CallStack.swift
+++ b/TalerWallet1/Helper/CallStack.swift
@@ -65,7 +65,7 @@ extension CallStack {
 #else
     init(_ message: String = "") {
         let item = CallStackItem(message: message)
-        self.storage = [item]
+        self.stack = [item]
     }
 #endif
 #if DEBUG
diff --git a/TalerWallet1/Helper/CurrencyFormatter.swift 
b/TalerWallet1/Helper/CurrencyFormatter.swift
index b997731..2a56f33 100644
--- a/TalerWallet1/Helper/CurrencyFormatter.swift
+++ b/TalerWallet1/Helper/CurrencyFormatter.swift
@@ -13,7 +13,7 @@ public class CurrencyFormatter: NumberFormatter {
         self.locale = Locale.current
         self.numberStyle = .currency            // currencyISOCode, 
currencyPlural, (currencyAccounting)
         self.numberStyle = .currencyISOCode
-        self.numberStyle = .spellOut
+//        self.numberStyle = .spellOut
 
 //        self.currencyCode = code              // EUR, USD, JPY, GBP
 //        self.minimumFractionDigits = fractionDigits
diff --git a/TalerWallet1/Helper/Font+Taler.swift 
b/TalerWallet1/Helper/Font+Taler.swift
index 64e32c8..31f0190 100644
--- a/TalerWallet1/Helper/Font+Taler.swift
+++ b/TalerWallet1/Helper/Font+Taler.swift
@@ -10,78 +10,122 @@ fileprivate let ATKINSON    = "AtkinsonHyperlegible-"
 fileprivate let NUNITO      = "Nunito-"
 
 fileprivate let REGULAR     = "Regular"
+fileprivate let ITALIC      = "Italic"
 fileprivate let BOLD        = "Bold"
+fileprivate let BOLDITALIC  = "BoldItalic"
 fileprivate let BLACK       = "Black"
 fileprivate let BLACKITALIC = "BlackItalic"
-fileprivate let BOLDITALIC  = "BoldItalic"
-fileprivate let ITALIC      = "Italic"
 
-struct TalerFont {
-    static var atkinson: Font {
-        Font.custom(ATKINSON + REGULAR, size: 24, relativeTo: .title2)
+extension UIFont {
+    /// scalable system font for style and weight (and italic)
+    /// https://stackoverflow.com/users/2145198/beebcon
+    static func preferredFont(for style: TextStyle, weight: Weight, italic: 
Bool = false) -> UIFont {
+        @Environment(\.sizeCategory) var sizeCategory
+
+        // Get the style's default pointSize
+        let traits = UITraitCollection(preferredContentSizeCategory: .large)
+        let desc = UIFontDescriptor.preferredFontDescriptor(withTextStyle: 
style, compatibleWith: traits)
+
+        // Get the font at the default size and preferred weight
+        var font = UIFont.systemFont(ofSize: desc.pointSize, weight: weight)
+        if italic == true {
+            font = font.with([.traitItalic])
+        }
+
+        // Setup the font to be auto-scalable
+        let metrics = UIFontMetrics(forTextStyle: style)
+        return metrics.scaledFont(for: font)
     }
-    static var nunito: Font {
-        Font.custom(NUNITO + REGULAR, size: 24, relativeTo: .title2)
+
+    private func with(_ traits: UIFontDescriptor.SymbolicTraits...) -> UIFont {
+        guard let descriptor = 
fontDescriptor.withSymbolicTraits(UIFontDescriptor.SymbolicTraits(traits).union(fontDescriptor.symbolicTraits))
 else {
+            return self
+        }
+        return UIFont(descriptor: descriptor, size: 0)
     }
-    static var nunitoItalic: Font {
-        Font.custom(NUNITO + ITALIC, size: 24, relativeTo: .title2)
+}
+// Use it like this:
+//    UIFont.preferredFont(for: .largeTitle, weight: .regular)
+//    UIFont.preferredFont(for: .headline, weight: .semibold, italic: true)
+
+
+
+/// provides a (custom) scalable UIFont based on the first parameter: 0 = 
system, 1 = Atkinson, 2 = Nunito, 3 = NunitoItalic
+struct TalerFont {
+    @Environment(\.legibilityWeight) private var legibilityWeight: 
LegibilityWeight?
+
+    private static func scalableSystemFont(for style: UIFont.TextStyle, 
legibilityBold: Bool = false,
+                                           bold: Bool = false, italic: Bool = 
false) -> UIFont {
+        let black = bold && legibilityBold
+        return UIFont.preferredFont(for: style,
+                                 weight: black ? .heavy
+                                               : (bold || legibilityBold) ? 
.semibold : .regular,
+                                 italic: italic)
     }
 
-    static func atkinson(size: CGFloat, relativeTo style: UIFont.TextStyle) -> 
UIFont {
-        if let font = UIFont(name: ATKINSON + REGULAR, size: size) {
+    /// check wether a custom font for fontName is available
+    /// fontName already contains "Bold" (instead of "Regular") - the bold and 
italic params are only for the fallback
+    private static func scalableUIFont(_ fontName: String, size: CGFloat, 
relativeTo style: UIFont.TextStyle,
+                                       legibilityBold: Bool = false, bold: 
Bool = false, italic: Bool = false) -> UIFont {
+        @Environment(\.sizeCategory) var sizeCategory
+        if let font = UIFont(name: fontName, size: size) {
+            // return a scalable UIFont
             let fontMetrics = UIFontMetrics(forTextStyle: style)
             return fontMetrics.scaledFont(for: font)
         } else {
-            return UIFont.preferredFont(forTextStyle: style)
+            // fallback: return the system font
+            return scalableSystemFont(for: style, legibilityBold: 
legibilityBold, bold: bold, italic: italic)
         }
     }
-    static func nunito(size: CGFloat, relativeTo: UIFont.TextStyle) -> UIFont {
-        if let font = UIFont(name: NUNITO + REGULAR, size: size) {
-            let fontMetrics = UIFontMetrics(forTextStyle: relativeTo)
-            return fontMetrics.scaledFont(for: font)
-        } else {
-            return UIFont.preferredFont(forTextStyle: relativeTo)
-        }
+
+    private static func atkinson(size: CGFloat, relativeTo style: 
UIFont.TextStyle,
+                                 legibilityBold: Bool = false, bold: Bool = 
false, italic: Bool = false) -> UIFont {
+        let useBold = bold || legibilityBold
+        let fontName = ATKINSON + (italic ? (useBold ? BOLDITALIC : ITALIC)
+                                          : (useBold ? BOLD : REGULAR))
+        return scalableUIFont(fontName, size: size, relativeTo: style,
+                              legibilityBold: legibilityBold, bold: bold, 
italic: italic)
     }
-    static func nunitoItalic(size: CGFloat, relativeTo: UIFont.TextStyle) -> 
UIFont {
-        if let font = UIFont(name: NUNITO + ITALIC, size: size) {
-            let fontMetrics = UIFontMetrics(forTextStyle: relativeTo)
-            return fontMetrics.scaledFont(for: font)
-        } else {
-            return UIFont.preferredFont(forTextStyle: relativeTo)
-        }
+
+    private static func nunito(size: CGFloat, relativeTo style: 
UIFont.TextStyle,
+                               legibilityBold: Bool = false, bold: Bool = 
false, italic: Bool = false) -> UIFont {
+        let black = bold && legibilityBold
+        let fontName = NUNITO + (italic ? (black ? BLACKITALIC
+                                                 : (bold || legibilityBold) ? 
BOLDITALIC : ITALIC)
+                                        : (black ? BLACK
+                                                 : (bold || legibilityBold) ? 
BOLD : REGULAR))
+        return scalableUIFont(fontName, size: size, relativeTo: style,
+                              legibilityBold: legibilityBold, bold: bold, 
italic: italic)
     }
 
-    static func talerFont(_ talerFont: Int, size: CGFloat, relativeTo style: 
UIFont.TextStyle) -> UIFont {
-        if talerFont != 0 {
-            let uiFont: UIFont = (talerFont == 1) ? TalerFont.atkinson(size: 
size, relativeTo: style)
-                               : (talerFont == 2) ? TalerFont.nunito(size: 
size, relativeTo: style)
-                                                  : 
TalerFont.nunitoItalic(size: size, relativeTo: style)
-            return uiFont
-        } else {
-            return UIFont.preferredFont(forTextStyle: style)
+    static func uiFont(_ selectedFont: Int, size: CGFloat, relativeTo style: 
UIFont.TextStyle,
+                legibilityBold: Bool = false, bold: Bool = false, italic: Bool 
= false) -> UIFont {
+        switch selectedFont {
+            case 1: return TalerFont.atkinson(size: size, relativeTo: style,
+                                              legibilityBold: legibilityBold, 
bold: bold, italic: italic)
+            case 2: return TalerFont.nunito(size: size, relativeTo: style,
+                                            legibilityBold: legibilityBold, 
bold: bold, italic: italic)
+            default:
+//                return UIFont.preferredFont(forTextStyle: style)
+                return TalerFont.scalableSystemFont(for: style, 
legibilityBold: legibilityBold, bold: bold, italic: italic)
         }
     }
+
+    static func uiFont(_ styleSize: StyleSizeBold) -> UIFont {
+        return uiFont(Controller.shared.talerFont, size: styleSize.size, 
relativeTo: styleSize.style)
+    }
 }
 
-struct AccessibleFont {
+struct AccessibleFont {   // old running
     var regular: Font
     var bold: Font
-    static var talerFont: Int {
-        if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] 
== "1" {
-            return 3
-        } else {
-            return Controller.shared.talerFont
-        }
-    }
+    static var talerFont: Int { return 2 }
 
     init(_ base: String, size: CGFloat, relativeTo: Font.TextStyle, isBold: 
Bool = false) {
         if AccessibleFont.talerFont == 0 {
             self.regular = .system(relativeTo)
-            self.bold = .system(relativeTo).bold()      // why is this allowed 
here (iOS-15) ??? should give compiler error
-                                                        // bold() for Font 
needs iOS-16
-                                                        // Text has a function 
bold(), needs iOS-13, but that shouldn't matter here
-        } else if isBold && AccessibleFont.talerFont >= 2 {
+            self.bold = .system(relativeTo).bold()
+        } else if isBold {
             // Nunito has Black Variants, but AtkinsonHyperlegible doesn't
             self.regular = Font.custom(base + (AccessibleFont.talerFont == 2 ? 
BOLD : BOLDITALIC), size: size, relativeTo: relativeTo)
             self.bold = Font.custom(base + (AccessibleFont.talerFont == 2 ? 
BLACK : BLACKITALIC), size: size, relativeTo: relativeTo)
@@ -98,26 +142,33 @@ struct AccessibleFont {
 
     func value(_ legibilityWeight: LegibilityWeight?) -> Font {
         switch legibilityWeight {
-            case .bold:     // should increase Font.Weight by 2
-                // ultraLight => light
-                // thin => regular
-                // light => medium
-                // regular => semibold
-                // medium => bold
-                // semibold => heavy
-                // bold => black
-                return bold
-            default:
-                return regular
+            case .bold: return bold
+            default:    return regular
         }
     }
 }
 
-extension AccessibleFont {
-    static var fontName: String {
-        (talerFont == 1) ? ATKINSON
-                         : NUNITO
-    }
+struct StyleSizeBold {
+    let style: UIFont.TextStyle
+    let size: CGFloat
+    let bold: Bool
+    let italic: Bool = false
+
+    static var largeTitle:  StyleSizeBold { StyleSizeBold(style: .largeTitle, 
size: 38, bold: false) }      // 34 -> 38
+    static var title:       StyleSizeBold { StyleSizeBold(style: .title1, 
size: 31, bold: false) }          // 28 -> 31
+    static var title2:      StyleSizeBold { StyleSizeBold(style: .title2, 
size: 25, bold: false) }          // 22 -> 25
+    static var title3:      StyleSizeBold { StyleSizeBold(style: .title3, 
size: 23, bold: false) }          // 20 -> 23
+    static var headline:    StyleSizeBold { StyleSizeBold(style: .headline, 
size: 19, bold: true) }         // 17 bold -> 19 bold
+    static var body:        StyleSizeBold { StyleSizeBold(style: .body, size: 
19, bold: false) }            // 17 -> 19
+    static var callout:     StyleSizeBold { StyleSizeBold(style: .callout, 
size: 18, bold: false) }         // 16 -> 18
+    static var subheadline: StyleSizeBold { StyleSizeBold(style: .subheadline, 
size: 17, bold: false) }     // 15 -> 17
+    static var footnote:    StyleSizeBold { StyleSizeBold(style: .footnote, 
size: 15, bold: false) }        // 13 -> 15
+    static var caption:     StyleSizeBold { StyleSizeBold(style: .caption1, 
size: 13, bold: false) }        // 12 -> 13
+//    static var caption2:    AccessibleFont { AccessibleFont(fontName, size: 
12, relativeTo: .caption2) }    // 11 -> 12
+}
+
+extension AccessibleFont {   // old running
+    static var fontName: String { NUNITO }
 
     static var largeTitle:  AccessibleFont { AccessibleFont(fontName, size: 
38, relativeTo: .largeTitle) }  // 34 -> 38
     static var title:       AccessibleFont { AccessibleFont(fontName, size: 
31, relativeTo: .title) }       // 28 -> 31
@@ -129,10 +180,30 @@ extension AccessibleFont {
     static var subheadline: AccessibleFont { AccessibleFont(fontName, size: 
17, relativeTo: .subheadline) } // 15 -> 17
     static var footnote:    AccessibleFont { AccessibleFont(fontName, size: 
15, relativeTo: .footnote) }    // 13 -> 15
     static var caption:     AccessibleFont { AccessibleFont(fontName, size: 
13, relativeTo: .caption) }     // 12 -> 13
-//    static var caption2:    AccessibleFont { AccessibleFont(fontName, size: 
12, relativeTo: .caption2) }    // 11 -> 12
 }
 
-struct AccessibilityFontViewModifier: ViewModifier {
+struct StyleSizeBoldViewModifier: ViewModifier {
+    @Environment(\.legibilityWeight) private var legibilityWeight
+    var legibilityBold: Bool { legibilityWeight == .bold }
+
+    let styleSize: StyleSizeBold
+
+    static var talerFont: Int {
+        if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] 
== "1" {
+            return 2
+        } else {
+            return Controller.shared.talerFont
+        }
+    }
+
+    func body(content: Content) -> some View {      // TODO: italic
+        let uiFont = TalerFont.uiFont(Self.talerFont, size: styleSize.size, 
relativeTo: styleSize.style,
+                                      legibilityBold: legibilityBold, bold: 
styleSize.bold)
+        content.font(Font(uiFont))
+    }
+}
+
+struct AccessibilityFontViewModifier2: ViewModifier {   // old running
     @Environment(\.legibilityWeight) private var legibilityWeight
 
     var font: AccessibleFont
@@ -144,7 +215,10 @@ struct AccessibilityFontViewModifier: ViewModifier {
 
 extension View {
     func accessibilityFont(_ font: AccessibleFont) -> some View {
-        return self.modifier(AccessibilityFontViewModifier(font: font))
+        return self.modifier(AccessibilityFontViewModifier2(font: font))
+    }
+    func accessibilityFont1(_ styleSize: StyleSizeBold) -> some View {
+        return self.modifier(StyleSizeBoldViewModifier(styleSize: styleSize))
     }
 }
 // MARK: -
@@ -174,8 +248,8 @@ struct TalerNavBar: ViewModifier {
         navBarAppearance.titleTextAttributes = nil
         navBarAppearance.largeTitleTextAttributes = nil
         if talerFont != 0 {
-            navBarAppearance.titleTextAttributes = [.font: 
TalerFont.talerFont(talerFont, size: 24, relativeTo: .title2)]
-            navBarAppearance.largeTitleTextAttributes = [.font: 
TalerFont.talerFont(talerFont, size: 38, relativeTo: .largeTitle)]
+            navBarAppearance.titleTextAttributes = [.font: 
TalerFont.uiFont(talerFont, size: 24, relativeTo: .title2)]
+            navBarAppearance.largeTitleTextAttributes = [.font: 
TalerFont.uiFont(talerFont, size: 38, relativeTo: .largeTitle)]
         }
     }
 
@@ -221,6 +295,7 @@ struct NavigationBarConfigurator {
 }
 #endif
 // MARK: -
+#if DEBUG
 struct ContentViewFonts: View {
     //    let myWeight: Font.Weight
     var body: some View {
@@ -255,6 +330,7 @@ struct ContentViewFonts: View {
     }
 }
 
-//#Preview("Font View") {
-//    ContentViewFonts()
-//}
+#Preview("Font View") {
+    ContentViewFonts()
+}
+#endif
diff --git a/TalerWallet1/Helper/TalerStrings.swift 
b/TalerWallet1/Helper/TalerStrings.swift
index d1443b4..6c39f67 100644
--- a/TalerWallet1/Helper/TalerStrings.swift
+++ b/TalerWallet1/Helper/TalerStrings.swift
@@ -3,9 +3,9 @@
  * See LICENSE.md
  */
 import Foundation
+import UIKit
 
 extension StringProtocol {
-
     func trimURL() -> String {
         if let url = URL(string: String(self)) {
             if let host = url.host {
@@ -15,3 +15,14 @@ extension StringProtocol {
         return String(self)
     }
 }
+
+extension String {
+    func widthOfString(usingUIFont font: UIFont) -> CGFloat {
+        let fontAttributes = [NSAttributedString.Key.font: font]
+        let size = self.size(withAttributes: fontAttributes)
+        return size.width
+    }
+}
+// This would be used like so:
+// let uiFont = UIFont.systemFont(ofSize: 17, weight: .bold)
+// let width = "SomeString".widthOfString(usingUIFont: uiFont)
diff --git a/TalerWallet1/Helper/WalletColors.swift 
b/TalerWallet1/Helper/WalletColors.swift
index 1f037cf..815da41 100644
--- a/TalerWallet1/Helper/WalletColors.swift
+++ b/TalerWallet1/Helper/WalletColors.swift
@@ -14,14 +14,18 @@ public struct WalletColors {
     let gray5 = Color(.systemGray5)
     let gray6 = Color(.systemGray6)
 
-    func buttonForeColor(pressed: Bool, disabled: Bool, prominent: Bool = 
false) -> Color {
-            disabled ? gray2
+    func buttonForeColor(pressed: Bool, disabled: Bool,
+                       prominent: Bool = false, balance: Bool = false) -> 
Color {
+             balance ? Color.primary
+          : disabled ? gray2
         : !prominent ? tint
            : pressed ? gray6 : gray5
     }
 
-    func buttonBackColor(pressed: Bool, disabled: Bool, prominent: Bool = 
false) -> Color {
-            disabled ? gray5
+    func buttonBackColor(pressed: Bool, disabled: Bool,
+                       prominent: Bool = false, balance: Bool = false) -> 
Color {
+             balance ? (pressed ? gray5 : gray6)
+          : disabled ? gray5
          : prominent ? tint
            : pressed ? gray5 : gray4
     }
diff --git a/TalerWallet1/Model/Model+Balances.swift 
b/TalerWallet1/Model/Model+Balances.swift
index 3c5c585..c5fa095 100644
--- a/TalerWallet1/Model/Model+Balances.swift
+++ b/TalerWallet1/Model/Model+Balances.swift
@@ -45,13 +45,20 @@ extension WalletModel {
     /// fetch Balances from Wallet-Core. No networking involved
     @MainActor func balancesM(_ stack: CallStack)
       async -> [Balance] {          // M for MainActor
-        do {
-            let request = Balances()
-            let response = try await sendRequest(request, ASYNCDELAY)
-            return response.balances                // trigger view update in 
BalancesListView
-        } catch {
-            logger.error("balancesM failed: \(error)")
-            return []
+        await semaphore.wait()
+        defer { semaphore.signal() }
+        if cachedBalances == nil {
+            do {
+                let request = Balances()
+                let response = try await sendRequest(request, ASYNCDELAY)
+                cachedBalances = response.balances
+            } catch {
+                logger.error("balancesM failed: \(error)")
+                // TODO: show error
+            }
+        } else {
+            logger.trace("returning cached Balances")
         }
+        return cachedBalances ?? []
     }
 }
diff --git a/TalerWallet1/Model/Model+Exchange.swift 
b/TalerWallet1/Model/Model+Exchange.swift
index b5d6641..7ac8d72 100644
--- a/TalerWallet1/Model/Model+Exchange.swift
+++ b/TalerWallet1/Model/Model+Exchange.swift
@@ -82,10 +82,23 @@ fileprivate struct ListExchanges: 
WalletBackendFormattedRequest {
     }
 }
 
+/// A request to update a single exchange.
+fileprivate struct UpdateExchange: WalletBackendFormattedRequest {
+    struct Response: Decodable {}   // no result - getting no error back means 
success
+    func operation() -> String { return "updateExchangeEntry" }
+    func args() -> Args { return Args(scopeInfo: scopeInfo) }
+
+    var scopeInfo: ScopeInfo
+
+    struct Args: Encodable {
+        var scopeInfo: ScopeInfo
+    }
+}
+
 /// A request to add an exchange.
 fileprivate struct AddExchange: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
-    func operation() -> String { return "addExchange" }
+    func operation() -> String { return "addExchange" }     // addExchangeEntry
     func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl) }
 
     var exchangeBaseUrl: String
@@ -96,10 +109,9 @@ fileprivate struct AddExchange: 
WalletBackendFormattedRequest {
 }
 
 /// A request to get info about a currency
-
-fileprivate struct GetScopedCurrencyInfo: WalletBackendFormattedRequest {
-    typealias Response = ScopedCurrencyInfo
-    func operation() -> String { return "getScopedCurrencyInfo" }
+fileprivate struct GetCurrencySpecification: WalletBackendFormattedRequest {
+    typealias Response = CurrencySpecification
+    func operation() -> String { return "getCurrencySpecification" }
     func args() -> Args { return Args(scope: scope) }
 
     var scope: ScopeInfo
@@ -130,9 +142,17 @@ extension WalletModel {
         _ = try await sendRequest(request)
     }
 
-    @MainActor func getScopedCurrencyInfoM(scope: ScopeInfo)
-    async throws -> ScopedCurrencyInfo {   // M for MainActor
-        let request = GetScopedCurrencyInfo(scope: scope)
+    /// ask wallet-core to update an existing exchange by querying it for 
denominations, fees, and scoped currency info
+    func updateExchange(scopeInfo: ScopeInfo)
+      async throws  {
+        let request = UpdateExchange(scopeInfo: scopeInfo)
+        logger.info("updating exchange for: \(scopeInfo.currency, privacy: 
.public)")
+        _ = try await sendRequest(request)
+    }
+
+    @MainActor func getCurrencySpecificationM(scope: ScopeInfo)
+      async throws -> CurrencySpecification {   // M for MainActor
+        let request = GetCurrencySpecification(scope: scope)
         let response = try await sendRequest(request, ASYNCDELAY)
         return response
     }
diff --git a/TalerWallet1/Model/Model+Settings.swift 
b/TalerWallet1/Model/Model+Settings.swift
index c34a3fa..5dff9f8 100644
--- a/TalerWallet1/Model/Model+Settings.swift
+++ b/TalerWallet1/Model/Model+Settings.swift
@@ -7,77 +7,77 @@ import taler_swift
 import SymLog
 fileprivate let ASYNCDELAY: UInt = 0   //set e.g to 6 or 9 seconds for 
debugging
 
-fileprivate let APIBASEURL  = "/demobanks/default/access-api/"
-//fileprivate let MERCHANTAUTHTOKEN = "secret-token:sandbox"
-fileprivate let MERCHANTAUTHTOKEN = "secret-token:secret"
+fileprivate let MERCHANTAUTHTOKEN = "secret-token:sandbox"
 
 // MARK: -
 /// A request to add a test balance to the wallet.
-fileprivate struct WalletBackendWithdrawTestBalance: 
WalletBackendFormattedRequest {
+fileprivate struct WithdrawTestBalanceRequest: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
     func operation() -> String { return "withdrawTestBalance" }
     func args() -> Args {
-        return Args(amount: amount, bankBaseUrl: bankBaseUrl,
-                    exchangeBaseUrl: exchangeBaseUrl, bankAccessApiBaseUrl: 
bankAccessApiBaseUrl)
+        return Args(amount: amount,
+//                    bankBaseUrl: bankBaseUrl,
+                    corebankApiBaseUrl: corebankApiBaseUrl,
+                    exchangeBaseUrl: exchangeBaseUrl)
     }
 
     var amount: Amount
-    var bankBaseUrl: String
+//    var bankBaseUrl: String
+    var corebankApiBaseUrl: String
     var exchangeBaseUrl: String
-    var bankAccessApiBaseUrl: String
 
     struct Args: Encodable {
         var amount: Amount
-        var bankBaseUrl: String
+//        var bankBaseUrl: String                         // <= this should be 
the correct parameter name
+        var corebankApiBaseUrl: String                // <= but currently this 
is used by wallet-core
         var exchangeBaseUrl: String
-        var bankAccessApiBaseUrl: String
     }
 }
 extension WalletModel {
     @MainActor func loadTestKudosM(test: Bool)
     async throws {          // M for MainActor
         let amount = Amount(currency:  test ? TESTCURRENCY : DEMOCURRENCY, 
integer: 11, fraction: 0)
-        let request = WalletBackendWithdrawTestBalance(amount: amount,
-                                                       bankBaseUrl: test ? 
TESTBANK : DEMOBANK,
-                                                       exchangeBaseUrl: test ? 
TESTEXCHANGE : DEMOEXCHANGE,
-                                                       bankAccessApiBaseUrl: 
(test ? TESTBANK : DEMOBANK) + APIBASEURL)
+        let request = WithdrawTestBalanceRequest(amount: amount,
+//                                                 bankBaseUrl: test ? 
TESTBANK : DEMOBANK,
+                                                 corebankApiBaseUrl: test ? 
TESTBANK : DEMOBANK,
+                                                 exchangeBaseUrl: test ? 
TESTEXCHANGE : DEMOEXCHANGE)
         let response = try await sendRequest(request, ASYNCDELAY)
     }
 } // loadTestKudosM()
 // MARK: -
 /// A request to add a test balance to the wallet.
-fileprivate struct WalletBackendRunIntegration: WalletBackendFormattedRequest {
+fileprivate struct RunIntegrationTest: WalletBackendFormattedRequest {
     struct Response: Decodable {}   // no result - getting no error back means 
success
     func operation() -> String { return newVersion ? "runIntegrationTestV2" : 
"runIntegrationTest" }
     func args() -> Args {
-        return Args(amountToWithdraw: amountToWithdraw,
-                    amountToSpend: amountToSpend,
-                    bankBaseUrl: bankBaseUrl,
-                    bankAccessApiBaseUrl: bankAccessApiBaseUrl,
-                    exchangeBaseUrl: exchangeBaseUrl,
+        return Args(exchangeBaseUrl: exchangeBaseUrl,
+//                    bankBaseUrl: bankBaseUrl,
+                    corebankApiBaseUrl: corebankApiBaseUrl,
                     merchantBaseUrl: merchantBaseUrl,
-                    merchantAuthToken: merchantAuthToken
+                    merchantAuthToken: merchantAuthToken,
+                    amountToWithdraw: amountToWithdraw,
+                    amountToSpend: amountToSpend
         )
     }
 
     let newVersion: Bool
 
-    var amountToWithdraw: Amount
-    var amountToSpend: Amount
-    var bankBaseUrl: String
-    var bankAccessApiBaseUrl: String
     var exchangeBaseUrl: String
+//    var bankBaseUrl: String
+    var corebankApiBaseUrl: String
     var merchantBaseUrl: String
     var merchantAuthToken: String
+    var amountToWithdraw: Amount
+    var amountToSpend: Amount
 
     struct Args: Encodable {
-        var amountToWithdraw: Amount
-        var amountToSpend: Amount
-        var bankBaseUrl: String
-        var bankAccessApiBaseUrl: String
         var exchangeBaseUrl: String
+//        var bankBaseUrl: String
+        var corebankApiBaseUrl: String
         var merchantBaseUrl: String
         var merchantAuthToken: String
+        var amountToWithdraw: Amount
+        var amountToSpend: Amount
     }
 }
 extension WalletModel {
@@ -85,14 +85,14 @@ extension WalletModel {
     async throws {               // M for MainActor
         let amountW = Amount(currency: test ? TESTCURRENCY : DEMOCURRENCY, 
integer: 3, fraction: 0)
         let amountS = Amount(currency: test ? TESTCURRENCY : DEMOCURRENCY, 
integer: 1, fraction: 0)
-        let request = WalletBackendRunIntegration(newVersion: newVersion,
-                                                  amountToWithdraw: amountW,
-                                                  amountToSpend: amountS,
-                                                  bankBaseUrl: (test ? 
TESTBANK : DEMOBANK) + APIBASEURL,
-                                                  bankAccessApiBaseUrl: (test 
? TESTBANK : DEMOBANK) + APIBASEURL,
-                                                  exchangeBaseUrl: test ? 
TESTEXCHANGE : DEMOEXCHANGE,
-                                                  merchantBaseUrl: test ? 
TESTBACKEND : DEMOBACKEND,
-                                                  merchantAuthToken: 
MERCHANTAUTHTOKEN)
+        let request = RunIntegrationTest(newVersion: newVersion,
+                                         exchangeBaseUrl: test ? TESTEXCHANGE 
: DEMOEXCHANGE,
+//                                         bankBaseUrl: (test ? TESTBANK : 
DEMOBANK),
+                                         corebankApiBaseUrl: (test ? TESTBANK 
: DEMOBANK),
+                                         merchantBaseUrl: test ? TESTBACKEND : 
DEMOBACKEND,
+                                         merchantAuthToken: MERCHANTAUTHTOKEN,
+                                         amountToWithdraw: amountW,
+                                         amountToSpend: amountS)
         let _ = try await sendRequest(request, ASYNCDELAY)
     }
 } // runIntegrationTestM()
diff --git a/TalerWallet1/Model/WalletModel.swift 
b/TalerWallet1/Model/WalletModel.swift
index 508fbf9..6a24d8e 100644
--- a/TalerWallet1/Model/WalletModel.swift
+++ b/TalerWallet1/Model/WalletModel.swift
@@ -9,13 +9,14 @@ import os.log
 fileprivate let DATABASE = "talerwalletdb-v30"
 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 {
     public static let shared = WalletModel()
     static func className() -> String {"\(self)"}
     let logger = Logger (subsystem: "net.taler.gnu", category: "WalletModel")
+    let semaphore = AsyncSemaphore(value: 1)
+    var cachedBalances: [Balance]? = nil
 
     func sendRequest<T: WalletBackendFormattedRequest> (_ request: T, _ delay: 
UInt = 0)
       async throws -> T.Response {    // T for any Thread
diff --git a/TalerWallet1/Views/Balances/BalanceRowButtons.swift 
b/TalerWallet1/Views/Balances/BalanceRowButtons.swift
deleted file mode 100644
index 6ac8a9b..0000000
--- a/TalerWallet1/Views/Balances/BalanceRowButtons.swift
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
- * See LICENSE.md
- */
-import SwiftUI
-import taler_swift
-
-struct BalanceRowButtons: View {
-    let amount: Amount
-    let narrow: Bool
-    let lineLimit: Int
-    let sendAction: () -> Void
-    let recvAction: () -> Void
-    @Environment(\.sizeCategory) var sizeCategory
-
-    var body: some View {
-        let currency = amount.currencyStr
-        Group {
-            Button("Request\nPayment", action: recvAction)
-                .lineLimit(lineLimit)
-                .disabled(false)
-                .buttonStyle(TalerButtonStyle(type: .bordered,
-                                            dimmed: false,
-                                            narrow: narrow,
-                                           aligned: .center))
-            Button("Send\n\(currency)", action: sendAction)
-                .lineLimit(lineLimit)
-                .disabled(amount.isZero)
-                .buttonStyle(TalerButtonStyle(type: .bordered,
-                                            dimmed: false,
-                                            narrow: narrow,
-                                           aligned: .center))
-        }
-    }
-}
-
-struct BalanceRowButtons_Previews: PreviewProvider {
-    static var previews: some View {
-        List {
-            VStack {
-                let amount = try! Amount(fromString: LONGCURRENCY + ":1234.56")
-                BalanceRowButtons(amount: Amount(currency: "TestKUDOS", value: 
1234),
-                                  narrow: false, lineLimit: 0,
-                                  sendAction: {}, recvAction: {})
-                BalanceButton(amount: amount, rowAction: {})
-            }
-            HStack {
-                let amount = try! Amount(fromString: "KUDOS" + ":1234.56")
-                BalanceRowButtons(amount: amount,
-                                  narrow: true, lineLimit: 2,
-                                  sendAction: {}, recvAction: {})
-                BalanceButton(amount: amount, rowAction: {})
-            }
-        }
-    }
-}
diff --git a/TalerWallet1/Views/Balances/BalanceRowView.swift 
b/TalerWallet1/Views/Balances/BalanceRowView.swift
index b2b0a31..129ea3d 100644
--- a/TalerWallet1/Views/Balances/BalanceRowView.swift
+++ b/TalerWallet1/Views/Balances/BalanceRowView.swift
@@ -5,91 +5,134 @@
 import SwiftUI
 import taler_swift
 
-/// This view shows the currency row in a currency section
-/// [Send Coins]  [Receive Coins]     Balance
+let SPACING = CGFloat(10)                  // space between the two buttons
+
+struct BalanceLabel: View {
+    let balanceTitle: String
+    let horizontal: Bool
+    let amountStr: String
+    let iconOnly: Bool
+
+    var body: some View {
+        Group {         // can either be horizontal (preferred) or vertical 
(if doesn't fit horizontally)
+            if !iconOnly {
+                Text(balanceTitle)
+                    .accessibilityFont(.title2)
+                    .foregroundColor(.secondary)
+            }
+            if horizontal {
+                Spacer(minLength: 0)
+                Text(amountStr)
+                    .accessibilityFont(.title)
+                    .monospacedDigit()
+            } else { HStack {
+                Spacer(minLength: 0)
+                Text(amountStr)
+                    .accessibilityFont(.title)
+                    .monospacedDigit()
+            } }
+        }
+    }
+}
 
 struct BalanceButton: View {
-    let amount: Amount
+    let amountStr: String
     let rowAction: () -> Void
 
+    @AppStorage("iconOnly") var iconOnly: Bool = false
+
     var body: some View {
+        let balanceTitle = String(localized: "Balance:", comment: "Main view")
         Button(action: rowAction) {
-            VStack(alignment: .trailing, spacing: 0) {
-                Text("Balance", comment: "Balance in main view")
-                    .bold()         // in iOS-15 defined for Text, but not for 
Font (only iOS-16)
-                Text(verbatim: "\(amount.valueStr)")  // TODO: 
CurrencyFormatter?
-                    .accessibilityFont(.title)
-                    .monospacedDigit()
+            SingleAxisGeometryReader { width in
+                Group {
+                    let balancesFont = TalerFont.uiFont(.title2)
+                    let amountFont = TalerFont.uiFont(.title)
+                    let titles = [(balanceTitle, balancesFont),
+                                  (amountStr, amountFont)]
+                    let needVStack = !iconOnly && Self.needVStack(titles, 
width: width, spacing: SPACING, sameSize: false)
+                    if needVStack {
+                        VStack(alignment: .leading, spacing: 0) {
+                            BalanceLabel(balanceTitle: balanceTitle, 
horizontal: false, amountStr: amountStr, iconOnly: iconOnly)
+                        }
+                    } else {
+                        HStack(alignment: .lastTextBaseline, spacing: 0) {
+                            BalanceLabel(balanceTitle: balanceTitle, 
horizontal: true, amountStr: amountStr, iconOnly: iconOnly)
+                        }
+                    }
+                }
             }
-            .accessibilityFont(.subheadline)
         }   .disabled(false)
+            .buttonStyle(TalerButtonStyle(type: iconOnly ? .plain : .balance, 
aligned: .trailing))
             .accessibilityElement(children: 
/*@START_MENU_TOKEN@*/.ignore/*@END_MENU_TOKEN@*/)
-            .accessibilityLabel("Balance \(amount.readableDescription)")    // 
TODO: CurrencyFormatter!
-            .buttonStyle(TalerButtonStyle(type: .plain, aligned: .trailing))
-//                .background(Color.yellow)
+            .accessibilityLabel("\(balanceTitle) \(amountStr)")    // TODO: 
CurrencyFormatter!
     }
 }
 
+
+/// This view shows the currency row in a currency section
+/// [Send Coins]  [Receive Coins]     Balance
 struct BalanceRowView: View {
     let amount: Amount
     let sendAction: () -> Void
     let recvAction: () -> Void
     let rowAction: () -> Void
     @Environment(\.sizeCategory) var sizeCategory
+    @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
-    func needVStack(_ amount: Amount) -> Bool {
-        // Sizes: 320 (SE), 375 (X, Xs, 12, 13 mini), 390 (12,13,14), 414 
(Plus, Max), 428 (Pro Max)
-        guard 350 < UIScreen.screenWidth else {return true}   // always for 
iPhone SE 1st Gen
-        var count = amount.currencyStr.count
-//        print(sizeCategory)
-        switch sizeCategory {
-            case ContentSizeCategory.extraSmall:
-                count += 0
-            case ContentSizeCategory.small:
-                count += 1
-            case ContentSizeCategory.medium:
-                count += 2
-            case ContentSizeCategory.large:
-                count += 3
-            case ContentSizeCategory.extraLarge:
-                count += 4
-            default:
-                count += 5
-        }
-        return count > 9
-    }
+    let sendTitle1 = String(localized: "Send", comment: "Top of button <Send 
Money>")
+    let sendTitle2 = String(localized: "Money", comment: "Bottom of button 
<Send Money>")
+    let requestTitle1 = String(localized: "Request", comment: "Top of button 
<Request Payment>")
+    let requestTitle2 = String(localized: "Payment", comment: "Bottom of 
button <Request Payment>")
 
     var body: some View {
-        Group {
-            if needVStack(amount) {
-                VStack (alignment: .trailing) {
-                    BalanceButton(amount: amount, rowAction: rowAction)
-                    HStack {
-                        BalanceRowButtons(amount: amount, narrow: false, 
lineLimit: 5,
-                                          sendAction: sendAction, recvAction: 
recvAction)
-                    }
-                }
-            } else {
-                HStack {
-                    BalanceRowButtons(amount: amount, narrow: true, lineLimit: 
5,
-                                      sendAction: sendAction, recvAction: 
recvAction)
-                    BalanceButton(amount: amount, rowAction: rowAction)
+        SingleAxisGeometryReader { width in
+            VStack (alignment: .trailing) {
+                BalanceButton(amountStr: amount.valueStr,        // TODO: 
CurrencyFormatter?
+                              rowAction: rowAction)
+                let uiFont = TalerFont.uiFont(.title3)
+                let titles = [(requestTitle1, uiFont), (requestTitle2, 
uiFont), (sendTitle1, uiFont), (sendTitle2, uiFont)]
+                let sendTitle = sendTitle1 + "\n" + sendTitle2
+                let requestTitle = requestTitle1 + "\n" + requestTitle2
+                let twoRowButtons = TwoRowButtons(sendTitle: sendTitle, 
recvTitle: requestTitle,
+                                                  lineLimit: 5, sendDisabled: 
amount.isZero,
+                                                  sendAction: sendAction, 
recvAction: recvAction)
+  let _ = print("Screenwidth: \(UIScreen.screenWidth)  Geometry: \(width)  
\(sizeCategory): \(sizeCategory)")
+                if Self.needVStack(titles, width: width, spacing: SPACING) {
+                    VStack { twoRowButtons }
+                } else {
+                    HStack(spacing: SPACING) { twoRowButtons }
                 }
-//                .fixedSize(horizontal: true, vertical: true)   // should 
make all buttons equal height - but doesn't
             }
+            .accessibilityElement(children: .combine)
         }
-        .accessibilityElement(children: .combine)
     }
 }
 // MARK: -
 #if DEBUG
 struct BalanceRowView_Previews: PreviewProvider {
     static var previews: some View {
+        let test = try! Amount(fromString: TESTCURRENCY + ":1.23")
+        let demo = try! Amount(fromString: DEMOCURRENCY + ":1234.56")
         List {
-            BalanceRowView(amount: try! Amount(fromString: TESTCURRENCY + 
":1234.56"),
-                           sendAction: {}, recvAction: {}, rowAction: {})
-            BalanceRowView(amount: try! Amount(fromString: DEMOCURRENCY + 
":1234.56"),
-                           sendAction: {}, recvAction: {}, rowAction: {})
+            Section {
+                HStack(alignment: .lastTextBaseline) {
+                    BalanceLabel(balanceTitle: "BalanceA:", horizontal: true, 
amountStr: test.valueStr, iconOnly: false)
+                        .listRowSeparator(.hidden)
+                }
+            }
+            Section {
+                BalanceLabel(balanceTitle: "Balance:", horizontal: false, 
amountStr: demo.valueStr, iconOnly: false)
+                    .listRowSeparator(.hidden)
+            }
+            Section {
+                BalanceButton(amountStr: demo.valueStr, rowAction: {})
+                    .listRowSeparator(.hidden)
+            }
+            Section {
+                BalanceRowView(amount: demo, sendAction: {}, recvAction: {}, 
rowAction: {})
+            }
+            BalanceRowView(amount: test, sendAction: {}, recvAction: {}, 
rowAction: {})
         }
     }
 }
diff --git a/TalerWallet1/Views/Balances/BalancesListView.swift 
b/TalerWallet1/Views/Balances/BalancesListView.swift
index f1b091e..8ccad0c 100644
--- a/TalerWallet1/Views/Balances/BalancesListView.swift
+++ b/TalerWallet1/Views/Balances/BalancesListView.swift
@@ -12,10 +12,15 @@ struct BalancesListView: View {
     private let symLog = SymLogV()
     let stack: CallStack
     let navTitle: String
+    @Binding var balances: [Balance]
+    @Binding var shouldReloadBalances: Int
+    @State private var lastReloadedBalances = 0
+#if TABBAR  // Taler Wallet
+#else       // GNU Taler
     let hamburgerAction: () -> Void
+#endif
 
     @EnvironmentObject private var model: WalletModel
-    @State private var balances: [Balance] = []
     @State private var centsToTransfer: UInt64 = 0
     @State private var summary: String = ""
     @State private var showQRScanner: Bool = false
@@ -31,40 +36,44 @@ struct BalancesListView: View {
     private var dismissAlertButton: some View {
         Button("Cancel", role: .cancel) {
             if #available(iOS 17.0, *) {
-//                
AccessibilityNotification.Announcement(ClosingAnnouncement).post()
+                
AccessibilityNotification.Announcement(ClosingAnnouncement).post()
             }
             showCameraAlert = false
         }
     }
+    private func dismissingSheet() {
+        if #available(iOS 17.0, *) {
+            AccessibilityNotification.Announcement(ClosingAnnouncement).post()
+        }
+    }
 
     var defaultPriorityAnnouncement = AttributedString(localized: "Opening 
Camera")
     var lowPriorityAnnouncement: AttributedString {
         var lowPriorityString = AttributedString ("Camera Loading")
         if #available(iOS 17.0, *) {
-//            lowPriorityString.accessibilitySpeechAnnouncementPriority = .low
+            lowPriorityString.accessibilitySpeechAnnouncementPriority = .low
         }
         return lowPriorityString
     }
     var highPriorityAnnouncement: AttributedString {
         var highPriorityString = AttributedString("Camera Active")
         if #available(iOS 17.0, *) {
-//            highPriorityString.accessibilitySpeechAnnouncementPriority = 
.high
+            highPriorityString.accessibilitySpeechAnnouncementPriority = .high
         }
         return highPriorityString
     }
     private func checkCameraAvailable() -> Void {
-        /// Open Camera Code
+        // Open Camera when QR-Button was tapped
         if #available(iOS 17.0, *) {
-//            
AccessibilityNotification.Announcement(defaultPriorityAnnouncement).post()
+            
AccessibilityNotification.Announcement(defaultPriorityAnnouncement).post()
         }
         AVCaptureDevice.requestAccess(for: .video, completionHandler: { 
(granted: Bool) -> Void in
             if granted {
-                showQRScanner = true
-            } else {
-                // Camera Loaded Code
+                showQRScanner = true            // present sheet
                 if #available(iOS 17.0, *) {
-//                    
AccessibilityNotification.Announcement(highPriorityAnnouncement).post()
+                    
AccessibilityNotification.Announcement(highPriorityAnnouncement).post()
                 }
+            } else {
                 showCameraAlert = true
             }
         })
@@ -72,7 +81,10 @@ struct BalancesListView: View {
 
     /// runs on MainActor if called in some Task {}
     @discardableResult
-    private func reloadAction(_ stack: CallStack) async -> Int {
+    private func reloadBalances(_ stack: CallStack, _ invalidateCache: Bool) 
async -> Int {
+        if invalidateCache {
+            model.cachedBalances = nil
+        }
         let reloaded = await model.balancesM(stack.push())
         let count = reloaded.count
         balances = reloaded         // redraw
@@ -83,41 +95,32 @@ struct BalancesListView: View {
 #if DEBUG
         let _ = Self._printChanges()
         let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+#if TABBAR  // Taler Wallet
+        let hamburger: HamburgerButton? = nil
+#else       // GNU Taler
+        let hamburger: HamburgerButton = HamburgerButton(action: 
hamburgerAction)
 #endif
         Content(symLog: symLog, stack: stack.push(), balances: $balances,
                 centsToTransfer: $centsToTransfer, summary: $summary,
-                reloadAction: reloadAction)
+                reloadBalances: reloadBalances)
             .navigationTitle(navTitle)
-            .navigationBarItems(leading: HamburgerButton(action: 
hamburgerAction),
+            .navigationBarItems(leading: hamburger,
                                 trailing: QRButton(action: 
checkCameraAvailable))
-            .overlay {
-                if balances.isEmpty {
-                    WalletEmptyView()
-                        .refreshable {  // already async
-                            symLog.log("empty refreshing")
-                            let count = await reloadAction(stack.push("empty 
refreshing"))
-                            if count > 0 {
-//                                postNotificationM(.BalanceReloaded)
-                                NotificationCenter.default.post(name: 
.BalanceReloaded, object: nil)
-                            }
-                        }
-                }
-            }
             .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) {
+            .sheet(isPresented: $showQRScanner, onDismiss: dismissingSheet) {
                 let sheet = AnyView(QRSheet(stack: stack.push()))
                 Sheet(sheetView: sheet)
             } // sheet
-            .task {
-                symLog.log(".task getBalances")
-                await reloadAction(stack.push(".task"))
+            .task(id: shouldReloadBalances) {
+                symLog.log(".task shouldReloadBalances 
\(shouldReloadBalances)")
+                let invalidateCache = (lastReloadedBalances != 
shouldReloadBalances)
+                lastReloadedBalances = shouldReloadBalances
+                await reloadBalances(stack.push(".task"), invalidateCache)
             } // task
     }
 }
@@ -130,11 +133,7 @@ extension BalancesListView {
         @Binding var balances: [Balance]
         @Binding var centsToTransfer: UInt64
         @Binding var summary: String
-//        @discardableResult
-        var reloadAction: (_ stack: CallStack) async -> Int
-
-        @State private var isActive = true
-        @State private var shouldReload = false
+        var reloadBalances: (_ stack: CallStack, _ invalidateCache: Bool) 
async -> Int
 
         var body: some View {
 #if DEBUG
@@ -143,43 +142,27 @@ extension BalancesListView {
 #endif
             Group { // necessary for .backslide transition (bug in SwiftUI)
                 let count = balances.count
-                List(balances, id: \.self) { balance in
-                    BalancesSectionView(stack: stack.push(),
-                                        balance: balance,
-                                   sectionCount: count,
-                                centsToTransfer: $centsToTransfer,
-                                        summary: $summary)
-                }
-                .refreshable {  // already async
-                    symLog?.log("refreshing")
-                    let count = await reloadAction(stack.push("refreshing"))
-                    if count > 0 {
-//                        postNotificationM(.BalanceReloaded)
-                        NotificationCenter.default.post(name: 
.BalanceReloaded, object: nil)
+                if balances.isEmpty {
+                    WalletEmptyView(stack: stack.push("isEmpty"))
+                } else {
+                    List(balances, id: \.self) { balance in
+                        BalancesSectionView(stack: 
stack.push("\(balance.available.currencyStr)"),
+                                          balance: balance,
+                                     sectionCount: count,
+                                  centsToTransfer: $centsToTransfer,
+                                          summary: $summary)
                     }
-                }
-                .listStyle(myListStyle.style).anyView
-            }
-            .onAppear() {
-                isActive = true
-                if shouldReload {
-                    shouldReload = false
-                    symLog?.log(".onAppear ==>  shouldReload was true, 
reloading now")
-                    Task { await reloadAction(stack.push("shouldReload")) } // 
runs on MainActor
+                    .onAppear() {
+                        DebugViewC.shared.setViewID(VIEW_BALANCES, stack: 
stack.push("onAppear"))
+                    }
+                    .listStyle(myListStyle.style).anyView
                 }
             }
-            .onDisappear() {
-                isActive = false
-            }
-            .onNotification(.BalanceChange) { notification in
-                // reload balances on receiving BalanceChange notification ...
-                // doesn't need to be received on main thread because we just 
reload in a background task anyway
-                if isActive {
-                    symLog?.log(".onNotification(.BalanceChange) ==> reload")
-                    Task { await reloadAction(stack.push(".BalanceChange")) }
-                } else {
-                    symLog?.log(".onNotification(.BalanceChange) ==> reload 
postponed, shouldReload = true")
-                    shouldReload = true
+            .refreshable {  // already async
+                symLog?.log("refreshing balances")
+                let count = await reloadBalances(stack.push("refreshing 
balances"), true)
+                if count > 0 {
+                    NotificationCenter.default.post(name: .BalanceReloaded, 
object: nil)
                 }
             }
         } // body
diff --git a/TalerWallet1/Views/Balances/BalancesSectionView.swift 
b/TalerWallet1/Views/Balances/BalancesSectionView.swift
index dfeffcd..dfc5996 100644
--- a/TalerWallet1/Views/Balances/BalancesSectionView.swift
+++ b/TalerWallet1/Views/Balances/BalancesSectionView.swift
@@ -74,22 +74,22 @@ extension BalancesSectionView: View {
                     .accessibilityFont(.body)
                     .multilineTextAlignment(.leading)
             }
-            NavigationLinksView(stack: stack.push(),
-                              balance: balance,
-                      centsToTransfer: $centsToTransfer,
-                              summary: $summary,
-//                       buttonSelected: $buttonSelected,
-                completedTransactions: $completedTransactions,
-                      reloadAllAction: reloadCompleted,
-                      reloadOneAction: reloadOneAction)
+            BalancesNavigationLinksView(stack: stack.push(),
+                                      balance: balance,
+                              centsToTransfer: $centsToTransfer,
+                                      summary: $summary,
+//                             buttonSelected: $buttonSelected,
+                        completedTransactions: $completedTransactions,
+                              reloadAllAction: reloadCompleted,
+                              reloadOneAction: reloadOneAction)
             let hasPending = pendingTransactions.count > 0
             if hasPending {
-                SectionPendingRowView(symLog: symLog,
-                                       stack: stack.push(),
-                                    currency: currency,
-                         pendingTransactions: $pendingTransactions,
-                               reloadPending: reloadPending,
-                             reloadOneAction: reloadOneAction)
+                BalancesPendingRowView(symLog: symLog,
+                                        stack: stack.push(),
+                                     currency: currency,
+                          pendingTransactions: $pendingTransactions,
+                                reloadPending: reloadPending,
+                              reloadOneAction: reloadOneAction)
             }
             let hasUncompleted = uncompletedTransactions.count > 0
             if hasUncompleted {
@@ -110,16 +110,12 @@ extension BalancesSectionView: View {
 
             }
         } header: {
-            HStack (alignment: .bottom, spacing: 10) {
-                BarGraph(transactions: $completedTransactions, barHeight: 10)
-                Text(currency)
-                    .accessibilityFont(.title2)
-            }
+            BarGraphHeader(stack: stack.push(), currency: currency)
         }.id(sectionID)
         .task {
 //            if shownSectionID != sectionID {
-                symLog.log("task for \(sectionID) - reload Transactions")
-                let response = await model.transactionsT(stack.push("task for 
\(sectionID) - reload Transactions"), currency: currency)
+                symLog.log(".task for BalancesSectionView - reload 
Transactions")
+                let response = await model.transactionsT(stack.push(".task - 
reload Transactions"), currency: currency)
                 transactions = response
                 pendingTransactions = WalletModel.pendingTransactions(response)
                 uncompletedTransactions = 
WalletModel.uncompletedTransactions(response)
@@ -130,6 +126,7 @@ extension BalancesSectionView: View {
 //            }
         }
         let transactionCount = completedTransactions.count
+        /// if there is only one currency, then show recent transactions
         if sectionCount == 1 && transactionCount > 0 {
             let sortedTransactions = completedTransactions.sorted {
                 do {
@@ -155,9 +152,9 @@ extension BalancesSectionView: View {
             }
         }
     } // body
-}
+} // extension BalancesSectionView
 
-fileprivate struct SectionPendingRowView: View {
+fileprivate struct BalancesPendingRowView: View {
     let symLog: SymLogV?
     let stack: CallStack
     let currency: String
@@ -218,10 +215,10 @@ fileprivate struct SectionPendingRowView: View {
             }
         }
 
-    }
-}
+    } // body
+} // BalancesPendingRowView
 
-fileprivate struct NavigationLinksView: View {
+fileprivate struct BalancesNavigationLinksView: View {
     let stack: CallStack
     let balance: Balance
 //    let sectionCount: Int
@@ -230,9 +227,22 @@ fileprivate struct NavigationLinksView: View {
     @Binding var completedTransactions: [Transaction]
     let reloadAllAction: (_ stack: CallStack) async -> ()
     let reloadOneAction: ((_ transactionId: String) async throws -> 
Transaction)
+//    @EnvironmentObject private var model: WalletModel
 
     @State private var buttonSelected: Int? = nil
 
+    func selectAndUpdate(_ button: Int) {
+        buttonSelected = button      // will trigger NavigationLink
+        // while navigation animation runs, contact Exchange to update Fees
+//        Task { // runs on MainActor
+//            do {
+//                try await model.updateExchange(scopeInfo: balance.scopeInfo)
+//            } catch {    // TODO: error handling - couldn't updateExchange
+//                symLog.log("error: \(error)")
+//            }
+//        }
+    }
+
     var body: some View {
         let currency = balance.available.currencyStr
         HStack(spacing: 0) {
@@ -264,9 +274,9 @@ fileprivate struct NavigationLinksView: View {
             ) { EmptyView() }.frame(width: 0).opacity(0).hidden()           // 
TransactionsListView
 
             BalanceRowView(amount: balance.available, sendAction: {
-                buttonSelected = 1      // will trigger SendAmount 
NavigationLink
+                selectAndUpdate(1)      // will trigger SendAmount 
NavigationLink
             }, recvAction: {
-                buttonSelected = 2      // will trigger RequestPayment 
NavigationLink
+                selectAndUpdate(2)      // will trigger RequestPayment 
NavigationLink
             }, rowAction: {
                 buttonSelected = 3      // will trigger TransactionList 
NavigationLink
             })
diff --git a/TalerWallet1/Views/Balances/TwoRowButtons.swift 
b/TalerWallet1/Views/Balances/TwoRowButtons.swift
new file mode 100644
index 0000000..163b703
--- /dev/null
+++ b/TalerWallet1/Views/Balances/TwoRowButtons.swift
@@ -0,0 +1,50 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+
+struct TwoRowButtons: View {
+    let sendTitle: String
+    let recvTitle: String
+    let lineLimit: Int
+    let sendDisabled: Bool
+    let sendAction: () -> Void
+    let recvAction: () -> Void
+    @Environment(\.sizeCategory) var sizeCategory
+
+    var body: some View {
+        Group {
+            Button(sendTitle, action: sendAction)
+                .lineLimit(lineLimit)
+                .disabled(sendDisabled)
+                .buttonStyle(TalerButtonStyle(type: .bordered,
+                                            dimmed: false,
+                                           aligned: .center))
+            Button(recvTitle, action: recvAction)
+                .lineLimit(lineLimit)
+                .disabled(false)
+                .buttonStyle(TalerButtonStyle(type: .bordered,
+                                            dimmed: false,
+                                           aligned: .center))
+        }
+    }
+}
+
+struct TwoRowButtons_Previews: PreviewProvider {
+    static var previews: some View {
+        List {
+                TwoRowButtons(sendTitle: "Send\n" + TESTCURRENCY,
+                              recvTitle: "Receive\n" + LONGCURRENCY,
+                              lineLimit: 2, sendDisabled: true,
+                             sendAction: {}, recvAction: {})
+                .listRowSeparator(.hidden)
+                TwoRowButtons(sendTitle: "Send\n" + DEMOCURRENCY,
+                              recvTitle: "Receive\n" + DEMOCURRENCY,
+                              lineLimit: 2, sendDisabled: true,
+                             sendAction: {}, recvAction: {})
+                .listRowSeparator(.hidden)
+        }
+    }
+}
diff --git a/TalerWallet1/Views/Exchange/ExchangeListView.swift 
b/TalerWallet1/Views/Exchange/ExchangeListView.swift
index 3d1a1f2..47c74c8 100644
--- a/TalerWallet1/Views/Exchange/ExchangeListView.swift
+++ b/TalerWallet1/Views/Exchange/ExchangeListView.swift
@@ -10,8 +10,12 @@ import SymLog
 struct ExchangeListView: View {
     private let symLog = SymLogV(0)
     let stack: CallStack
+//    @Binding var balances: [Balance]
     let navTitle: String
+#if TABBAR  // Taler Wallet
+#else       // GNU Taler
     var hamburgerAction: () -> Void
+#endif
 
     @EnvironmentObject private var model: WalletModel
 
@@ -20,7 +24,7 @@ struct ExchangeListView: View {
     // 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 {
+    func reloadExchanges() async -> Void {
             exchanges = await model.listExchangesM()
     }
 
@@ -48,15 +52,20 @@ struct ExchangeListView: View {
 //        withAnimation { showAlert = true }
             showAlert = true
         }
-
+#if TABBAR  // Taler Wallet
+        let hamburger: HamburgerButton? = nil
+#else       // GNU Taler
+        let hamburger: HamburgerButton = HamburgerButton(action: 
hamburgerAction)
+#endif
         //Text("Exchanges...")
         Content(symLog: symLog,
                  stack: stack.push(),
+//              balances: $balances,
              exchanges: $exchanges,
        centsToTransfer: $centsToTransfer,
-          reloadAction: reloadAction)
+       reloadExchanges: reloadExchanges)
         .navigationTitle(navTitle)
-        .navigationBarItems(leading: HamburgerButton(action: hamburgerAction),
+        .navigationBarItems(leading: hamburger,
                             trailing: PlusButton(action: plusAction)
                                         .accessibilityLabel("Add Exchange"))
         .overlay {
@@ -67,7 +76,7 @@ struct ExchangeListView: View {
         }
         .task {
             symLog.log(".task")
-            await reloadAction()
+            await reloadExchanges()
         }
         .textFieldAlert(isPresented: $showAlert, title: "Add Exchange",
                         doneText: "Add", text: $newExchange, action: 
addExchange)
@@ -88,9 +97,10 @@ extension ExchangeListView {
         let symLog: SymLogV?
         let stack: CallStack
         @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+//        @Binding var balances: [Balance]
         @Binding var exchanges: [Exchange]
         @Binding var centsToTransfer: UInt64
-        var reloadAction: () async -> Void
+        var reloadExchanges: () async -> Void
 
         func currenciesDict(_ exchanges: [Exchange]) -> [String : [Exchange]] {
             var currencies: [String : [Exchange]] = [:]
@@ -119,17 +129,17 @@ extension ExchangeListView {
                 }
                 .refreshable {
                     symLog?.log("refreshing")
-                    await reloadAction()
+                    await reloadExchanges()
                 }
                 .listStyle(myListStyle.style).anyView
             }
             .onAppear() {
-                DebugViewC.shared.setViewID(VIEW_EXCHANGES)
+                DebugViewC.shared.setViewID(VIEW_EXCHANGES, stack: 
stack.push())
             }
             .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() } // runs on MainActor
+                Task { await reloadExchanges() } // runs on MainActor
             }
         } // body
     }
diff --git a/TalerWallet1/Views/Exchange/ExchangeSectionView.swift 
b/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
index a87ea0b..73e8dbf 100644
--- a/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
+++ b/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
@@ -8,10 +8,28 @@ import taler_swift
 struct ExchangeRowView: View {
     let stack: CallStack
     let exchange: Exchange
+//    let amount: Amount
     let currency: String
     @Binding var centsToTransfer: UInt64
 
+    @Environment(\.sizeCategory) var sizeCategory
     @State private var buttonSelected: Int? = nil
+
+    func selectAndUpdate(_ button: Int) {
+        buttonSelected = button      // will trigger NavigationLink
+        // while navigation animation runs, contact Exchange to update Fees
+//        Task { // runs on MainActor
+//            do {
+//                try await model.updateExchange(scopeInfo: balance.scopeInfo)
+//            } catch {    // TODO: error handling - couldn't updateExchange
+//                //                symLog.log("error: \(error)")
+//            }
+//        }
+    }
+
+    let depositTitle = String(localized: "Deposit", comment: "Top of button 
<Deposit (currency)>")
+    let withdrawTitle = String(localized: "Withdraw", comment: "Top of button 
<Withdraw (currency)>")
+
     var body: some View {
         let baseURL = exchange.exchangeBaseUrl
 
@@ -31,28 +49,35 @@ struct ExchangeRowView: View {
             ) { EmptyView() }.frame(width: 0).opacity(0)
         }.listRowSeparator(.hidden)
 
-        HStack {        // buttons just set "buttonSelected" so the 
NavigationLink will trigger
-            Button("Deposit\n\(currency)") { buttonSelected = 1 }
-                .multilineTextAlignment(.center)
-                .buttonStyle(TalerButtonStyle(type: .bordered))
-                .disabled(true)     // TODO: after implementing Deposit check 
available
-
-            Button("Withdraw\n\(currency)") { buttonSelected = 2 }
-                .multilineTextAlignment(.center)
-                .buttonStyle(TalerButtonStyle(type: .bordered))
-        }.listRowSeparator(.visible)
-//        .listRowSeparatorTint(.red)
-        .fixedSize(horizontal: false, vertical: true)
+        SingleAxisGeometryReader { width in
+            Group {
+                let uiFont = TalerFont.uiFont(.title3)
+                let titles = [(depositTitle, uiFont), (withdrawTitle, uiFont), 
(currency, uiFont)]
+                let sendTitle = depositTitle + "\n" + currency                 
         // TODO:  amount.currencyStr
+                let recvTitle = withdrawTitle + "\n" + currency                
         // TODO:  amount.currencyStr
+                let twoRowButtons = TwoRowButtons(sendTitle: sendTitle, 
recvTitle: recvTitle,
+                                                  lineLimit: 5, sendDisabled: 
true,     // TODO: amount.isZero
+                                                 sendAction: { 
selectAndUpdate(1) },
+                                                 recvAction: { 
selectAndUpdate(2) })
+                let spacing = CGFloat(10)                  // space between 
the two buttons
+                if Self.needVStack(titles, width: width, spacing: spacing) {
+                    VStack { twoRowButtons }
+                } else {
+                    HStack(spacing: spacing) { twoRowButtons }
+                }
+            }
+        }
     }
 }
+// MARK: -
 /// This view shows the currency name in an exchange section
 ///         currency
 /// [Deposit Coins]  [Withdraw Coins]
 struct ExchangeSectionView: View {
     let stack: CallStack
-    let currency: String
+//    let amount: Amount
+    let currency: String          // TODO: amount.currencyStr
     let exchanges: [Exchange]
-
     @Binding var centsToTransfer: UInt64
 
     var body: some View {
@@ -64,13 +89,13 @@ struct ExchangeSectionView: View {
             ForEach(exchanges) { exchange in
                 ExchangeRowView(stack: stack.push(),
                              exchange: exchange,
-                             currency: currency,
+//                               amount: amount,
+                             currency: currency,            // TODO: 
(balance.available) amount.isZero to disable Deposit-button
                       centsToTransfer: $centsToTransfer)
             }
             .accessibilityElement(children: .combine)
         } header: {
-            Text(currency)
-                .accessibilityFont(.title)
+            BarGraphHeader(stack: stack.push(), currency: currency)
         }
     }
 }
@@ -79,6 +104,7 @@ struct ExchangeSectionView: View {
 struct ExchangeRow_Container : View {
     @State private var centsToTransfer: UInt64 = 100
 
+//    let amount = try! Amount(fromString: LONGCURRENCY + ":1234.56")
     var body: some View {
         let exchange1 = Exchange(exchangeBaseUrl: ARS_AGE_EXCHANGE,
                                         currency: LONGCURRENCY,
@@ -94,8 +120,9 @@ struct ExchangeRow_Container : View {
                              exchangeEntryStatus: .ephemeral,
                             exchangeUpdateStatus: .ready,
                            ageRestrictionOptions: [])
-        ExchangeSectionView(stack: CallStack("Preview"), currency: 
LONGCURRENCY, exchanges: [exchange1, exchange2],
-                                centsToTransfer: $centsToTransfer)
+        ExchangeSectionView(stack: CallStack("Preview"), currency: 
LONGCURRENCY,
+                            exchanges: [exchange1, exchange2],
+                            centsToTransfer: $centsToTransfer)
     }
 }
 
diff --git a/TalerWallet1/Views/Exchange/ManualWithdraw.swift 
b/TalerWallet1/Views/Exchange/ManualWithdraw.swift
index 4d689c1..78356be 100644
--- a/TalerWallet1/Views/Exchange/ManualWithdraw.swift
+++ b/TalerWallet1/Views/Exchange/ManualWithdraw.swift
@@ -62,9 +62,10 @@ struct ManualWithdraw: View {
                             .buttonStyle(TalerButtonStyle(type: .prominent))
                             .padding(.horizontal)
                         } else {
-                            ToSButtonView(exchangeBaseUrl: 
exchange.exchangeBaseUrl,
-                                                   viewID: VIEW_WITHDRAW_TOS,
-                                                      p2p: false)
+                            ToSButtonView(stack: stack.push(),
+                                exchangeBaseUrl: exchange.exchangeBaseUrl,
+                                         viewID: VIEW_WITHDRAW_TOS,
+                                            p2p: false)
                         }
                     }
                 } // disabled
@@ -77,7 +78,7 @@ struct ManualWithdraw: View {
         .navigationTitle(navTitle)
         .onAppear {
             symLog.log("onAppear")
-            DebugViewC.shared.setViewID(VIEW_WITHDRAWAL)
+            DebugViewC.shared.setViewID(VIEW_WITHDRAWAL, stack: stack.push())
         }
         .task(id: centsToTransfer) { // re-run this whenever centsToTransfer 
changes
             let amount = Amount.amountFromCents(currency, centsToTransfer)
diff --git a/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift 
b/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
index df5b3b1..1a182fa 100644
--- a/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
+++ b/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
@@ -50,7 +50,7 @@ struct ManualWithdrawDone: View {
             }
         }.onAppear() {
             symLog.log("onAppear")
-            DebugViewC.shared.setViewID(VIEW_WITHDRAW_ACCEPT)
+            DebugViewC.shared.setViewID(VIEW_WITHDRAW_ACCEPT, stack: 
stack.push())
         }.task {
             do {
                 let amount = Amount.amountFromCents(exchange.currency!, 
centsToTransfer)
diff --git a/TalerWallet1/Views/HelperViews/AmountView.swift 
b/TalerWallet1/Views/HelperViews/AmountView.swift
index f635dc0..2015115 100644
--- a/TalerWallet1/Views/HelperViews/AmountView.swift
+++ b/TalerWallet1/Views/HelperViews/AmountView.swift
@@ -25,7 +25,6 @@ struct AmountView: View {
                     .accessibilityFont(large ? .title : .title2)
 //                    .fontWeight(large ? .medium : .regular)       // 
@available(iOS 16.0, *)
                     .monospacedDigit()
-                Spacer()
             }
         }
             .frame(maxWidth: .infinity, alignment: .leading)
diff --git a/TalerWallet1/Views/HelperViews/BarGraph.swift 
b/TalerWallet1/Views/HelperViews/BarGraph.swift
index a842c01..dd66634 100644
--- a/TalerWallet1/Views/HelperViews/BarGraph.swift
+++ b/TalerWallet1/Views/HelperViews/BarGraph.swift
@@ -3,9 +3,38 @@
  * See LICENSE.md
  */
 import SwiftUI
+import SymLog
 
+let MAXBARS = 10
+
+struct BarGraphHeader: View {
+    private let symLog = SymLogV(0)
+    let stack: CallStack
+    let currency: String
+
+    @EnvironmentObject private var model: WalletModel
+    @State private var completedTransactions: [Transaction] = []
+
+    var body: some View {
+        HStack (alignment: .center, spacing: 10) {
+            Text(currency)
+                .accessibilityFont(.title2)
+            BarGraph(transactions: $completedTransactions,
+                     maxBars: MAXBARS, barHeight: 10)       // TODO: barHeight 
relative to fontSize
+        }
+        .task {
+            symLog.log(".task for BarGraphHeader(\(currency)) - reload 
Transactions")
+            // TODO: only load the 10 most recent transactions
+            let response = await model.transactionsT(stack.push(".task - 
reload Transactions for \(currency)"),
+                                                     currency: currency)
+            completedTransactions = WalletModel.completedTransactions(response)
+        }
+    }
+}
+// MARK: -
 struct BarGraph: View {
     @Binding var transactions: [Transaction]
+    let maxBars: Int
     let barHeight : Double
 
     func maxValue(_ someTransactions: [Transaction]) -> Double {
@@ -20,17 +49,14 @@ struct BarGraph: View {
     }
 
     var body: some View {
-        let slice = transactions.prefix(8)
-        let eightTransactions: [Transaction] = Array(slice)
-        let count = eightTransactions.count
-        let maxValue = maxValue(eightTransactions)
+        let slice = transactions.prefix(maxBars)
+        let count = slice.count
+        let tenTransactions: [Transaction] = Array(slice)
+        let maxValue = maxValue(tenTransactions)
 
-        HStack(alignment: .firstTextBaseline, spacing: 1) {
-#if DEBUG
-//            Text("first")
-#endif
+        HStack(alignment: .center, spacing: 1) {
             if count > 0 {
-                ForEach(Array(eightTransactions.enumerated()), id: \.element) 
{ index, transaction in
+                ForEach(Array(zip(slice.indices, slice)), id: \.0) { index, 
transaction in
                     let common = transaction.common
                     let incoming = common.incoming()
                     let netto = common.amountEffective.value
@@ -50,19 +76,9 @@ struct BarGraph: View {
                     }
                 }
             }
-//            if count < 8 {
-//                ForEach(count...8, id: \.self) {_ in
-//                    Rectangle()
-//                        .opacity(0.001)
-//                        .frame (width: 3, height: barHeight * 2 )
-//                }
-//            }
-#if DEBUG
-//            Text("last")
-#endif
         }
         .accessibilityHidden(true)      // cannot speak out this bar chart info
-//        .flippedDirection()         // draw first array item on trailing edge
+        .flippedDirection()             // draw first array item on trailing 
edge
     }
 }
 
diff --git a/TalerWallet1/Views/HelperViews/Buttons.swift 
b/TalerWallet1/Views/HelperViews/Buttons.swift
index 650ad36..945c86e 100644
--- a/TalerWallet1/Views/HelperViews/Buttons.swift
+++ b/TalerWallet1/Views/HelperViews/Buttons.swift
@@ -5,8 +5,6 @@
 import SwiftUI
 import Foundation
 
-
-
 extension ShapeStyle where Self == Color {
     static var random: Color {
         Color(
@@ -23,6 +21,7 @@ struct HamburgerButton : View  {
     var body: some View {
         Button(action: action) {
             Image(systemName: "line.3.horizontal")
+//            Image(systemName: "sidebar.squares.leading")
         }
         .accessibilityFont(.title)
         .accessibilityLabel("Main Menu")
@@ -97,6 +96,7 @@ struct TalerButtonStyle: ButtonStyle {
 
     enum TalerButtonStyleType {
         case plain
+        case balance
         case bordered
         case prominent
     }
@@ -106,7 +106,7 @@ struct TalerButtonStyle: ButtonStyle {
     var aligned: TextAlignment = .center
 
     public func makeBody(configuration: ButtonStyle.Configuration) -> some 
View {
-            MyBigButton(type: type,
+            MyBigButton(//type: type,
                    foreColor: foreColor(type: type, pressed: 
configuration.isPressed),
                    backColor: backColor(type: type, pressed: 
configuration.isPressed),
                       dimmed: dimmed,
@@ -124,10 +124,11 @@ struct TalerButtonStyle: ButtonStyle {
 //                                          disabled: disabled(),
 //                                         prominent: type == .prominent)
 //        }
-        return type == .plain ? WalletColors().fieldForeground :
+        return type == .plain ? WalletColors().fieldForeground :      // 
primary text color
             WalletColors().buttonForeColor(pressed: pressed,
-                                           disabled: disabled(),
-                                           prominent: type == .prominent)
+                                          disabled: disabled(),
+                                         prominent: type == .prominent,
+                                           balance: type == .balance)
     }
     func backColor(type: TalerButtonStyleType, pressed: Bool) -> Color {
 //        return if type == .plain {
@@ -137,11 +138,13 @@ struct TalerButtonStyle: ButtonStyle {
 //                                           disabled: disabled(),
 //                                           prominent: type == .prominent)
 //        }
-        return type == .plain ? Color.clear :
+        return type == .plain && !pressed ? Color.clear :
             WalletColors().buttonBackColor(pressed: pressed,
-                                           disabled: disabled(),
-                                           prominent: type == .prominent)
+                                          disabled: disabled(),
+                                         prominent: type == .prominent,
+                                           balance: type == .balance)
     }
+
     struct BackgroundView: View {
         let color: Color
         let dimmed: Bool
@@ -156,7 +159,7 @@ struct TalerButtonStyle: ButtonStyle {
     }
 
     struct MyBigButton: View {
-        var type: TalerButtonStyleType
+//        var type: TalerButtonStyleType
         let foreColor: Color
         let backColor: Color
         let dimmed: Bool
diff --git a/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift 
b/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift
index 66438a6..33a5e44 100644
--- a/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift
+++ b/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift
@@ -14,40 +14,43 @@ struct QRCodeDetailView: View {
 
     var body: some View {
         if talerURI.count > 10 {
-            VStack (alignment: .leading) {
+            Section {
                 Text("Either")
                     .multilineTextAlignment(.leading)
                     .accessibilityFont(.title3)
 //                    .padding(.vertical)
+                    .listRowSeparator(.hidden)
 
                 CopyShare(textToCopy: talerURI)
                     .disabled(false)
 //                    .padding(.bottom)
+                    .listRowSeparator(.hidden)
 
                 let otherParty = incoming ? String(localized: "payer")
                                           : String(localized: "payee")
                 Text("the link to the \(otherParty), or", comment: 
"(copy/share) the link to the other party (payer/payee), or")
                     .multilineTextAlignment(.leading)
                     .accessibilityFont(.title3)
+                    .listRowSeparator(.hidden)
 
                 HStack {
                     Spacer()
                     QRGeneratorView(text: talerURI)
                     Spacer()
                 }
+                .listRowSeparator(.hidden)
 
                 // TODO: use currency formatter instead of .readableDescription
-                let amountStr = (amount == nil) ?
+                let hintStr = (amount == nil) ?
                     (incoming ? String(localized: "let the payer scan this QR 
code to pay.")
                               : String(localized: "let the payee scan this QR 
code to receive."))
                 :   (incoming ? String(localized: "let the payer scan this QR 
code to pay \(amount!.readableDescription).",
-                                       comment: "amount.readableDescription: 
5,3 €")
+                                       comment: "e.g. '5,3 €'")
                               : String(localized: "let the payee scan this QR 
code to receive \(amount!.readableDescription).",
-                                       comment: "amount.readableDescription: 
7.41 $"))
-                Text(amountStr)
+                                       comment: "e.g. '$ 7.41'"))
+                Text(hintStr)
                     .fixedSize(horizontal: false, vertical: true)       // 
wrap in scrollview
                     .accessibilityFont(.title3)
-                    .padding(.top)
             }
         }
     }
diff --git a/TalerWallet1/Helper/AnyTransition+backslide.swift 
b/TalerWallet1/Views/HelperViews/SingleAxisGeometryReader.swift
similarity index 50%
copy from TalerWallet1/Helper/AnyTransition+backslide.swift
copy to TalerWallet1/Views/HelperViews/SingleAxisGeometryReader.swift
index 3cd1d81..e30cb2a 100644
--- a/TalerWallet1/Helper/AnyTransition+backslide.swift
+++ b/TalerWallet1/Views/HelperViews/SingleAxisGeometryReader.swift
@@ -1,5 +1,5 @@
 /* MIT License
- * Copyright (c) 2021 noranraskin
+ * Copyright (c) 2020 Canis from @wooji
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to 
deal
@@ -20,11 +20,33 @@
  * SOFTWARE.
  */
 import SwiftUI
+import UIKit
 
-extension AnyTransition {
-    static var backslide: AnyTransition {
-        AnyTransition.asymmetric(
-            insertion: .move(edge: .trailing),
-            removal: .move(edge: .leading))
+struct SingleAxisGeometryReader<Content: View>: View {
+
+    private struct SizeKey: PreferenceKey {
+        static var defaultValue: CGFloat { 10 }
+        static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
+            value = max(value, nextValue())
+        }
+    }
+
+    @State private var viewSize: CGFloat = SizeKey.defaultValue
+
+    var axis: Axis = .horizontal
+    var alignment: Alignment = .center
+    let content: (CGFloat)->Content
+
+    var body: some View {
+        content(viewSize)
+            .frame(maxWidth:  axis == .horizontal ? .infinity : nil,
+                   maxHeight: axis == .vertical   ? .infinity : nil,
+                   alignment: alignment)
+            .background(GeometryReader { proxy in
+                Color.clear.preference(key: SizeKey.self,
+                                     value: axis == .horizontal ? 
proxy.size.width
+                                                                : 
proxy.size.height)
+            })
+            .onPreferenceChange(SizeKey.self) { viewSize = $0 }
     }
 }
diff --git a/TalerWallet1/Views/HelperViews/ToSButtonView.swift 
b/TalerWallet1/Views/HelperViews/ToSButtonView.swift
index 1bcfb76..45a2a91 100644
--- a/TalerWallet1/Views/HelperViews/ToSButtonView.swift
+++ b/TalerWallet1/Views/HelperViews/ToSButtonView.swift
@@ -9,6 +9,7 @@
 import SwiftUI
 
 struct ToSButtonView: View {
+    let stack: CallStack
     let exchangeBaseUrl: String?
     let viewID: Int         // either VIEW_WITHDRAW_TOS or SHEET_WITHDRAW_TOS
     let p2p: Bool
@@ -21,9 +22,10 @@ struct ToSButtonView: View {
             .multilineTextAlignment(.leading)
             .padding()
         NavigationLink(destination: LazyView {
-            WithdrawTOSView(exchangeBaseUrl: exchangeBaseUrl,
-                                     viewID: viewID,
-                               acceptAction: nil)         // pop back to here
+            WithdrawTOSView(stack: stack.push(),
+                  exchangeBaseUrl: exchangeBaseUrl,
+                           viewID: viewID,
+                     acceptAction: nil)         // pop back to here
         }) {
             Text("Terms of Service")  // VIEW_WITHDRAW_TOS
         }.buttonStyle(TalerButtonStyle(type: .prominent))
@@ -32,5 +34,5 @@ struct ToSButtonView: View {
 }
 
 #Preview {
-    ToSButtonView(exchangeBaseUrl: nil, viewID: 0, p2p: false)
+    ToSButtonView(stack: CallStack("Preview"), exchangeBaseUrl: nil, viewID: 
0, p2p: false)
 }
diff --git a/TalerWallet1/Views/HelperViews/View+needVStack.swift 
b/TalerWallet1/Views/HelperViews/View+needVStack.swift
new file mode 100644
index 0000000..e71cf72
--- /dev/null
+++ b/TalerWallet1/Views/HelperViews/View+needVStack.swift
@@ -0,0 +1,32 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import UIKit
+
+extension View {
+    /// returns true if any of the strings in 'titles' wouldn't fit in a view 
1/'numViews' the size of 'width', with 'spacing'
+    static func needVStack(_ titles: [(String, UIFont)], width: CGFloat, 
spacing: CGFloat,
+                           sameSize: Bool = true, numViews: Int = 2) -> Bool {
+        let padding: CGFloat = 20        // TODO: depend on myListStyle
+        var maxTitleWidth: CGFloat = 0
+        var totalWidth = padding
+
+        for (title, uiFont) in titles {
+            let titleWidth = title.widthOfString(usingUIFont: uiFont)
+            if titleWidth > maxTitleWidth {
+                maxTitleWidth = titleWidth
+            }
+            totalWidth += titleWidth
+        }
+
+        let neededWidth = padding + maxTitleWidth
+        let totalSpacing = spacing * CGFloat(numViews - 1)
+        let availableWidth = (width / CGFloat(numViews)) - totalSpacing
+        totalWidth += totalSpacing
+
+        return sameSize ? neededWidth > availableWidth
+                        : totalWidth > width
+    }
+}
diff --git a/TalerWallet1/Views/Main/MainView.swift 
b/TalerWallet1/Views/Main/MainView.swift
index 7743cf9..7d70698 100644
--- a/TalerWallet1/Views/Main/MainView.swift
+++ b/TalerWallet1/Views/Main/MainView.swift
@@ -33,7 +33,7 @@ struct MainView: View {
 #endif
         Group {
             if controller.backendState == .ready {
-                Content(symLog: symLog, stack: stack.push(), talerFont: 
$talerFont)
+                Content(symLog: symLog, stack: stack.push("Content"), 
talerFont: $talerFont)
                 // any change to rootViewId triggers popToRootView behaviour
                     .id(viewState.rootViewId)
                     .onAppear() {
@@ -65,12 +65,43 @@ struct MainView: View {
         }
     } // body
 }
+// MARK: - TabBar
+enum Tab {
+    case balances, exchanges, settings
+}
+
 // MARK: - Content
 extension MainView {
     struct Content: View {
         let symLog: SymLogV?
         let stack: CallStack
+        @State private var shouldReloadBalances = 0
+        @State private var balances: [Balance] = []
         @Binding var talerFont: Int
+        @AppStorage("iconOnly") var iconOnly: Bool = false
+        let balancesTitle = String(localized: "Balances")
+        let exchangesTitle = String(localized: "Exchanges")
+        let settingsTitle = String(localized: "Settings")
+#if TABBAR  // Taler Wallet
+        @State private var selectedTab: Tab = .balances
+
+        private func tabSelection() -> Binding<Tab> {
+            Binding { //this is the get block
+                self.selectedTab
+            } set: { tappedTab in
+                if tappedTab == self.selectedTab {
+                    //User tapped on the tab twice == Pop to root view
+//                    if homeNavigationStack.isEmpty {
+                        //User already on home view, scroll to top
+//                    } else {
+//                        homeNavigationStack = []
+//                    }
+                }
+                //Set the tab to the tabbed tab
+                self.selectedTab = tappedTab
+            }
+        }
+#else       // GNU Taler
         @State var sidebarVisible: Bool = false
         func hamburgerAction() {
             withAnimation(.easeInOut(duration: 0.25)) {
@@ -78,35 +109,76 @@ extension MainView {
             }
         }
 
-        let balances = String(localized: "Balances")
-        let exchanges = String(localized: "Exchanges")
-        let settings = String(localized: "Settings")
         var views: [SidebarItem] {[
-            SidebarItem(name: balances,
-                    sysImage: "creditcard.fill",        // TODO: Wallet Icon
-                        view: AnyView(BalancesListView(stack: 
stack.push(balances),
-                                                    navTitle: balances,
+            SidebarItem(name: balancesTitle,
+                    sysImage: "chart.bar.xaxis",  // creditcard.fill     // 
TODO: Wallet Icon
+                        view: AnyView(BalancesListView(stack: 
stack.push(balancesTitle),
+                                                    navTitle: balancesTitle,
+                                                    balances: $balances,
+                                        shouldReloadBalances: 
$shouldReloadBalances,
                                              hamburgerAction: hamburgerAction)
                                      )),
-            SidebarItem(name: exchanges,
+            SidebarItem(name: exchangesTitle,
                     sysImage: "building.columns",
-                        view: AnyView(ExchangeListView(stack: 
stack.push(exchanges),
-                                                    navTitle: exchanges,
+                        view: AnyView(ExchangeListView(stack: 
stack.push(exchangesTitle),
+//                                                    balances: $balances,
+                                                    navTitle: exchangesTitle,
                                              hamburgerAction: hamburgerAction)
                                      )),
-            SidebarItem(name: settings,    // TODO: "About"?
+            SidebarItem(name: settingsTitle,    // TODO: "About"?
                     sysImage: "gearshape.fill",
-                        view: AnyView(SettingsView(navTitle: settings,
-                                            hamburgerAction: hamburgerAction)
+                        view: AnyView(SettingsView(stack: 
stack.push(settingsTitle),
+                                                navTitle: settingsTitle,
+                                         hamburgerAction: hamburgerAction)
                                      ))
         ]}
         @State var currentView: Int = 0
+#endif
 
         var body: some View {
 #if DEBUG
             let _ = Self._printChanges()
             let _ = symLog?.vlog()       // just to get the # to compare it 
with .onAppear & onDisappear
 #endif
+          Group {
+#if TABBAR  // Taler Wallet
+//            let labelStyle = iconOnly ? IconOnlyLabelStyle() : 
TitleAndIconLabelStyle()    // labelStyle doesn't work
+            TabView(selection: tabSelection()) {
+                NavigationView {
+                    BalancesListView(stack: stack.push(balancesTitle),
+                                  navTitle: balancesTitle,
+                                  balances: $balances,
+                      shouldReloadBalances: $shouldReloadBalances)
+                }
+                .tabItem {
+                    Image(systemName: "chart.bar.xaxis")             // 
creditcard     system will automatically use filled variant
+                    if !iconOnly { Text(balancesTitle) }
+                }
+                .tag(Tab.balances)
+                .badge(0)         // TODO: set badge if transaction finished 
in background
+
+                NavigationView {
+                    ExchangeListView(stack: stack.push(exchangesTitle),
+//                                  balances: $balances,
+                                  navTitle: exchangesTitle)
+                }
+                .tabItem {
+                    Image(systemName: "building.columns")
+                    if !iconOnly { Text(exchangesTitle) }
+                }
+                .tag(Tab.exchanges)
+
+                NavigationView {
+                    SettingsView(stack: stack.push(), navTitle: settingsTitle)
+                }
+                .tabItem {
+                    Image(systemName: "gear")                   // system will 
automatically use filled variant
+                    if !iconOnly { Text(settingsTitle) }
+                }
+                .tag(Tab.settings)
+            }
+//            .animation(.linear(duration: LAUNCHDURATION), value: 
selectedTab)     doesn't work. Needs CustomTabView
+#else       // GNU Taler
             ZStack(alignment: .leading) {
                 NavigationView {   // the one and only for all non-sheet views
                     VStack(alignment: .leading) {   // only needed for 
backslide transition
@@ -119,9 +191,9 @@ extension MainView {
                         .background(NavigationBarBuilder { 
navigationController in
                             //                            
navigationController.navigationBar.barTintColor = .red
                             
navigationController.navigationBar.titleTextAttributes =
-                                [.font: TalerFont.talerFont(talerFont, size: 
24, relativeTo: .title2)]
+                                [.font: TalerFont.uiFont(talerFont, size: 24, 
relativeTo: .title2)]
                             
navigationController.navigationBar.largeTitleTextAttributes =
-                                [.font: TalerFont.talerFont(talerFont, size: 
38, relativeTo: .largeTitle)]
+                                [.font: TalerFont.uiFont(talerFont, size: 38, 
relativeTo: .largeTitle)]
                         })
                 }
                 .navigationViewStyle(.stack)
@@ -134,6 +206,13 @@ extension MainView {
                    sidebarVisible: sidebarVisible,
                   hamburgerAction: hamburgerAction)
             }
-        }
+#endif
+          }
+            .onNotification(.BalanceChange) { notification in
+              // reload balances on receiving BalanceChange notification ...
+                symLog?.log(".onNotification(.BalanceChange) ==> reload")
+                shouldReloadBalances += 1
+            }
+        } // body
     } // Content
 }
diff --git a/TalerWallet1/Views/Main/SideBarView.swift 
b/TalerWallet1/Views/Main/SideBarView.swift
index 9072542..0ae3b1d 100644
--- a/TalerWallet1/Views/Main/SideBarView.swift
+++ b/TalerWallet1/Views/Main/SideBarView.swift
@@ -97,10 +97,10 @@ struct SideBarView_Previews: PreviewProvider {
     static var views: [SidebarItem] {[
         SidebarItem(name: "Balances",
                 sysImage: "creditcard.fill",        // TODO: Wallet Icon
-                    view: AnyView(WalletEmptyView())),
+                    view: AnyView(WalletEmptyView(stack: 
CallStack("Preview")))),
         SidebarItem(name: "Settings",
                 sysImage: "gearshape.fill",
-                    view: AnyView(WalletEmptyView()))
+                    view: AnyView(WalletEmptyView(stack: 
CallStack("Preview"))))
     ]}
     static var previews: some View {
         BindingViewContainer(views: views)
diff --git a/TalerWallet1/Views/Main/WalletEmptyView.swift 
b/TalerWallet1/Views/Main/WalletEmptyView.swift
index 1e691e6..c357f03 100644
--- a/TalerWallet1/Views/Main/WalletEmptyView.swift
+++ b/TalerWallet1/Views/Main/WalletEmptyView.swift
@@ -10,6 +10,7 @@ import SymLog
 
 struct WalletEmptyView: View {
     private let symLog = SymLogV(0)
+    let stack: CallStack
     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     var body: some View {
@@ -31,13 +32,13 @@ struct WalletEmptyView: View {
         .accessibilityFont(.title2)
         .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .onAppear() {
-            DebugViewC.shared.setViewID(VIEW_EMPTY)     // 10
+            DebugViewC.shared.setViewID(VIEW_EMPTY, stack: 
stack.push("onAppear"))     // 10
         }
     }
 }
 
 struct WalletEmptyView_Previews: PreviewProvider {
     static var previews: some View {
-        WalletEmptyView()
+        WalletEmptyView(stack: CallStack("Preview"))
     }
 }
diff --git a/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift 
b/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
index 577ecc5..339a8f6 100644
--- a/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
+++ b/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
@@ -86,7 +86,7 @@ struct PaymentPurpose: View {
         .navigationTitle("Request")
         .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .onAppear {
-            DebugViewC.shared.setViewID(VIEW_RECEIVE_PURPOSE)
+            DebugViewC.shared.setViewID(VIEW_RECEIVE_PURPOSE, stack: 
stack.push())
             print("❗️ PaymentPurpose onAppear")
         }
         .onDisappear {
diff --git a/TalerWallet1/Views/Peer2peer/RequestPayment.swift 
b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
index 3460039..028dc1c 100644
--- a/TalerWallet1/Views/Peer2peer/RequestPayment.swift
+++ b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
@@ -62,7 +62,7 @@ struct RequestPayment: View {
         .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .navigationTitle(navTitle)
         .onAppear {   // make CurrencyField show the keyboard
-            DebugViewC.shared.setViewID(VIEW_RECEIVE_P2P)
+            DebugViewC.shared.setViewID(VIEW_RECEIVE_P2P, stack: stack.push())
             symLog.log("❗️Yikes \(navTitle) onAppear")
         }
         .onDisappear {
diff --git a/TalerWallet1/Views/Peer2peer/SendAmount.swift 
b/TalerWallet1/Views/Peer2peer/SendAmount.swift
index a7475cd..5fa6693 100644
--- a/TalerWallet1/Views/Peer2peer/SendAmount.swift
+++ b/TalerWallet1/Views/Peer2peer/SendAmount.swift
@@ -75,7 +75,7 @@ struct SendAmount: View {
         .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .navigationTitle(navTitle)
         .onAppear {   // make CurrencyField show the keyboard
-            DebugViewC.shared.setViewID(VIEW_SEND_P2P)
+            DebugViewC.shared.setViewID(VIEW_SEND_P2P, stack: stack.push())
             symLog.log("❗️Yikes SendAmount onAppear")
         }
         .onDisappear {
diff --git a/TalerWallet1/Views/Peer2peer/SendPurpose.swift 
b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
index 3117450..b0c4673 100644
--- a/TalerWallet1/Views/Peer2peer/SendPurpose.swift
+++ b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
@@ -103,7 +103,7 @@ struct SendPurpose: View {
         .navigationTitle("Purpose")
         .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .onAppear {
-            DebugViewC.shared.setViewID(VIEW_SEND_PURPOSE)
+            DebugViewC.shared.setViewID(VIEW_SEND_PURPOSE, stack: stack.push())
             print("❗️ SendPurpose onAppear")
         }
         .onDisappear {
diff --git a/TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift 
b/TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift
index 2612eea..f16288a 100644
--- a/TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift
+++ b/TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift
@@ -7,6 +7,7 @@ import SymLog
 
 struct PendingOpsListView: View {
     let navTitle = String(localized: "Pending")
+    let stack: CallStack
 
     @EnvironmentObject private var model: WalletModel
 
@@ -14,7 +15,7 @@ struct PendingOpsListView: View {
 
     var body: some View {
         let reloadAction = model.getPendingOperationsM
-        Content(pendingOperations: $pendingOperations, reloadAction: 
reloadAction)
+        Content(stack: stack.push(), pendingOperations: $pendingOperations, 
reloadAction: reloadAction)
             .navigationTitle(navTitle)
             .task {
                 pendingOperations = await reloadAction()
@@ -24,6 +25,7 @@ struct PendingOpsListView: View {
 // MARK: -
 extension PendingOpsListView {
     struct Content: View {
+        let stack: CallStack
         @Binding var pendingOperations: [PendingOperation]
         var reloadAction: () async -> [PendingOperation]
         @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
@@ -34,7 +36,7 @@ extension PendingOpsListView {
                 }
                 .listStyle(myListStyle.style).anyView
                 .onAppear() {
-                    DebugViewC.shared.setViewID(VIEW_PENDING)
+                    DebugViewC.shared.setViewID(VIEW_PENDING, stack: 
stack.push())
                 }
                 .refreshable {
                     pendingOperations = await reloadAction()
diff --git a/TalerWallet1/Views/Settings/SettingsItem.swift 
b/TalerWallet1/Views/Settings/SettingsItem.swift
index e49194c..ae61cf2 100644
--- a/TalerWallet1/Views/Settings/SettingsItem.swift
+++ b/TalerWallet1/Views/Settings/SettingsItem.swift
@@ -63,7 +63,7 @@ struct SettingsFont: View {
     let action: (Int) -> Void
 
     @State private var selected = 0
-    let fonts = [String(localized: "Standard iOS Font"), 
"Atkinson-Hyperlegible", "Nunito", "Nunito Italic"]
+    let fonts = [String(localized: "Standard iOS Font"), 
"Atkinson-Hyperlegible", "Nunito"]
 
     var body: some View {
         Picker(title, selection: $selected, content: {
diff --git a/TalerWallet1/Views/Settings/SettingsView.swift 
b/TalerWallet1/Views/Settings/SettingsView.swift
index cf2734e..3bf5b69 100644
--- a/TalerWallet1/Views/Settings/SettingsView.swift
+++ b/TalerWallet1/Views/Settings/SettingsView.swift
@@ -19,6 +19,7 @@ import SymLog
 
 struct SettingsView: View {
     private let symLog = SymLogV(0)
+    let stack: CallStack
     let navTitle: String
 
     @EnvironmentObject private var controller: Controller
@@ -32,8 +33,12 @@ struct SettingsView: View {
     @AppStorage("talerFont") var talerFont: Int = 0
     @AppStorage("developDelay") var developDelay: Bool = false
     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+    @AppStorage("iconOnly") var iconOnly: Bool = false
 
+#if TABBAR  // Taler Wallet
+#else       // GNU Taler
     var hamburgerAction: () -> Void
+#endif
 
     @EnvironmentObject private var model: WalletModel
 
@@ -79,6 +84,11 @@ struct SettingsView: View {
 #if DEBUG
         let _ = Self._printChanges()
         let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+#if TABBAR  // Taler Wallet
+        let hamburger: HamburgerButton? = nil
+#else       // GNU Taler
+        let hamburger: HamburgerButton = HamburgerButton(action: 
hamburgerAction)
 #endif
         let walletCore = WalletCore.shared
         Group {
@@ -91,15 +101,17 @@ struct SettingsView: View {
                                 description: String(localized: "When a 
transaction finished"))
                 SettingsFont(title: String(localized: "Font:"), value: 
talerFont, action: redraw)
                 SettingsStyle(title: String(localized: "Liststyle:"), 
myListStyle: $myListStyle)
+                SettingsToggle(name: String(localized: "Minimalistic"), value: 
$iconOnly,
+                               description: String(localized: "Omit text where 
possible"))
                 if diagnosticModeEnabled {
                     SettingsToggle(name: String(localized: "Developer Mode"), 
value: $developerMode,
                             description: String(localized: "More information 
intended for debugging")) {
-                        DebugViewC.shared.setViewID(VIEW_SETTINGS)
+                        DebugViewC.shared.setViewID(VIEW_SETTINGS, stack: 
stack.push())
                         withAnimation { showDevelopItems = developerMode }
                     }
                     if showDevelopItems {  // show or hide the following items
                         NavigationLink {        // whole row like in a 
tableView
-                            LazyView { PendingOpsListView() }
+                            LazyView { PendingOpsListView(stack: stack.push()) 
}
                         } label: {
                             SettingsItem(name: String(localized: "Pending 
Operations"),
                                   description: String(localized: "Exchange not 
yet ready...")) {}
@@ -249,10 +261,14 @@ struct SettingsView: View {
             .listStyle(myListStyle.style).anyView
         }
         .navigationTitle(navTitle)
-        .navigationBarItems(leading: HamburgerButton(action: hamburgerAction))
+        .navigationBarItems(leading: hamburger)
         .onAppear() {
             showDevelopItems = developerMode
-            DebugViewC.shared.setViewID(VIEW_SETTINGS)
+            DebugViewC.shared.setViewID(VIEW_SETTINGS, stack: stack.push())
+        }
+        .onDisappear() {
+            checkDisabled = false    // reset
+            withDrawDisabled = false
         }
         .alert("Reset Wallet",
                isPresented: $showResetAlert,
@@ -286,7 +302,11 @@ extension Bundle {
 #if DEBUG
 struct SettingsView_Previews: PreviewProvider {
     static var previews: some View {
-        SettingsView(navTitle: "Settings") { }
+#if TABBAR  // Taler Wallet
+        SettingsView(stack: CallStack("Preview"), navTitle: "Settings")
+#else       // GNU Taler
+        SettingsView(stack: CallStack("Preview"), navTitle: "Settings") { }
+#endif
     }
 }
 #endif
diff --git a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift 
b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
index 6ff8f16..26fc4a3 100644
--- a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
+++ b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
@@ -50,9 +50,10 @@ struct P2pReceiveURIView: View {
                     .buttonStyle(TalerButtonStyle(type: .prominent))
                     .padding(.horizontal)
                 } else {
-                    ToSButtonView(exchangeBaseUrl: nil,
-                                           viewID: SHEET_RCV_P2P_TOS,
-                                              p2p: true)
+                    ToSButtonView(stack: stack.push(),
+                        exchangeBaseUrl: nil,
+                                 viewID: SHEET_RCV_P2P_TOS,
+                                    p2p: true)
                 }
             } else {
                 // Yikes no details or no baseURL
diff --git a/TalerWallet1/Views/Sheets/URLSheet.swift 
b/TalerWallet1/Views/Sheets/URLSheet.swift
index b9f5751..4bb7a10 100644
--- a/TalerWallet1/Views/Sheets/URLSheet.swift
+++ b/TalerWallet1/Views/Sheets/URLSheet.swift
@@ -13,7 +13,11 @@ struct URLSheet: View {
     @EnvironmentObject private var controller: Controller
 
     var body: some View {
-        let urlCommand = controller.openURL(urlToOpen)
+#if DEBUG
+        let _ = Self._printChanges()
+        let _ = symLog.vlog()       // just to get the # to compare it with 
.onAppear & onDisappear
+#endif
+        let urlCommand = controller.openURL(urlToOpen, stack: stack.push())
 
         Group {
             switch urlCommand {
diff --git 
a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift 
b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift
index 3df21c8..bf584a5 100644
--- a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawTOSView.swift
@@ -7,6 +7,7 @@ import SymLog
 
 struct WithdrawTOSView: View {
     private let symLog = SymLogV(0)
+    let stack: CallStack
     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     let navTitle = String(localized: "Terms of Service")
@@ -56,7 +57,7 @@ struct WithdrawTOSView: View {
             if viewID > SHEET_WITHDRAWAL {
                 DebugViewC.shared.setSheetID(SHEET_WITHDRAW_TOS)
             } else {
-                DebugViewC.shared.setViewID(VIEW_WITHDRAW_TOS)
+                DebugViewC.shared.setViewID(VIEW_WITHDRAW_TOS, stack: 
stack.push())
             }
         }.task {
             do {
diff --git 
a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift 
b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
index 9388983..0dc3a9e 100644
--- a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
@@ -60,9 +60,10 @@ struct WithdrawURIView: View {
                     .buttonStyle(TalerButtonStyle(type: .prominent))
                     .padding(.horizontal)
                 } else {
-                    ToSButtonView(exchangeBaseUrl: exchangeBaseUrl,
-                                           viewID: SHEET_WITHDRAW_TOS,
-                                              p2p: false)
+                    ToSButtonView(stack: stack.push(),
+                        exchangeBaseUrl: exchangeBaseUrl,
+                                 viewID: SHEET_WITHDRAW_TOS,
+                                    p2p: false)
                 }
             } else {
                 // Yikes no details or no baseURL
diff --git a/TalerWallet1/Views/Transactions/ThreeAmounts.swift 
b/TalerWallet1/Views/Transactions/ThreeAmounts.swift
index ff01a1a..83d8d73 100644
--- a/TalerWallet1/Views/Transactions/ThreeAmounts.swift
+++ b/TalerWallet1/Views/Transactions/ThreeAmounts.swift
@@ -46,15 +46,14 @@ struct ThreeAmountsView: View {
         let labelColor = Color(UIColor.label)
         let foreColor = pending ? WalletColors().pendingColor(incoming)
                                 : WalletColors().transactionColor(incoming)
-        VStack {
+        Section {
             AmountView(title: topTitle,
                        value: topAmount.readableDescription,
                        color: labelColor,
                        large: large)
                 .padding(.bottom, 4)
             if let fee {
-                let feeSign = incoming ? "- " : "+ "
-                AmountView(title: feeSign + String(localized: "Exchange fee:"),
+                AmountView(title: String(localized: "Exchange fee:"),
                            value: fee.readableDescription,
                            color: labelColor,
                            large: false)
@@ -73,10 +72,9 @@ struct ThreeAmountsView: View {
                         Spacer()
                         Text(baseURL.trimURL())
                             .multilineTextAlignment(.center)
-                            .accessibilityFont(large ? .title2 : .title3)
+                            .accessibilityFont(large ? .title3 : .body)
 //                          .fontWeight(large ? .medium : .regular)  // 
@available(iOS 16.0, *)
                             .foregroundColor(labelColor)
-                        Spacer()
                     }
                 }
                 .padding(.top, 4)
diff --git a/TalerWallet1/Views/Transactions/TransactionDetailView.swift 
b/TalerWallet1/Views/Transactions/TransactionDetailView.swift
index 97953e5..409b37b 100644
--- a/TalerWallet1/Views/Transactions/TransactionDetailView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionDetailView.swift
@@ -77,7 +77,7 @@ struct TransactionDetailView: View {
                     Spacer()
                     Text("Status: \(common.txState.major.localizedState)")
                 }    .listRowSeparator(.automatic)
-                    .accessibilityFont(.title2)
+                    .accessibilityFont(.title)
                 SwitchCase(transaction: $transaction, hasDone: doneAction != 
nil)
 
                 if transaction.isAbortable { if let abortAction {
@@ -158,7 +158,7 @@ struct TransactionDetailView: View {
         }
         .onAppear {
             symLog.log("onAppear")
-            DebugViewC.shared.setViewID(VIEW_TRANSACTIONDETAIL)
+            DebugViewC.shared.setViewID(VIEW_TRANSACTIONDETAIL, stack: 
stack.push())
         }
         .onDisappear {
             symLog.log("onDisappear")
diff --git a/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift 
b/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift
index 73dc463..1e44551 100644
--- a/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionsEmptyView.swift
@@ -10,6 +10,7 @@ import SymLog
 
 struct TransactionsEmptyView: View {
     private let symLog = SymLogV(0)
+    let stack: CallStack
     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
     let currency: String
@@ -25,13 +26,13 @@ struct TransactionsEmptyView: View {
 //        .padding(.vertical)
         .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .onAppear() {
-            DebugViewC.shared.setViewID(VIEW_EMPTY)     // 10
+            DebugViewC.shared.setViewID(VIEW_EMPTY, stack: stack.push())     
// 10
         }
     }
 }
 
 struct TransactionsEmptyView_Previews: PreviewProvider {
     static var previews: some View {
-        TransactionsEmptyView(currency: LONGCURRENCY)
+        TransactionsEmptyView(stack: CallStack("Preview"), currency: 
LONGCURRENCY)
     }
 }
diff --git a/TalerWallet1/Views/Transactions/TransactionsListView.swift 
b/TalerWallet1/Views/Transactions/TransactionsListView.swift
index 3548566..8322ba5 100644
--- a/TalerWallet1/Views/Transactions/TransactionsListView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionsListView.swift
@@ -68,11 +68,11 @@ struct TransactionsListView: View {
         }
         .overlay {
             if transactions.isEmpty {
-                TransactionsEmptyView(currency: currency)
+                TransactionsEmptyView(stack: stack.push(), currency: currency)
             }
         }
         .onAppear {
-            DebugViewC.shared.setViewID(VIEW_TRANSACTIONLIST)
+            DebugViewC.shared.setViewID(VIEW_TRANSACTIONLIST, stack: 
stack.push())
         }
     }
 }
diff --git a/TestFlight/WhatToTest.en-US.txt b/TestFlight/WhatToTest.en-US.txt
index 9611ffa..15cf835 100644
--- a/TestFlight/WhatToTest.en-US.txt
+++ b/TestFlight/WhatToTest.en-US.txt
@@ -1,4 +1,22 @@
 
+Version 0.9.3 (20)
+
+• A/B test: GNU Taler with hamburger button and sideview, Taler Wallet with 
tab bar
+• New feature: bar graphic after currency header name representing recent 
transactions
+
+- Usability improvements
+  Button layout in Balances and Exchanges
+- Bugfix: Textsize is changeable again
+  (but font is not changeable in this version)
+
+
+Version 0.9.3 (19)
+
+• New feature: database is now sqlite3
+
+- Usability improvements
+
+
 Version 0.9.3 (18)
 
 - Should work now with https://kaufen.tschunk.shop
diff --git a/taler-swift/Sources/taler-swift/Amount.swift 
b/taler-swift/Sources/taler-swift/Amount.swift
index 2e91e6c..088e237 100644
--- a/taler-swift/Sources/taler-swift/Amount.swift
+++ b/taler-swift/Sources/taler-swift/Amount.swift
@@ -38,11 +38,33 @@ enum AmountError: Error {
     case divideByZero
 }
 
-public struct ScopedCurrencyInfo: Codable, Sendable {
+public struct CurrencySpecification: Codable, Sendable {
+    enum CodingKeys: String, CodingKey {
+        case decimalSeparator = "decimal_separator"
+        case name = "name"
+        case fractionalInputDigits = "num_fractional_input_digits"
+        case fractionalNormalDigits = "num_fractional_normal_digits"
+        case fractionalTrailingZeroDigits = 
"num_fractional_trailing_zero_digits"
+        case isCurrencyNameLeading = "is_currency_name_leading"
+        case altUnitNames = "alt_unit_names"
+    }
+    /// e.g. “.” for $ and ¥;  “,” for €
     let decimalSeparator: String
-    let numFractionalDigits: Int        // 0 Yen, 2 €,$, 3 arabic
-    let numTinyDigits: Int              // SuperScriptDigits
+    /// some name for this CurrencySpecification
+    let name: String
+    /// how much digits the user may enter after the decimal separator
+    let fractionalInputDigits: Int
+    /// €,$,£: 2;  some arabic currencies: 3,  ¥: 0
+    let fractionalNormalDigits: Int
+    /// usually same as numFractionalNormalDigits, but e.g. might be 2 for ¥
+    let fractionalTrailingZeroDigits: Int
+    /// true for “$ 3.50”;  false for “3,50 €”
     let isCurrencyNameLeading: Bool
+    /// map of powers of 10 to alternative currency names / symbols
+    /// must always have an entry under "0" that defines the base name
+    /// e.g.  "0 => €" or "3 => k€". For BTC, would be "0 => BTC, -3 => mBTC".
+    /// This way, we can also communicate the currency symbol to be used.
+    let altUnitNames: [Int : String]
 }
 
 /// A value of some currency.
@@ -69,7 +91,7 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
     var fraction: UInt32
 
     /// Additional info for formatting currency strings
-    var currencyInfo: ScopedCurrencyInfo?
+    var currencySpecification: CurrencySpecification?
 
     public func hash(into hasher: inout Hasher) {
         hasher.combine(currency)
@@ -96,8 +118,8 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
     /// The string representation of the value, formatted as 
"`integer`.`fraction`".
     public var valueStr: String {
         var decimalSeparator = "."
-        if let currencyInfo {
-            decimalSeparator = currencyInfo.decimalSeparator
+        if let currencySpecification {      // TODO: use locale
+            decimalSeparator = currencySpecification.decimalSeparator
         }
         if fraction == 0 {
             return "\(integer)"

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