[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.
- [taler-taler-ios] branch master updated (bb640f0 -> 65313c5),
gnunet <=
- [taler-taler-ios] 04/32: CallStack, gnunet, 2023/10/15
- [taler-taler-ios] 19/32: AccessibilityNotification.Announcement, gnunet, 2023/10/15
- [taler-taler-ios] 01/32: products seem no longer mandatory, gnunet, 2023/10/15
- [taler-taler-ios] 18/32: updateExchange, gnunet, 2023/10/15
- [taler-taler-ios] 14/32: fix bars, gnunet, 2023/10/15
- [taler-taler-ios] 03/32: Debugging, gnunet, 2023/10/15
- [taler-taler-ios] 17/32: getCurrencySpecification, gnunet, 2023/10/15
- [taler-taler-ios] 02/32: Layout for QR-View and ThreeAmounts, gnunet, 2023/10/15
- [taler-taler-ios] 08/32: cleanup, gnunet, 2023/10/15
- [taler-taler-ios] 13/32: width of rendered string, gnunet, 2023/10/15