[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-taler-ios] branch master updated (adfc919 -> a0c6bdb)
From: |
gnunet |
Subject: |
[taler-taler-ios] branch master updated (adfc919 -> a0c6bdb) |
Date: |
Sat, 21 Oct 2023 21:09: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 adfc919 disable some debug output
new 28e5f78 CurrencySpecification
new 49524f0 HTTPError
new 0ebc3a8 ExchangeListItem
new 3e2da08 cleanup
new 1dfae88 getCurrencyInfo(scope)
new fa93599 listExchangesForScopedCurrency
new cefa3ed cache currency infos
new fba7fcf AboutView
new 0762407 Call AboutView
new aec0b49 Use CurrencyFormatter for Balance
new a0c6bdb Minimalistic
The 11 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 | 20 ++-
TalerWallet1/Backend/WalletBackendRequest.swift | 138 ++++++------------
TalerWallet1/Controllers/Controller.swift | 30 ++++
TalerWallet1/Controllers/DebugViewC.swift | 7 +-
TalerWallet1/Helper/CurrencyFormatter.swift | 30 ----
TalerWallet1/Helper/CurrencySpecification.swift | 158 +++++++++++++++++++++
TalerWallet1/Helper/TalerStrings.swift | 22 +++
TalerWallet1/Model/Model+Balances.swift | 4 +-
TalerWallet1/Model/Model+Exchange.swift | 90 ++++++++----
TalerWallet1/Model/Model+P2P.swift | 38 ++---
TalerWallet1/Model/Model+Payment.swift | 12 +-
TalerWallet1/Model/Model+Pending.swift | 2 +-
TalerWallet1/Model/Model+Settings.swift | 33 ++---
TalerWallet1/Model/Model+Transactions.swift | 26 ++--
TalerWallet1/Model/Model+Withdraw.swift | 41 ++----
TalerWallet1/Model/WalletModel.swift | 20 ++-
TalerWallet1/Views/Balances/BalanceRowView.swift | 7 +-
TalerWallet1/Views/Balances/BalancesListView.swift | 2 +-
.../Views/Balances/BalancesSectionView.swift | 29 ++--
TalerWallet1/Views/Main/MainView.swift | 19 +++
TalerWallet1/Views/Peer2peer/PaymentPurpose.swift | 8 +-
TalerWallet1/Views/Peer2peer/SendPurpose.swift | 8 +-
TalerWallet1/Views/Settings/AboutView.swift | 111 +++++++++++++++
TalerWallet1/Views/Settings/SettingsItem.swift | 24 +++-
TalerWallet1/Views/Settings/SettingsView.swift | 105 ++++++--------
taler-swift/Sources/taler-swift/Amount.swift | 37 +----
26 files changed, 640 insertions(+), 381 deletions(-)
delete mode 100644 TalerWallet1/Helper/CurrencyFormatter.swift
create mode 100644 TalerWallet1/Helper/CurrencySpecification.swift
create mode 100644 TalerWallet1/Views/Settings/AboutView.swift
diff --git a/TalerWallet.xcodeproj/project.pbxproj
b/TalerWallet.xcodeproj/project.pbxproj
index dbec31f..cbefbd1 100644
--- a/TalerWallet.xcodeproj/project.pbxproj
+++ b/TalerWallet.xcodeproj/project.pbxproj
@@ -7,7 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
- 4E16E12329F3BB99008B9C86 /* CurrencyFormatter.swift in Sources
*/ = {isa = PBXBuildFile; fileRef = 4E16E12229F3BB99008B9C86 /*
CurrencyFormatter.swift */; };
+ 4E16E12329F3BB99008B9C86 /* CurrencySpecification.swift in
Sources */ = {isa = PBXBuildFile; fileRef = 4E16E12229F3BB99008B9C86 /*
CurrencySpecification.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 */; };
@@ -74,7 +74,7 @@
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 */; };
- 4E3EAE532A990778009F1BE8 /* CurrencyFormatter.swift in Sources
*/ = {isa = PBXBuildFile; fileRef = 4E16E12229F3BB99008B9C86 /*
CurrencyFormatter.swift */; };
+ 4E3EAE532A990778009F1BE8 /* CurrencySpecification.swift in
Sources */ = {isa = PBXBuildFile; fileRef = 4E16E12229F3BB99008B9C86 /*
CurrencySpecification.swift */; };
4E3EAE542A990778009F1BE8 /* TalerDater.swift in Sources */ =
{isa = PBXBuildFile; fileRef = 4EB095062989CB7C0043A8A1 /* TalerDater.swift */;
};
4E3EAE552A990778009F1BE8 /* Model+Balances.swift in Sources */
= {isa = PBXBuildFile; fileRef = 4E3B4BC42A428AF700CC88B8 /*
Model+Balances.swift */; };
4E3EAE562A990778009F1BE8 /* LocalizedAlertError.swift in
Sources */ = {isa = PBXBuildFile; fileRef = 4E363CC12A2621C200D7E98C /*
LocalizedAlertError.swift */; };
@@ -231,6 +231,8 @@
4EBA56412A7FF5200084948B /* PayTemplateView.swift in Sources */
= {isa = PBXBuildFile; fileRef = 4EBA56402A7FF5200084948B /*
PayTemplateView.swift */; };
4EBA82AB2A3EB2CA00E5F39A /* TransactionButton.swift in Sources
*/ = {isa = PBXBuildFile; fileRef = 4EBA82AA2A3EB2CA00E5F39A /*
TransactionButton.swift */; };
4EBA82AD2A3F580500E5F39A /* QuiteSomeCoins.swift in Sources */
= {isa = PBXBuildFile; fileRef = 4EBA82AC2A3F580500E5F39A /*
QuiteSomeCoins.swift */; };
+ 4EC400892AE3E7E800DF72C7 /* AboutView.swift in Sources */ =
{isa = PBXBuildFile; fileRef = 4EC400882AE3E7E800DF72C7 /* AboutView.swift */;
};
+ 4EC4008A2AE3E7E800DF72C7 /* AboutView.swift in Sources */ =
{isa = PBXBuildFile; fileRef = 4EC400882AE3E7E800DF72C7 /* AboutView.swift */;
};
4EC90C782A1B528B0071DC58 /* ExchangeSectionView.swift in
Sources */ = {isa = PBXBuildFile; fileRef = 4EC90C772A1B528B0071DC58 /*
ExchangeSectionView.swift */; };
4ECB62802A0BA6DF004ABBB7 /* Model+P2P.swift in Sources */ =
{isa = PBXBuildFile; fileRef = 4ECB627F2A0BA6DF004ABBB7 /* Model+P2P.swift */;
};
4ECB62822A0BB01D004ABBB7 /* SelectDays.swift in Sources */ =
{isa = PBXBuildFile; fileRef = 4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */;
};
@@ -287,7 +289,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 4E16E12229F3BB99008B9C86 /* CurrencyFormatter.swift */ = {isa =
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path
= CurrencyFormatter.swift; sourceTree = "<group>"; };
+ 4E16E12229F3BB99008B9C86 /* CurrencySpecification.swift */ =
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType =
sourcecode.swift; path = CurrencySpecification.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>"; };
@@ -399,6 +401,7 @@
4EBA56402A7FF5200084948B /* PayTemplateView.swift */ = {isa =
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path
= PayTemplateView.swift; sourceTree = "<group>"; };
4EBA82AA2A3EB2CA00E5F39A /* TransactionButton.swift */ = {isa =
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path
= TransactionButton.swift; sourceTree = "<group>"; };
4EBA82AC2A3F580500E5F39A /* QuiteSomeCoins.swift */ = {isa =
PBXFileReference; lastKnownFileType = sourcecode.swift; path =
QuiteSomeCoins.swift; sourceTree = "<group>"; };
+ 4EC400882AE3E7E800DF72C7 /* AboutView.swift */ = {isa =
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path
= AboutView.swift; sourceTree = "<group>"; };
4EC90C772A1B528B0071DC58 /* ExchangeSectionView.swift */ = {isa
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift;
path = ExchangeSectionView.swift; sourceTree = "<group>"; };
4ECB627F2A0BA6DF004ABBB7 /* Model+P2P.swift */ = {isa =
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path
= "Model+P2P.swift"; sourceTree = "<group>"; };
4ECB62812A0BB01D004ABBB7 /* SelectDays.swift */ = {isa =
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path
= SelectDays.swift; sourceTree = "<group>"; };
@@ -569,7 +572,7 @@
4E363CBD2A23CB2100D7E98C /*
AnyTransition+backslide.swift */,
4E3327B92AD1635100BF5AD6 /*
AsyncSemaphore.swift */,
4EDBDCD82AB787CB00925C02 /* CallStack.swift */,
- 4E16E12229F3BB99008B9C86 /*
CurrencyFormatter.swift */,
+ 4E16E12229F3BB99008B9C86 /*
CurrencySpecification.swift */,
4EAD117529F672FA008EDD0B /*
KeyboardResponder.swift */,
4E363CC12A2621C200D7E98C /*
LocalizedAlertError.swift */,
4E578E912A481D8600F21F1C /*
Controller+playSound.swift */,
@@ -643,6 +646,7 @@
isa = PBXGroup;
children = (
4EB095252989CBFE0043A8A1 /* SettingsView.swift
*/,
+ 4EC400882AE3E7E800DF72C7 /* AboutView.swift */,
4EB095262989CBFE0043A8A1 /* SettingsItem.swift
*/,
4EB0954B2989CBFE0043A8A1 /* Pending */,
);
@@ -794,7 +798,7 @@
D14AFD1E24D232B300C51073 /* Products */ = {
isa = PBXGroup;
children = (
- 4E3EAE892A990778009F1BE8 /* GNU_Taler.app */,
+ 4E3EAE892A990778009F1BE8 /* GNU_Taler.app */,
D14AFD1D24D232B300C51073 /* Taler_Wallet.app */,
D14AFD3324D232B500C51073 /* TalerTests.xctest
*/,
D14AFD3E24D232B500C51073 /* TalerUITests.xctest
*/,
@@ -1037,6 +1041,7 @@
4E605DB72AB05E48002FB9A7 /*
View+flippedDirection.swift in Sources */,
4E983C2C2ADC416800FA9CC5 /*
View+needVStack.swift in Sources */,
4E3EAE272A990778009F1BE8 /* WalletColors.swift
in Sources */,
+ 4EC400892AE3E7E800DF72C7 /* AboutView.swift in
Sources */,
4E3EAE282A990778009F1BE8 /*
BalancesListView.swift in Sources */,
4E3EAE292A990778009F1BE8 /*
WalletBackendError.swift in Sources */,
4E3EAE2A2A990778009F1BE8 /*
PendingRowView.swift in Sources */,
@@ -1084,7 +1089,7 @@
4E3EAE502A990778009F1BE8 /*
Model+Transactions.swift in Sources */,
4E3EAE512A990778009F1BE8 /*
Controller+playSound.swift in Sources */,
4E3EAE522A990778009F1BE8 /*
WalletEmptyView.swift in Sources */,
- 4E3EAE532A990778009F1BE8 /*
CurrencyFormatter.swift in Sources */,
+ 4E3EAE532A990778009F1BE8 /*
CurrencySpecification.swift in Sources */,
4E3EAE542A990778009F1BE8 /* TalerDater.swift in
Sources */,
4E3EAE552A990778009F1BE8 /*
Model+Balances.swift in Sources */,
4E3EAE562A990778009F1BE8 /*
LocalizedAlertError.swift in Sources */,
@@ -1142,6 +1147,7 @@
4E605DB82AB05E48002FB9A7 /*
View+flippedDirection.swift in Sources */,
4E983C2D2ADC416800FA9CC5 /*
View+needVStack.swift in Sources */,
4E9320432A14F6EA00A87B0E /* WalletColors.swift
in Sources */,
+ 4EC4008A2AE3E7E800DF72C7 /* AboutView.swift in
Sources */,
4EB0955D2989CBFE0043A8A1 /*
BalancesListView.swift in Sources */,
4EB095212989CBCB0043A8A1 /*
WalletBackendError.swift in Sources */,
4EB0955E2989CBFE0043A8A1 /*
PendingRowView.swift in Sources */,
@@ -1189,7 +1195,7 @@
4EB095592989CBFE0043A8A1 /*
Model+Transactions.swift in Sources */,
4E578E922A481D8600F21F1C /*
Controller+playSound.swift in Sources */,
4EB0955F2989CBFE0043A8A1 /*
WalletEmptyView.swift in Sources */,
- 4E16E12329F3BB99008B9C86 /*
CurrencyFormatter.swift in Sources */,
+ 4E16E12329F3BB99008B9C86 /*
CurrencySpecification.swift in Sources */,
4EB095092989CB7C0043A8A1 /* TalerDater.swift in
Sources */,
4E3B4BC52A428AF700CC88B8 /*
Model+Balances.swift in Sources */,
4E363CC22A2621C200D7E98C /*
LocalizedAlertError.swift in Sources */,
diff --git a/TalerWallet1/Backend/WalletBackendRequest.swift
b/TalerWallet1/Backend/WalletBackendRequest.swift
index 3f4cedc..293269c 100644
--- a/TalerWallet1/Backend/WalletBackendRequest.swift
+++ b/TalerWallet1/Backend/WalletBackendRequest.swift
@@ -111,12 +111,15 @@ struct OrderShortInfo: Codable {
// MARK: -
/// A request to process a refund.
struct WalletBackendApplyRefundRequest: WalletBackendFormattedRequest {
+ func operation() -> String { "applyRefund" }
+ func args() -> Args { Args(talerRefundUri: talerRefundUri) }
+
var talerRefundUri: String
-
+
struct Args: Encodable {
var talerRefundUri: String
}
-
+
struct Response: Decodable {
var contractTermsHash: String
var amountEffectivePaid: Amount
@@ -125,38 +128,27 @@ struct WalletBackendApplyRefundRequest:
WalletBackendFormattedRequest {
var pendingAtExchange: Bool
var info: OrderShortInfo
}
-
- func operation() -> String {
- return "applyRefund"
- }
-
- func args() -> Args {
- return Args(talerRefundUri: talerRefundUri)
- }
}
/// A request to force update an exchange.
struct WalletBackendForceUpdateRequest: WalletBackendFormattedRequest {
+ func operation() -> String { "addRequest" }
+ func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl) }
+
var exchangeBaseUrl: String
-
struct Args: Encodable {
var exchangeBaseUrl: String
}
-
+
struct Response: Decodable {}
-
- func operation() -> String {
- return "addRequest"
- }
-
- func args() -> Args {
- return Args(exchangeBaseUrl: exchangeBaseUrl)
- }
}
/// A request to deposit funds.
struct WalletBackendCreateDepositGroupRequest: WalletBackendFormattedRequest {
+ func operation() -> String { "createDepositGroup" }
+ func args() -> Args { Args(depositPayToUri: depositePayToUri, amount:
amount) }
+
var depositePayToUri: String
var amount: Amount
@@ -168,52 +160,32 @@ struct WalletBackendCreateDepositGroupRequest:
WalletBackendFormattedRequest {
struct Response: Decodable {
var depositGroupId: String
}
-
- func operation() -> String {
- return "createDepositGroup"
- }
-
- func args() -> Args {
- return Args(depositPayToUri: depositePayToUri, amount: amount)
- }
}
/// A request to get information about a payment request.
struct WalletBackendPreparePayRequest: WalletBackendFormattedRequest {
+ func operation() -> String { "preparePay" }
+ func args() -> Args { Args(talerPayUri: talerPayUri) }
var talerPayUri: String
-
+
struct Args: Encodable {
var talerPayUri: String
}
-
+
struct Response: Decodable {}
-
- func operation() -> String {
- return "preparePay"
- }
-
- func args() -> Args {
- return Args(talerPayUri: talerPayUri)
- }
}
/// A request to confirm a payment.
struct WalletBackendConfirmPayRequest: WalletBackendFormattedRequest {
+ func operation() -> String { "confirmPay" }
+ func args() -> Args { Args(proposalId: proposalId) }
var proposalId: String
-
+
struct Args: Encodable {
var proposalId: String
}
-
+
struct Response: Decodable {}
-
- func operation() -> String {
- return "confirmPay"
- }
-
- func args() -> Args {
- return Args(proposalId: proposalId)
- }
}
// MARK: -
@@ -229,8 +201,8 @@ struct PrepareRewardResponse: Decodable {
/// A request to prepare a reward.
struct PrepareRewardRequest: WalletBackendFormattedRequest {
typealias Response = PrepareRewardResponse
- func operation() -> String { return "prepareReward" }
- func args() -> Args { return Args(talerRewardUri: talerRewardUri) }
+ func operation() -> String { "prepareReward" }
+ func args() -> Args { Args(talerRewardUri: talerRewardUri) }
var talerRewardUri: String
struct Args: Encodable {
@@ -241,8 +213,8 @@ struct PrepareRewardRequest: WalletBackendFormattedRequest {
/// A request to accept a reward.
struct AcceptRewardRequest: WalletBackendFormattedRequest {
struct Response: Decodable {}
- func operation() -> String { return "acceptReward" }
- func args() -> Args { return Args(walletRewardId: walletRewardId) }
+ func operation() -> String { "acceptReward" }
+ func args() -> Args { Args(walletRewardId: walletRewardId) }
var walletRewardId: String
struct Args: Encodable {
@@ -252,21 +224,16 @@ struct AcceptRewardRequest: WalletBackendFormattedRequest
{
// MARK: -
/// A request to abort a failed payment.
struct WalletBackendAbortFailedPaymentRequest: WalletBackendFormattedRequest {
+ func operation() -> String { "abortFailedPayWithRefund" }
+ func args() -> Args { Args(proposalId: proposalId) }
+
var proposalId: String
-
struct Args: Encodable {
var proposalId: String
}
struct Response: Decodable {}
- func operation() -> String {
- return "abortFailedPayWithRefund"
- }
-
- func args() -> Args {
- return Args(proposalId: proposalId)
- }
}
// MARK: -
struct IntegrationTestArgs: Codable {
@@ -280,19 +247,13 @@ struct IntegrationTestArgs: Codable {
/// A request to run a basic integration test.
struct WalletBackendRunIntegrationTestRequest: WalletBackendFormattedRequest {
+ func operation() -> String { "runIntegrationTest" }
+ func args() -> Args { integrationTestArgs }
var integrationTestArgs: IntegrationTestArgs
typealias Args = IntegrationTestArgs
struct Response: Decodable {}
-
- func operation() -> String {
- return "runIntegrationTest"
- }
-
- func args() -> Args {
- return integrationTestArgs
- }
}
struct TestPayArgs: Codable {
@@ -304,19 +265,13 @@ struct TestPayArgs: Codable {
/// A request to make a test payment.
struct WalletBackendTestPayRequest: WalletBackendFormattedRequest {
+ func operation() -> String { "testPay" }
+ func args() -> Args { testPayArgs }
+
var testPayArgs: TestPayArgs
-
typealias Args = TestPayArgs
struct Response: Decodable {}
-
- func operation() -> String {
- return "testPay"
- }
-
- func args() -> Args {
- return testPayArgs
- }
}
struct Coin: Codable {
@@ -333,25 +288,22 @@ struct Coin: Codable {
/// A request to dump all coins to JSON.
struct WalletBackendDumpCoinsRequest: WalletBackendFormattedRequest {
- struct Args: Encodable {
-
- }
-
+ func operation() -> String { "dumpCoins" }
+ func args() -> Args { Args() }
+ struct Args: Encodable { }
+
struct Response: Decodable {
var coins: [Coin]
}
- func operation() -> String {
- return "dumpCoins"
- }
-
- func args() -> Args {
- return Args()
- }
}
/// A request to suspend or unsuspend a coin.
struct WalletBackendSuspendCoinRequest: WalletBackendFormattedRequest {
+ struct Response: Decodable {}
+ func operation() -> String { "setCoinSuspended" }
+ func args() -> Args { Args(coinPub: coinPub, suspended: suspended) }
+
var coinPub: String
var suspended: Bool
@@ -359,16 +311,6 @@ struct WalletBackendSuspendCoinRequest:
WalletBackendFormattedRequest {
var coinPub: String
var suspended: Bool
}
-
- struct Response: Decodable {}
-
- func operation() -> String {
- return "setCoinSuspended"
- }
-
- func args() -> Args {
- return Args(coinPub: coinPub, suspended: suspended)
- }
}
diff --git a/TalerWallet1/Controllers/Controller.swift
b/TalerWallet1/Controllers/Controller.swift
index f408084..33169ee 100644
--- a/TalerWallet1/Controllers/Controller.swift
+++ b/TalerWallet1/Controllers/Controller.swift
@@ -33,12 +33,14 @@ class Controller: ObservableObject {
private let symLog = SymLogC()
@Published var backendState: BackendState = .none // only used for
launch animation
+ @Published var currencyInfos: [CurrencyInfo]
@AppStorage("useHaptics") var useHaptics: Bool = false // extension
mustn't define this, so it must be here
@AppStorage("playSounds") var playSounds: Int = 0 // extension
mustn't define this, so it must be here
@AppStorage("talerFont") var talerFont: Int = 0 // extension
mustn't define this, so it must be here
let hapticCapability = CHHapticEngine.capabilitiesForHardware()
let logger = Logger (subsystem: "net.taler.gnu", category: "Controller")
let player = AVQueuePlayer()
+ let semaphore = AsyncSemaphore(value: 1)
var messageForSheet: String? = nil
@@ -50,6 +52,34 @@ class Controller: ObservableObject {
// }
// }
backendState = .instantiated
+ currencyInfos = []
+ }
+
+ func info(for currency: String) -> CurrencyInfo? {
+ for currencyInfo in currencyInfos {
+ if currencyInfo.scope.currency == currency {
+ return currencyInfo
+ }
+ }
+ return nil
+ }
+
+ @MainActor
+ func setInfo(_ info: CurrencyInfo) async {
+ await semaphore.wait()
+ defer { semaphore.signal() }
+ var existing: ScopeInfo? = nil
+ for currencyInfo in currencyInfos {
+ let scope = currencyInfo.scope
+ if scope.currency == info.scope.currency {
+ existing = scope
+ break
+ }
+ }
+ if existing != nil {
+ currencyInfos.removeAll(where: { $0.scope == existing })
+ }
+ currencyInfos.append(info)
}
@MainActor
diff --git a/TalerWallet1/Controllers/DebugViewC.swift
b/TalerWallet1/Controllers/DebugViewC.swift
index 78ef45b..990f53a 100644
--- a/TalerWallet1/Controllers/DebugViewC.swift
+++ b/TalerWallet1/Controllers/DebugViewC.swift
@@ -27,9 +27,10 @@ import os.log
// MARK: - Main View
public let VIEW_EMPTY = 10 // 10
WalletEmptyView
public let VIEW_BALANCES = VIEW_EMPTY + 1 // 11
BalancesListView
-public let VIEW_SETTINGS = VIEW_BALANCES + 1 // 12
SettingsView
-public let VIEW_PENDING = VIEW_SETTINGS + 1 // 13
PendingOpsListView
-public let VIEW_EXCHANGES = VIEW_PENDING + 1 // 14
ExchangeListView
+public let VIEW_EXCHANGES = VIEW_BALANCES + 1 // 12
ExchangeListView
+public let VIEW_SETTINGS = VIEW_EXCHANGES + 1 // 13
SettingsView
+public let VIEW_ABOUT = VIEW_SETTINGS + 1 // 14
AboutView
+public let VIEW_PENDING = VIEW_ABOUT + 1 // 15
PendingOpsListView
// MARK: Transactions
public let VIEW_TRANSACTIONLIST = VIEW_EMPTY + 10 // 20
TransactionsListView
diff --git a/TalerWallet1/Helper/CurrencyFormatter.swift
b/TalerWallet1/Helper/CurrencyFormatter.swift
deleted file mode 100644
index 2a56f33..0000000
--- a/TalerWallet1/Helper/CurrencyFormatter.swift
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
- * See LICENSE.md
- */
-import Foundation
-import taler_swift
-
-public class CurrencyFormatter: NumberFormatter {
- public static let shared = CurrencyFormatter()
-
- public override init() {
- super.init()
- self.locale = Locale.current
- self.numberStyle = .currency // currencyISOCode,
currencyPlural, (currencyAccounting)
- self.numberStyle = .currencyISOCode
-// self.numberStyle = .spellOut
-
-// self.currencyCode = code // EUR, USD, JPY, GBP
-// self.minimumFractionDigits = fractionDigits
-// self.maximumFractionDigits = fractionDigits
-// self.groupingSize = 3 // thousands
-// self.groupingSeparator = ","
-// self.usesGroupingSeparator = true
-// self.decimalSeparator = "."
- }
-
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-}
diff --git a/TalerWallet1/Helper/CurrencySpecification.swift
b/TalerWallet1/Helper/CurrencySpecification.swift
new file mode 100644
index 0000000..6b8a059
--- /dev/null
+++ b/TalerWallet1/Helper/CurrencySpecification.swift
@@ -0,0 +1,158 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import taler_swift
+
+public struct CurrencyInfo {
+ let scope: ScopeInfo
+ let specs: CurrencySpecification
+ let formatter: CurrencyFormatter
+
+ func string(for value: Double, useSymbol: Bool = true) -> String {
+ formatter.setUseSymbol(useSymbol)
+ if let valueStr = formatter.string(for: value) {
+ let decimalSeparator = formatter.decimalSeparator ??
specs.decimalSeparator
+ if let decimalIndex = valueStr.endIndex(of: decimalSeparator) {
+ var fraction = 1
+ var integerStr = String(valueStr[..<decimalIndex])
+ let fractionStr = valueStr[decimalIndex...]
+ for character in fractionStr {
+ let charStr = String(character)
+ if let digit = Int(charStr) {
+ let digitStr = fraction > specs.fractionalNormalDigits
?
+ SuperScriptDigits(charStr)
: charStr
+ integerStr += digitStr
+ } else { integerStr += charStr }
+ fraction += 1
+ }
+ return integerStr
+ }
+ return valueStr
+ } else { // formatter doesn't work - we need to format ourselves
+ var madeUpStr = ""
+ var currencyName = scope.currency
+ var hasSymbol = false
+ if useSymbol && formatter.hasAltUnitName0 {
+ if let symbol = specs.altUnitNames?[0] {
+ currencyName = symbol
+ hasSymbol = true
+ }
+ }
+ if specs.isCurrencyNameLeading {
+ madeUpStr = currencyName
+ if !hasSymbol {
+ madeUpStr += " "
+ }
+ }
+ let integerPart = Int(value)
+ madeUpStr += String(integerPart)
+ madeUpStr += specs.decimalSeparator
+ if !specs.isCurrencyNameLeading {
+ if !hasSymbol {
+ madeUpStr += " "
+ }
+ madeUpStr += currencyName
+ }
+ return madeUpStr
+ } // DIY
+ }
+}
+
+public struct CurrencySpecification2: Codable, Sendable {
+ let currencySpecification: CurrencySpecification
+}
+
+public struct CurrencySpecification: Codable, Sendable {
+ enum CodingKeys: String, CodingKey {
+ case name = "name"
+ case decimalSeparator = "decimal_separator"
+ case groupSeparator = "group_separator"
+ 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"
+ }
+ /// some name for this CurrencySpecification
+ let name: String
+ /// e.g. “.” for $, and “,” for €
+ let decimalSeparator: String
+ /// e.g. “,” for $, and “.” or “ ” for € (France uses a narrow space
character)
+ let groupSeparator: 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]?
+}
+
+
+public class CurrencyFormatter: NumberFormatter {
+
+ var hasAltUnitName0: Bool // specs.altUnitNames[0] should have the
Symbol ($,€,¥)
+ /// factory
+ static func formatter(scope: ScopeInfo, specs: CurrencySpecification) ->
CurrencyFormatter {
+ let formatter = CurrencyFormatter()
+ formatter.setCode(to: scope.currency)
+ formatter.setMinimumFractionDigits(specs.fractionalTrailingZeroDigits)
+ if let symbol = specs.altUnitNames?[0] {
+ formatter.setSymbol(to: symbol)
+ formatter.hasAltUnitName0 = true
+ }
+ return formatter
+ }
+
+ public override init() {
+ self.hasAltUnitName0 = false
+ super.init()
+ self.locale = Locale.current
+ self.usesGroupingSeparator = true
+ self.numberStyle = .currencyISOCode // .currency
+ self.maximumFractionDigits = 8 // ensure that formatter
will not round
+
+// self.currencyCode = code // EUR, USD, JPY, GBP
+// self.currencySymbol = symbol
+// self.internationalCurrencySymbol =
+// self.minimumFractionDigits = fractionDigits
+// self.maximumFractionDigits = fractionDigits
+// self.groupingSize = 3 // thousands
+// self.groupingSeparator = ","
+// self.decimalSeparator = "."
+ }
+
+ func setUseSymbol(_ useSymbol: Bool) {
+ numberStyle = useSymbol ? .currency : .currencyISOCode
+ }
+
+ func setCode(to code:String) {
+ currencyCode = code
+ }
+
+ func setSymbol(to symbol:String) {
+ currencySymbol = symbol
+ }
+
+ func setMinimumFractionDigits(_ digits: Int) {
+ minimumFractionDigits = digits
+ }
+
+ func setLocale(to newLocale: String) {
+ locale = Locale(identifier: newLocale)
+ maximumFractionDigits = 8 // ensure that formatter will
not round
+// NumberFormatter.RoundingMode
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
diff --git a/TalerWallet1/Helper/TalerStrings.swift
b/TalerWallet1/Helper/TalerStrings.swift
index 6c39f67..70b7330 100644
--- a/TalerWallet1/Helper/TalerStrings.swift
+++ b/TalerWallet1/Helper/TalerStrings.swift
@@ -14,6 +14,28 @@ extension StringProtocol {
}
return String(self)
}
+
+ func index<S: StringProtocol>(of string: S, options: String.CompareOptions
= []) -> Index? {
+ range(of: string, options: options)?.lowerBound
+ }
+ func endIndex<S: StringProtocol>(of string: S, options:
String.CompareOptions = []) -> Index? {
+ range(of: string, options: options)?.upperBound
+ }
+ func indices<S: StringProtocol>(of string: S, options:
String.CompareOptions = []) -> [Index] {
+ ranges(of: string, options: options).map(\.lowerBound)
+ }
+ func ranges<S: StringProtocol>(of string: S, options:
String.CompareOptions = []) -> [Range<Index>] {
+ var result: [Range<Index>] = []
+ var startIndex = self.startIndex
+ while startIndex < endIndex,
+ let range = self[startIndex...]
+ .range(of: string, options: options) {
+ result.append(range)
+ startIndex = range.lowerBound < range.upperBound ?
range.upperBound :
+ index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ??
endIndex
+ }
+ return result
+ }
}
extension String {
diff --git a/TalerWallet1/Model/Model+Balances.swift
b/TalerWallet1/Model/Model+Balances.swift
index c5fa095..1bab4ca 100644
--- a/TalerWallet1/Model/Model+Balances.swift
+++ b/TalerWallet1/Model/Model+Balances.swift
@@ -31,8 +31,8 @@ struct Balance: Decodable, Hashable, Sendable {
// MARK: -
/// A request to get the balances held in the wallet.
fileprivate struct Balances: WalletBackendFormattedRequest {
- func operation() -> String { return "getBalances" }
- func args() -> Args { return Args() }
+ func operation() -> String { "getBalances" }
+ func args() -> Args { Args() }
struct Args: Encodable {} // no arguments needed
diff --git a/TalerWallet1/Model/Model+Exchange.swift
b/TalerWallet1/Model/Model+Exchange.swift
index 7ac8d72..524f462 100644
--- a/TalerWallet1/Model/Model+Exchange.swift
+++ b/TalerWallet1/Model/Model+Exchange.swift
@@ -7,6 +7,10 @@ import taler_swift
import SymLog
fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for
debugging
+struct ExchangeError: Codable, Hashable {
+ var error: HTTPError
+}
+
enum ExchangeEntryStatus: String, Codable {
case preset
case ephemeral
@@ -43,9 +47,7 @@ struct Exchange: Codable, Hashable, Identifiable {
var ageRestrictionOptions: [Int]
var lastUpdateErrorInfo: ExchangeError?
- var id: String {
- exchangeBaseUrl
- }
+ var id: String { exchangeBaseUrl }
var name: String? {
if let url = URL(string: exchangeBaseUrl) {
if let host = url.host {
@@ -56,24 +58,48 @@ struct Exchange: Codable, Hashable, Identifiable {
}
}
-struct ExchangeError: Codable, Hashable {
- var error: HTTPError
+struct ExchangeListItem: Codable, Hashable {
+ var exchangeBaseUrl: String
+ var currency: String
+ var paytoUris: [String]
+
+ public static func == (lhs: ExchangeListItem, rhs: ExchangeListItem) ->
Bool {
+ return lhs.exchangeBaseUrl == rhs.exchangeBaseUrl &&
+ lhs.currency == rhs.currency &&
+ lhs.paytoUris == rhs.paytoUris
+ }
+
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(exchangeBaseUrl)
+ hasher.combine(currency)
+ hasher.combine(paytoUris)
+ }
}
-struct HTTPError: Codable, Hashable {
- var code: Int
- var requestUrl: String?
- var hint: String
- var requestMethod: String?
- var httpStatusCode: Int?
- var when: Timestamp?
- var stack: String?
+struct ExchangeUrlItem: Codable, Hashable, Identifiable {
+ var exchangeBaseUrl: String
+ var id: String { exchangeBaseUrl }
}
// MARK: -
+/// A request to list exchanges names for a currency
+fileprivate struct ListExchangesForScopedCurrency:
WalletBackendFormattedRequest {
+ func operation() -> String { "listExchangesForScopedCurrency" }
+ func args() -> Args { Args(scope: scope) }
+
+ var scope: ScopeInfo
+
+ struct Args: Encodable {
+ var scope: ScopeInfo
+ }
+ struct Response: Decodable, Sendable {
+ var exchanges: [ExchangeUrlItem] // list of Exchange names
+ }
+}
+
/// A request to list exchanges.
fileprivate struct ListExchanges: WalletBackendFormattedRequest {
- func operation() -> String { return "listExchanges" }
- func args() -> Args { return Args() }
+ func operation() -> String { "listExchanges" }
+ func args() -> Args { Args() }
struct Args: Encodable {} // no arguments needed
@@ -85,8 +111,8 @@ 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) }
+ func operation() -> String { "updateExchangeEntry" }
+ func args() -> Args { Args(scopeInfo: scopeInfo) }
var scopeInfo: ScopeInfo
@@ -98,8 +124,8 @@ fileprivate struct UpdateExchange:
WalletBackendFormattedRequest {
/// 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" } // addExchangeEntry
- func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl) }
+ func operation() -> String { "addExchange" } // addExchangeEntry
+ func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl) }
var exchangeBaseUrl: String
@@ -110,9 +136,9 @@ fileprivate struct AddExchange:
WalletBackendFormattedRequest {
/// A request to get info about a currency
fileprivate struct GetCurrencySpecification: WalletBackendFormattedRequest {
- typealias Response = CurrencySpecification
- func operation() -> String { return "getCurrencySpecification" }
- func args() -> Args { return Args(scope: scope) }
+ typealias Response = CurrencySpecification2
+ func operation() -> String { "getCurrencySpecification" }
+ func args() -> Args { Args(scope: scope) }
var scope: ScopeInfo
@@ -122,6 +148,18 @@ fileprivate struct GetCurrencySpecification:
WalletBackendFormattedRequest {
}
// MARK: -
extension WalletModel {
+ /// ask wallet-core for its list of known exchanges
+ @MainActor func listExchangesForScopedCurrencyM(scope: ScopeInfo)
+ async -> [ExchangeUrlItem] { // M for MainActor
+ do {
+ let request = ListExchangesForScopedCurrency(scope: scope)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response.exchanges
+ } catch {
+ return [] // empty, but not nil
+ }
+ }
+
/// ask wallet-core for its list of known exchanges
@MainActor func listExchangesM()
async -> [Exchange] { // M for MainActor
@@ -150,11 +188,13 @@ extension WalletModel {
_ = try await sendRequest(request)
}
- @MainActor func getCurrencySpecificationM(scope: ScopeInfo)
- async throws -> CurrencySpecification { // M for MainActor
+ func getCurrencyInfo(scope: ScopeInfo)
+ async throws -> CurrencyInfo {
let request = GetCurrencySpecification(scope: scope)
let response = try await sendRequest(request, ASYNCDELAY)
- return response
+ return CurrencyInfo(scope: scope, specs:
response.currencySpecification,
+ formatter: CurrencyFormatter.formatter(scope: scope,
+ specs:
response.currencySpecification))
}
}
diff --git a/TalerWallet1/Model/Model+P2P.swift
b/TalerWallet1/Model/Model+P2P.swift
index 3cafc8a..82e5bc4 100644
--- a/TalerWallet1/Model/Model+P2P.swift
+++ b/TalerWallet1/Model/Model+P2P.swift
@@ -22,8 +22,8 @@ struct AmountResponse: Codable {
}
fileprivate struct GetMaxPeerPushAmount: WalletBackendFormattedRequest {
typealias Response = AmountResponse
- func operation() -> String { return "GetMaxPeerPushAmount" }
- func args() -> Args { return Args(currency: currency) }
+ func operation() -> String { "GetMaxPeerPushAmount" }
+ func args() -> Args { Args(currency: currency) }
var currency: String
struct Args: Encodable {
@@ -48,8 +48,8 @@ struct CheckPeerPushDebitResponse: Codable {
}
fileprivate struct CheckPeerPushDebit: WalletBackendFormattedRequest {
typealias Response = CheckPeerPushDebitResponse
- func operation() -> String { return "checkPeerPushDebit" }
- func args() -> Args { return Args(amount: amount) }
+ func operation() -> String { "checkPeerPushDebit" }
+ func args() -> Args { Args(amount: amount) }
var amount: Amount
struct Args: Encodable {
@@ -77,9 +77,9 @@ struct InitiatePeerPushDebitResponse: Codable {
}
fileprivate struct InitiatePeerPushDebit: WalletBackendFormattedRequest {
typealias Response = InitiatePeerPushDebitResponse
- func operation() -> String { return "initiatePeerPushDebit" }
- func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl,
- partialContractTerms:
partialContractTerms) }
+ func operation() -> String { "initiatePeerPushDebit" }
+ func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl,
+ partialContractTerms: partialContractTerms) }
var exchangeBaseUrl: String?
var partialContractTerms: PeerContractTerms
@@ -109,8 +109,8 @@ struct CheckPeerPullCreditResponse: Codable {
}
fileprivate struct CheckPeerPullCredit: WalletBackendFormattedRequest {
typealias Response = CheckPeerPullCreditResponse
- func operation() -> String { return "checkPeerPullCredit" }
- func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl,
scopeInfo: scopeInfo, amount: amount) }
+ func operation() -> String { "checkPeerPullCredit" }
+ func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl, scopeInfo:
scopeInfo, amount: amount) }
var exchangeBaseUrl: String?
var scopeInfo: ScopeInfo?
@@ -138,8 +138,8 @@ struct InitiatePeerPullCreditResponse: Codable {
}
fileprivate struct InitiatePeerPullCredit: WalletBackendFormattedRequest {
typealias Response = InitiatePeerPullCreditResponse
- func operation() -> String { return "initiatePeerPullCredit" }
- func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl,
+ func operation() -> String { "initiatePeerPullCredit" }
+ func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl,
partialContractTerms: partialContractTerms) }
var exchangeBaseUrl: String?
@@ -170,8 +170,8 @@ struct PreparePeerPushCreditResponse: Codable {
}
fileprivate struct PreparePeerPushCredit: WalletBackendFormattedRequest {
typealias Response = PreparePeerPushCreditResponse
- func operation() -> String { return "preparePeerPushCredit" }
- func args() -> Args { return Args(talerUri: talerUri) }
+ func operation() -> String { "preparePeerPushCredit" }
+ func args() -> Args { Args(talerUri: talerUri) }
var talerUri: String
struct Args: Encodable {
@@ -191,8 +191,8 @@ extension WalletModel {
/// Accept an incoming peer push payment
fileprivate struct AcceptPeerPushCredit: WalletBackendFormattedRequest {
struct Response: Decodable {} // no result - getting no error back means
success
- func operation() -> String { return "confirmPeerPushCredit" } // should be
"acceptPeerPushCredit"
- func args() -> Args { return Args(transactionId: transactionId) }
+ func operation() -> String { "confirmPeerPushCredit" } // should be
"acceptPeerPushCredit"
+ func args() -> Args { Args(transactionId: transactionId) }
var transactionId: String
struct Args: Encodable {
@@ -219,8 +219,8 @@ struct PreparePeerPullDebitResponse: Codable {
}
fileprivate struct PreparePeerPullDebit: WalletBackendFormattedRequest {
typealias Response = PreparePeerPullDebitResponse
- func operation() -> String { return "preparePeerPullDebit" }
- func args() -> Args { return Args(talerUri: talerUri) }
+ func operation() -> String { "preparePeerPullDebit" }
+ func args() -> Args { Args(talerUri: talerUri) }
var talerUri: String
struct Args: Encodable {
@@ -240,8 +240,8 @@ extension WalletModel {
/// Confirm incoming peer push request(invoice) and pay
fileprivate struct ConfirmPeerPullDebit: WalletBackendFormattedRequest {
struct Response: Decodable {} // no result - getting no error back means
success
- func operation() -> String { return "confirmPeerPullDebit" }
- func args() -> Args { return Args(transactionId: transactionId) }
+ func operation() -> String { "confirmPeerPullDebit" }
+ func args() -> Args { Args(transactionId: transactionId) }
var transactionId: String
struct Args: Encodable {
diff --git a/TalerWallet1/Model/Model+Payment.swift
b/TalerWallet1/Model/Model+Payment.swift
index b183d36..b211c51 100644
--- a/TalerWallet1/Model/Model+Payment.swift
+++ b/TalerWallet1/Model/Model+Payment.swift
@@ -139,8 +139,8 @@ struct PreparePayResult: Codable {
/// A request to get an exchange's payment contract terms.
fileprivate struct PreparePayForUri: WalletBackendFormattedRequest {
typealias Response = PreparePayResult
- func operation() -> String { return "preparePayForUri" }
- func args() -> Args { return Args(talerPayUri: talerPayUri) }
+ func operation() -> String { "preparePayForUri" }
+ func args() -> Args { Args(talerPayUri: talerPayUri) }
var talerPayUri: String
struct Args: Encodable {
@@ -150,8 +150,8 @@ fileprivate struct PreparePayForUri:
WalletBackendFormattedRequest {
/// A request to get an exchange's payment contract terms.
fileprivate struct PreparePayForTemplate: WalletBackendFormattedRequest {
typealias Response = PreparePayResult
- func operation() -> String { return "preparePayForTemplate" }
- func args() -> Args { return Args(talerPayTemplateUri:
talerPayTemplateUri) }
+ func operation() -> String { "preparePayForTemplate" }
+ func args() -> Args { Args(talerPayTemplateUri: talerPayTemplateUri) }
var talerPayTemplateUri: String
struct Args: Encodable {
@@ -168,8 +168,8 @@ struct ConfirmPayResult: Decodable {
/// A request to get an exchange's payment details.
fileprivate struct confirmPayForUri: WalletBackendFormattedRequest {
typealias Response = ConfirmPayResult
- func operation() -> String { return "confirmPay" }
- func args() -> Args { return Args(transactionId: transactionId) }
+ func operation() -> String { "confirmPay" }
+ func args() -> Args { Args(transactionId: transactionId) }
var transactionId: String
struct Args: Encodable {
diff --git a/TalerWallet1/Model/Model+Pending.swift
b/TalerWallet1/Model/Model+Pending.swift
index eb9f447..e2dec9f 100644
--- a/TalerWallet1/Model/Model+Pending.swift
+++ b/TalerWallet1/Model/Model+Pending.swift
@@ -11,7 +11,7 @@ fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9
seconds for debugging
// MARK: -
/// A request to list the backend's currently pending operations.
fileprivate struct GetPendingOperations: WalletBackendFormattedRequest {
- func operation() -> String { return "getPendingOperations" }
+ func operation() -> String { "getPendingOperations" }
func args() -> Args { Args() }
struct Args: Encodable {}
diff --git a/TalerWallet1/Model/Model+Settings.swift
b/TalerWallet1/Model/Model+Settings.swift
index 5dff9f8..288e22b 100644
--- a/TalerWallet1/Model/Model+Settings.swift
+++ b/TalerWallet1/Model/Model+Settings.swift
@@ -13,13 +13,12 @@ fileprivate let MERCHANTAUTHTOKEN = "secret-token:sandbox"
/// A request to add a test balance to the wallet.
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,
- corebankApiBaseUrl: corebankApiBaseUrl,
- exchangeBaseUrl: exchangeBaseUrl)
- }
+ func operation() -> String { "withdrawTestBalance" }
+ func args() -> Args { Args(amount: amount,
+// bankBaseUrl: bankBaseUrl,
+ corebankApiBaseUrl: corebankApiBaseUrl,
+ exchangeBaseUrl: exchangeBaseUrl)
+ }
var amount: Amount
// var bankBaseUrl: String
@@ -48,17 +47,15 @@ extension WalletModel {
/// A request to add a test balance to the wallet.
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(exchangeBaseUrl: exchangeBaseUrl,
-// bankBaseUrl: bankBaseUrl,
- corebankApiBaseUrl: corebankApiBaseUrl,
- merchantBaseUrl: merchantBaseUrl,
- merchantAuthToken: merchantAuthToken,
- amountToWithdraw: amountToWithdraw,
- amountToSpend: amountToSpend
- )
- }
+ func operation() -> String { newVersion ? "runIntegrationTestV2" :
"runIntegrationTest" }
+ func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl,
+// bankBaseUrl: bankBaseUrl,
+ corebankApiBaseUrl: corebankApiBaseUrl,
+ merchantBaseUrl: merchantBaseUrl,
+ merchantAuthToken: merchantAuthToken,
+ amountToWithdraw: amountToWithdraw,
+ amountToSpend: amountToSpend)
+ }
let newVersion: Bool
diff --git a/TalerWallet1/Model/Model+Transactions.swift
b/TalerWallet1/Model/Model+Transactions.swift
index 5865ea3..2e59bfb 100644
--- a/TalerWallet1/Model/Model+Transactions.swift
+++ b/TalerWallet1/Model/Model+Transactions.swift
@@ -35,9 +35,9 @@ extension WalletModel {
// MARK: -
/// A request to get the transactions in the wallet's history.
fileprivate struct GetTransactions: WalletBackendFormattedRequest {
- func operation() -> String { return "getTransactions" }
-// func operation() -> String { return "testingGetSampleTransactions" }
- func args() -> Args { return Args(currency: currency, search: search,
sort: sort) }
+ func operation() -> String { "getTransactions" }
+// func operation() -> String { "testingGetSampleTransactions" }
+ func args() -> Args { Args(currency: currency, search: search, sort: sort)
}
var currency: String?
var search: String?
@@ -55,8 +55,8 @@ fileprivate struct GetTransactions:
WalletBackendFormattedRequest {
/// A request to abort a wallet transaction by ID.
struct AbortTransaction: WalletBackendFormattedRequest {
struct Response: Decodable {} // no result - getting no error back means
success
- func operation() -> String { return "abortTransaction" }
- func args() -> Args { return Args(transactionId: transactionId) }
+ func operation() -> String { "abortTransaction" }
+ func args() -> Args { Args(transactionId: transactionId) }
var transactionId: String
struct Args: Encodable {
@@ -66,8 +66,8 @@ struct AbortTransaction: WalletBackendFormattedRequest {
/// A request to delete a wallet transaction by ID.
struct DeleteTransaction: WalletBackendFormattedRequest {
struct Response: Decodable {} // no result - getting no error back means
success
- func operation() -> String { return "deleteTransaction" }
- func args() -> Args { return Args(transactionId: transactionId) }
+ func operation() -> String { "deleteTransaction" }
+ func args() -> Args { Args(transactionId: transactionId) }
var transactionId: String
struct Args: Encodable {
@@ -77,8 +77,8 @@ struct DeleteTransaction: WalletBackendFormattedRequest {
/// A request to delete a wallet transaction by ID.
struct FailTransaction: WalletBackendFormattedRequest {
struct Response: Decodable {} // no result - getting no error back means
success
- func operation() -> String { return "failTransaction" }
- func args() -> Args { return Args(transactionId: transactionId) }
+ func operation() -> String { "failTransaction" }
+ func args() -> Args { Args(transactionId: transactionId) }
var transactionId: String
struct Args: Encodable {
@@ -88,8 +88,8 @@ struct FailTransaction: WalletBackendFormattedRequest {
/// A request to suspend a wallet transaction by ID.
struct SuspendTransaction: WalletBackendFormattedRequest {
struct Response: Decodable {} // no result - getting no error back means
success
- func operation() -> String { return "suspendTransaction" }
- func args() -> Args { return Args(transactionId: transactionId) }
+ func operation() -> String { "suspendTransaction" }
+ func args() -> Args { Args(transactionId: transactionId) }
var transactionId: String
struct Args: Encodable {
@@ -99,8 +99,8 @@ struct SuspendTransaction: WalletBackendFormattedRequest {
/// A request to suspend a wallet transaction by ID.
struct ResumeTransaction: WalletBackendFormattedRequest {
struct Response: Decodable {} // no result - getting no error back means
success
- func operation() -> String { return "resumeTransaction" }
- func args() -> Args { return Args(transactionId: transactionId) }
+ func operation() -> String { "resumeTransaction" }
+ func args() -> Args { Args(transactionId: transactionId) }
var transactionId: String
struct Args: Encodable {
diff --git a/TalerWallet1/Model/Model+Withdraw.swift
b/TalerWallet1/Model/Model+Withdraw.swift
index f0084eb..5b5fe8f 100644
--- a/TalerWallet1/Model/Model+Withdraw.swift
+++ b/TalerWallet1/Model/Model+Withdraw.swift
@@ -14,28 +14,11 @@ struct WithdrawUriInfoResponse: Decodable {
var defaultExchangeBaseUrl: String? // TODO: might be nil
❗️Yikes
var possibleExchanges: [ExchangeListItem] // TODO: query these for
fees?
}
-struct ExchangeListItem: Codable, Hashable {
- var exchangeBaseUrl: String
- var currency: String
- var paytoUris: [String]
-
- public static func == (lhs: ExchangeListItem, rhs: ExchangeListItem) ->
Bool {
- return lhs.exchangeBaseUrl == rhs.exchangeBaseUrl &&
- lhs.currency == rhs.currency &&
- lhs.paytoUris == rhs.paytoUris
- }
-
- public func hash(into hasher: inout Hasher) {
- hasher.combine(exchangeBaseUrl)
- hasher.combine(currency)
- hasher.combine(paytoUris)
- }
-}
/// A request to get an exchange's withdrawal details.
fileprivate struct GetWithdrawalDetailsForURI: WalletBackendFormattedRequest {
typealias Response = WithdrawUriInfoResponse
- func operation() -> String { return "getWithdrawalDetailsForUri" }
- func args() -> Args { return Args(talerWithdrawUri: talerWithdrawUri) }
+ func operation() -> String { "getWithdrawalDetailsForUri" }
+ func args() -> Args { Args(talerWithdrawUri: talerWithdrawUri) }
var talerWithdrawUri: String
struct Args: Encodable {
@@ -55,8 +38,8 @@ struct WithdrawalAmountDetails: Decodable {
/// A request to get an exchange's withdrawal details.
fileprivate struct GetWithdrawalDetailsForAmount:
WalletBackendFormattedRequest {
typealias Response = WithdrawalAmountDetails
- func operation() -> String { return "getWithdrawalDetailsForAmount" }
- func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl,
amount: amount) }
+ func operation() -> String { "getWithdrawalDetailsForAmount" }
+ func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl, amount:
amount) }
var exchangeBaseUrl: String
var amount: Amount
@@ -86,8 +69,8 @@ struct ExchangeTermsOfService: Decodable {
/// A request to query an exchange's terms of service.
fileprivate struct GetExchangeTermsOfService: WalletBackendFormattedRequest {
typealias Response = ExchangeTermsOfService
- func operation() -> String { return "getExchangeTos" }
- func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl,
acceptedFormat: acceptedFormat) }
+ func operation() -> String { "getExchangeTos" }
+ func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl,
acceptedFormat: acceptedFormat) }
var exchangeBaseUrl: String
var acceptedFormat: [String]?
@@ -99,8 +82,8 @@ fileprivate struct GetExchangeTermsOfService:
WalletBackendFormattedRequest {
/// A request to mark an exchange's terms of service as accepted.
fileprivate struct SetExchangeTOSAccepted: WalletBackendFormattedRequest {
struct Response: Decodable {} // no result - getting no error back means
success
- func operation() -> String { return "setExchangeTosAccepted" }
- func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, etag:
etag) }
+ func operation() -> String { "setExchangeTosAccepted" }
+ func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl, etag: etag) }
var exchangeBaseUrl: String
var etag: String
@@ -119,8 +102,8 @@ struct AcceptWithdrawalResponse: Decodable {
/// A request to accept a bank-integrated withdrawl.
fileprivate struct AcceptBankIntegratedWithdrawal:
WalletBackendFormattedRequest {
typealias Response = AcceptWithdrawalResponse
- func operation() -> String { return "acceptBankIntegratedWithdrawal" }
- func args() -> Args { return Args(talerWithdrawUri: talerWithdrawUri,
exchangeBaseUrl: exchangeBaseUrl) }
+ func operation() -> String { "acceptBankIntegratedWithdrawal" }
+ func args() -> Args { Args(talerWithdrawUri: talerWithdrawUri,
exchangeBaseUrl: exchangeBaseUrl) }
var talerWithdrawUri: String
var exchangeBaseUrl: String
@@ -139,8 +122,8 @@ struct AcceptManualWithdrawalResult: Decodable {
/// A request to accept a manual withdrawl.
fileprivate struct AcceptManualWithdrawal: WalletBackendFormattedRequest {
typealias Response = AcceptManualWithdrawalResult
- func operation() -> String { return "acceptManualWithdrawal" }
- func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl,
amount: amount, restrictAge: restrictAge) }
+ func operation() -> String { "acceptManualWithdrawal" }
+ func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl, amount:
amount, restrictAge: restrictAge) }
var exchangeBaseUrl: String
var amount: Amount
diff --git a/TalerWallet1/Model/WalletModel.swift
b/TalerWallet1/Model/WalletModel.swift
index 6a24d8e..1bc239f 100644
--- a/TalerWallet1/Model/WalletModel.swift
+++ b/TalerWallet1/Model/WalletModel.swift
@@ -3,12 +3,22 @@
* See LICENSE.md
*/
import Foundation
+import taler_swift
import SymLog
import os.log
fileprivate let DATABASE = "talerwalletdb-v30"
fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for
debugging
+struct HTTPError: Codable, Hashable {
+ var code: Int
+ var requestUrl: String?
+ var hint: String
+ var requestMethod: String?
+ var httpStatusCode: Int?
+ var when: Timestamp?
+ var stack: String?
+}
// MARK: -
/// The "virtual" base class for all models
class WalletModel: ObservableObject {
@@ -58,8 +68,8 @@ class WalletModel: ObservableObject {
/// A request to get a wallet transaction by ID.
fileprivate struct GetTransactionById: WalletBackendFormattedRequest {
typealias Response = Transaction
- func operation() -> String { return "getTransactionById" }
- func args() -> Args { return Args(transactionId: transactionId) }
+ func operation() -> String { "getTransactionById" }
+ func args() -> Args { Args(transactionId: transactionId) }
var transactionId: String
@@ -80,7 +90,7 @@ struct VersionInfo: Decodable {
// MARK: -
/// A request to initialize Wallet-core
fileprivate struct InitRequest: WalletBackendFormattedRequest {
- func operation() -> String { return "init" }
+ func operation() -> String { "init" }
func args() -> Args {
return Args(persistentStoragePath: persistentStoragePath,
// cryptoWorkerType: "sync",
@@ -148,8 +158,8 @@ extension WalletModel {
// MARK: -
/// A request to initialize Wallet-core
fileprivate struct ResetRequest: WalletBackendFormattedRequest {
- func operation() -> String { return "reset" }
- func args() -> Args { return Args() }
+ func operation() -> String { "reset" }
+ func args() -> Args { Args() }
struct Args: Encodable {} // no arguments needed
struct Response: Decodable {}
diff --git a/TalerWallet1/Views/Balances/BalanceRowView.swift
b/TalerWallet1/Views/Balances/BalanceRowView.swift
index 6a35992..fcff95e 100644
--- a/TalerWallet1/Views/Balances/BalanceRowView.swift
+++ b/TalerWallet1/Views/Balances/BalanceRowView.swift
@@ -74,6 +74,7 @@ struct BalanceButton: View {
/// [Send Coins] [Receive Coins] Balance
struct BalanceRowView: View {
let amount: Amount
+ let currencyInfo: CurrencyInfo?
let sendAction: () -> Void
let recvAction: () -> Void
let rowAction: () -> Void
@@ -86,9 +87,11 @@ struct BalanceRowView: View {
let requestTitle2 = String(localized: "Payment", comment: "Bottom of
button <Request Payment>")
var body: some View {
+
SingleAxisGeometryReader { width in
VStack (alignment: .trailing) {
- BalanceButton(amountStr: amount.valueStr, // TODO:
CurrencyFormatter?
+ let amountStr = currencyInfo?.string(for: amount.value)
+ BalanceButton(amountStr: amountStr ?? amount.valueStr,
rowAction: rowAction)
let uiFont = TalerFont.uiFont(.title3)
let titles = [(requestTitle1, uiFont), (requestTitle2,
uiFont), (sendTitle1, uiFont), (sendTitle2, uiFont)]
@@ -109,7 +112,7 @@ struct BalanceRowView: View {
}
}
// MARK: -
-#if DEBUG
+#if false // DEBUG
struct BalanceRowView_Previews: PreviewProvider {
static var previews: some View {
let test = try! Amount(fromString: TESTCURRENCY + ":1.23")
diff --git a/TalerWallet1/Views/Balances/BalancesListView.swift
b/TalerWallet1/Views/Balances/BalancesListView.swift
index 2ab8f93..d8401e2 100644
--- a/TalerWallet1/Views/Balances/BalancesListView.swift
+++ b/TalerWallet1/Views/Balances/BalancesListView.swift
@@ -146,7 +146,7 @@ extension BalancesListView {
WalletEmptyView(stack: stack.push("isEmpty"))
} else {
List(balances, id: \.self) { balance in
- BalancesSectionView(stack:
stack.push("\(balance.available.currencyStr)"),
+ BalancesSectionView(stack:
stack.push("\(balance.scopeInfo.currency)"),
balance: balance,
sectionCount: count,
centsToTransfer: $centsToTransfer,
diff --git a/TalerWallet1/Views/Balances/BalancesSectionView.swift
b/TalerWallet1/Views/Balances/BalancesSectionView.swift
index dfc5996..80e446d 100644
--- a/TalerWallet1/Views/Balances/BalancesSectionView.swift
+++ b/TalerWallet1/Views/Balances/BalancesSectionView.swift
@@ -23,6 +23,7 @@ struct BalancesSectionView {
@Binding var summary: String
@EnvironmentObject private var model: WalletModel
+ @EnvironmentObject private var controller: Controller
@State private var isShowingDetailView = false
@@ -39,21 +40,21 @@ struct BalancesSectionView {
@State private var shownSectionID = UUID() // guaranteed to be different
the first time
func reloadCompleted(_ stack: CallStack) async -> () {
- let currency = balance.available.currencyStr
+ let currency = balance.scopeInfo.currency
transactions = await model.transactionsT(stack.push(), currency:
currency)
completedTransactions = WalletModel.completedTransactions(transactions)
// sectionID = UUID()
}
func reloadPending(_ stack: CallStack) async -> () {
- let currency = balance.available.currencyStr
+ let currency = balance.scopeInfo.currency
transactions = await model.transactionsT(stack.push(), currency:
currency)
pendingTransactions = WalletModel.pendingTransactions(transactions)
// sectionID = UUID()
}
func reloadUncompleted(_ stack: CallStack) async -> () {
- let currency = balance.available.currencyStr
+ let currency = balance.scopeInfo.currency
transactions = await model.transactionsT(stack.push(), currency:
currency)
uncompletedTransactions =
WalletModel.uncompletedTransactions(transactions)
// sectionID = UUID()
@@ -66,7 +67,8 @@ extension BalancesSectionView: View {
let _ = Self._printChanges()
let _ = symLog.vlog() // just to get the # to compare it with
.onAppear & onDisappear
#endif
- let currency = balance.available.currencyStr
+ let currency = balance.scopeInfo.currency
+ let currencyInfo = controller.info(for: currency)
Section {
if "KUDOS" == currency && !balance.available.isZero {
@@ -76,6 +78,7 @@ extension BalancesSectionView: View {
}
BalancesNavigationLinksView(stack: stack.push(),
balance: balance,
+ currencyInfo: currencyInfo,
centsToTransfer: $centsToTransfer,
summary: $summary,
// buttonSelected: $buttonSelected,
@@ -221,6 +224,7 @@ fileprivate struct BalancesPendingRowView: View {
fileprivate struct BalancesNavigationLinksView: View {
let stack: CallStack
let balance: Balance
+ let currencyInfo: CurrencyInfo?
// let sectionCount: Int
@Binding var centsToTransfer: UInt64
@Binding var summary: String
@@ -244,7 +248,7 @@ fileprivate struct BalancesNavigationLinksView: View {
}
var body: some View {
- let currency = balance.available.currencyStr
+ let currency = balance.scopeInfo.currency
HStack(spacing: 0) {
NavigationLink(destination: LazyView {
SendAmount(stack: stack.push(),
@@ -273,13 +277,14 @@ fileprivate struct BalancesNavigationLinksView: View {
}, tag: 3, selection: $buttonSelected
) { EmptyView() }.frame(width: 0).opacity(0).hidden() //
TransactionsListView
- BalanceRowView(amount: balance.available, sendAction: {
- selectAndUpdate(1) // will trigger SendAmount
NavigationLink
- }, recvAction: {
- selectAndUpdate(2) // will trigger RequestPayment
NavigationLink
- }, rowAction: {
- buttonSelected = 3 // will trigger TransactionList
NavigationLink
- })
+ BalanceRowView(amount: balance.available, currencyInfo:
currencyInfo,
+ sendAction: {
+ selectAndUpdate(1) // will trigger SendAmount
NavigationLink
+ }, recvAction: {
+ selectAndUpdate(2) // will trigger RequestPayment
NavigationLink
+ }, rowAction: {
+ buttonSelected = 3 // will trigger TransactionList
NavigationLink
+ })
}
}
}
diff --git a/TalerWallet1/Views/Main/MainView.swift
b/TalerWallet1/Views/Main/MainView.swift
index 7d70698..a0abc93 100644
--- a/TalerWallet1/Views/Main/MainView.swift
+++ b/TalerWallet1/Views/Main/MainView.swift
@@ -79,6 +79,8 @@ extension MainView {
@State private var balances: [Balance] = []
@Binding var talerFont: Int
@AppStorage("iconOnly") var iconOnly: Bool = false
+ @EnvironmentObject private var controller: Controller
+ @EnvironmentObject private var model: WalletModel
let balancesTitle = String(localized: "Balances")
let exchangesTitle = String(localized: "Exchanges")
let settingsTitle = String(localized: "Settings")
@@ -213,6 +215,23 @@ extension MainView {
symLog?.log(".onNotification(.BalanceChange) ==> reload")
shouldReloadBalances += 1
}
+ .onChange(of: balances) { newArray in
+ for balance in newArray {
+ let scope = balance.scopeInfo
+ if controller.info(for: scope.currency) == nil {
+ Task { // runs on MainActor
+ symLog?.log("get info for: \(scope.currency)")
+ do {
+ let info = try await
model.getCurrencyInfo(scope: scope)
+ symLog?.log("added: \(scope.currency)")
+ await controller.setInfo(info)
+ } catch { // TODO: error handling - couldn't
get CurrencyInfo
+ symLog?.log("error: \(error)")
+ }
+ }
+ }
+ }
+ }
} // body
} // Content
}
diff --git a/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
b/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
index 339a8f6..a203c4f 100644
--- a/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
+++ b/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift
@@ -19,12 +19,14 @@ struct PaymentPurpose: View {
@FocusState private var isFocused: Bool
- let formatter = CurrencyFormatter.shared // TODO: based on currency
+// let formatter = CurrencyFormatter.shared // TODO: based on currency
// let buttonFont: Font = .talerTitle2
private var label: String {
- let mag = pow(10, formatter.maximumFractionDigits)
- return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
+// let mag = pow(10, formatter.maximumFractionDigits)
+// return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
+
+ return String(centsToTransfer / 100)
}
var body: some View {
diff --git a/TalerWallet1/Views/Peer2peer/SendPurpose.swift
b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
index b0c4673..3bbb947 100644
--- a/TalerWallet1/Views/Peer2peer/SendPurpose.swift
+++ b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
@@ -18,11 +18,13 @@ struct SendPurpose: View {
@Binding var expireDays: UInt
// var deactivateAction: () -> Void
- let formatter = CurrencyFormatter.shared // TODO: based on currency
+// let formatter = CurrencyFormatter.shared // TODO: based on currency
private var value: String {
- let mag = pow(10, formatter.maximumFractionDigits)
- return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
+// let mag = pow(10, formatter.maximumFractionDigits)
+// return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
+
+ return String(centsToTransfer / 100)
}
var body: some View {
diff --git a/TalerWallet1/Views/Settings/AboutView.swift
b/TalerWallet1/Views/Settings/AboutView.swift
new file mode 100644
index 0000000..e6de724
--- /dev/null
+++ b/TalerWallet1/Views/Settings/AboutView.swift
@@ -0,0 +1,111 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct AboutView: View {
+ private let symLog = SymLogV(0)
+ let stack: CallStack
+ let navTitle: String
+
+ @EnvironmentObject private var controller: Controller
+#if DEBUG
+ @AppStorage("developerMode") var developerMode: Bool = true
+#else
+ @AppStorage("developerMode") var developerMode: Bool = false
+#endif
+ @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
+
+ @State private var rotationEnabled = false
+
+ @State private var listID = UUID()
+
+ var body: some View {
+#if DEBUG
+ let _ = Self._printChanges()
+ let _ = symLog.vlog() // just to get the # to compare it with
.onAppear & onDisappear
+#endif
+ let walletCore = WalletCore.shared
+ Group {
+ List {
+// VStack {
+ HStack {
+ Spacer()
+ RotatingTaler(size: 100, rotationEnabled:
$rotationEnabled)
+ .onTapGesture(count: 2) {
+ rotationEnabled.toggle()
+ }
+ .onLongPressGesture(minimumDuration: 0.3) {
+
UIApplication.shared.open(URL(string:"https://taler.net")!, options: [:])
+ }
+ Spacer()
+ }
+ SettingsItem(name: "App Version", id1: "app") {
+ Text(verbatim:
"\(Bundle.main.releaseVersionNumberPretty)")
+ }
+ SettingsItem(name: "Wallet Core Version", id1:
"wallet-core") {
+ Text(verbatim: "\(walletCore.versionInfo!.version)")
+ }
+ SettingsItem(name: "Wallet Core DevMode", id1: "devMode") {
+ Text(verbatim: "\(walletCore.versionInfo!.devMode ?
"YES" : "NO")")
+ }
+ SettingsItem(name: "Supported Exchange Versions", id1:
"exchange") {
+ Text(verbatim: "\(walletCore.versionInfo!.exchange)")
+ }
+ SettingsItem(name: "Supported Merchant Versions", id1:
"merchant") {
+ Text(verbatim: "\(walletCore.versionInfo!.merchant)")
+ }
+ SettingsItem(name: "Used Bank", id1: "bank") {
+ Text(verbatim: "\(walletCore.versionInfo!.bank)")
+ }
+// } // App version info
+ }
+ .id(listID)
+ .listStyle(myListStyle.style).anyView
+ }
+ .navigationTitle(navTitle)
+ .onAppear() {
+ DebugViewC.shared.setViewID(VIEW_ABOUT, stack: stack.push())
+ }
+ .onDisappear() {
+ }
+ .task {
+ try? await Task.sleep(nanoseconds: 1_000_000_000 * UInt64(5))
+ rotationEnabled.toggle()
+ }
+ }
+}
+extension Bundle {
+ var releaseVersionNumber: String? {
+ return infoDictionary?["CFBundleShortVersionString"] as? String
+ }
+ var buildVersionNumber: String? {
+ return infoDictionary?["CFBundleVersion"] as? String
+ }
+ var releaseVersionNumberPretty: String {
+ return "v\(releaseVersionNumber ?? "1.0.0")"
+ }
+}
+// MARK: -
+#if DEBUG
+struct AboutView_Previews: PreviewProvider {
+ static var previews: some View {
+#if TABBAR // Taler Wallet
+ AboutView(stack: CallStack("Preview"), navTitle: "About")
+#else // GNU Taler
+ SettingsView(stack: CallStack("Preview"), navTitle: "About") { }
+#endif
+ }
+}
+#endif
diff --git a/TalerWallet1/Views/Settings/SettingsItem.swift
b/TalerWallet1/Views/Settings/SettingsItem.swift
index ae61cf2..76220e0 100644
--- a/TalerWallet1/Views/Settings/SettingsItem.swift
+++ b/TalerWallet1/Views/Settings/SettingsItem.swift
@@ -6,11 +6,13 @@ import SwiftUI
struct SettingsItem<Content: View>: View {
var name: String
+ var id1: String? = nil
var description: String?
var content: () -> Content
- init(name: String, description: String? = nil, @ViewBuilder content:
@escaping () -> Content) {
+ init(name: String, id1: String, description: String? = nil, @ViewBuilder
content: @escaping () -> Content) {
self.name = name
+ self.id1 = id1
self.description = description
self.content = content
}
@@ -19,30 +21,35 @@ struct SettingsItem<Content: View>: View {
HStack {
VStack {
Text(name)
+ .id(id1)
.frame(maxWidth: .infinity, alignment: .leading)
.accessibilityFont(.title2)
.padding([.bottom], 0.01)
if let desc = description {
Text(desc)
+ .id(id1 == nil ? nil : id1! + "_T")
.frame(maxWidth: .infinity, alignment: .leading)
.accessibilityFont(.caption)
}
- }
+ }.id(id1 == nil ? nil : id1! + "_V")
content()
.accessibilityFont(.body)
- }.padding([.bottom], 4)
+ }.id(id1 == nil ? nil : id1! + "_H")
+ .padding([.bottom], 4)
}
}
// MARK: -
struct SettingsToggle: View {
var name: String
@Binding var value: Bool
+ var id1: String? = nil
var description: String?
var action: () -> Void = {}
var body: some View {
VStack {
Toggle(name, isOn: $value.animation())
+ .id(id1)
.accessibilityFont(.title2)
.onChange(of: value) { value in
action()
@@ -50,10 +57,13 @@ struct SettingsToggle: View {
if let desc = description {
Text(desc)
+ .id(id1 == nil ? nil : id1! + "_T")
.frame(maxWidth: .infinity, alignment: .leading)
.accessibilityFont(.caption)
}
- }.padding([.bottom], 4)
+ }
+ .padding([.bottom], 4)
+ .id(id1 == nil ? nil : id1! + "_V")
}
}
// MARK: -
@@ -157,7 +167,7 @@ struct SettingsItemPreview : View {
var body: some View {
VStack {
- SettingsToggle(name: "Developer Mode", value: $developerMode,
+ SettingsToggle(name: "Developer Mode", value: $developerMode, id1:
"dev1",
description: "More information intended for debugging")
SettingsSpeaker(name: String(localized: "Play Payment Sounds"),
value: $playSounds,
description: String(localized: "After a transaction
finished"))
@@ -169,9 +179,9 @@ struct SettingsItemPreview : View {
struct SettingsItem_Previews: PreviewProvider {
static var previews: some View {
List {
- SettingsItem (name: "Exchanges", description: "Manage list of
exchanges known to this wallet") {}
+ SettingsItem (name: "Exchanges", id1: "list", description: "Manage
list of exchanges known to this wallet") {}
SettingsItemPreview()
- SettingsItem(name: "Save Logfile", description: "Help debugging
wallet-core") {
+ SettingsItem(name: "Save Logfile", id1: "save", description: "Help
debugging wallet-core") {
Button("Save") {
}
.buttonStyle(.bordered)
diff --git a/TalerWallet1/Views/Settings/SettingsView.swift
b/TalerWallet1/Views/Settings/SettingsView.swift
index 3bf5b69..af94378 100644
--- a/TalerWallet1/Views/Settings/SettingsView.swift
+++ b/TalerWallet1/Views/Settings/SettingsView.swift
@@ -50,6 +50,7 @@ struct SettingsView: View {
@State private var diagnosticModeEnabled =
UserDefaults.standard.bool(forKey: "diagnostic_mode_enabled")
#endif
@State private var showDevelopItems = false
+ @State private var hideDescriptions = false
@State private var showResetAlert: Bool = false
@State private var didReset: Bool = false
@@ -93,35 +94,44 @@ struct SettingsView: View {
let walletCore = WalletCore.shared
Group {
List {
+ let aboutStr = String(localized: "About GNU Taler")
+ NavigationLink { // whole row like in a tableView
+ LazyView { AboutView(stack: stack.push(), navTitle:
aboutStr) }
+ } label: {
+ SettingsItem(name: aboutStr, id1: "about",
+ description: hideDescriptions ? nil :
String(localized: "More info about this app...")) {}
+ }
if controller.hapticCapability.supportsHaptics {
- SettingsToggle(name: String(localized: "Haptics"), value:
$useHaptics,
- description: String(localized: "Vibration
Feedback"))
+ SettingsToggle(name: String(localized: "Haptics"), value:
$useHaptics, id1: "haptics",
+ description: hideDescriptions ? nil :
String(localized: "Vibration Feedback"))
}
SettingsSpeaker(name: String(localized: "Play Payment
Sounds"), value: $playSounds,
- description: String(localized: "When a
transaction finished"))
+ description: hideDescriptions ? nil :
String(localized: "When a transaction finished"))
+ .id("playSounds")
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"))
+ SettingsToggle(name: String(localized: "Minimalistic"), value:
$iconOnly, id1: "minimal",
+ description: hideDescriptions ? nil :
String(localized: "Omit text where possible")) {
+ hideDescriptions = iconOnly //withAnimation {
hideDescriptions = iconOnly }
+ }
if diagnosticModeEnabled {
- SettingsToggle(name: String(localized: "Developer Mode"),
value: $developerMode,
- description: String(localized: "More information
intended for debugging")) {
- DebugViewC.shared.setViewID(VIEW_SETTINGS, stack:
stack.push())
+ SettingsToggle(name: String(localized: "Developer Mode"),
value: $developerMode, id1: "devMode",
+ description: hideDescriptions ? nil :
String(localized: "More information intended for debugging")) {
withAnimation { showDevelopItems = developerMode }
}
if showDevelopItems { // show or hide the following items
NavigationLink { // whole row like in a
tableView
LazyView { PendingOpsListView(stack: stack.push())
}
} label: {
- SettingsItem(name: String(localized: "Pending
Operations"),
- description: String(localized: "Exchange not
yet ready...")) {}
- }
+ SettingsItem(name: String(localized: "Pending
Operations"), id1: "pending",
+ description: hideDescriptions ? nil :
String(localized: "Exchange not yet ready...")) {}
+ }.id("pending_L")
SettingsToggle(name: String(localized: "Set 2 seconds
delay"),
value: $developDelay.onChange({ delay in
- walletCore.developDelay = delay}),
- description: String(localized: "After each
wallet-core action"))
- SettingsItem(name: String(localized: "Withdraw
\(DEMOCURRENCY)"),
- description: String(localized: "Get money for
testing")) {
+ walletCore.developDelay = delay}),
id1: "delay",
+ description: hideDescriptions ? nil :
String(localized: "After each wallet-core action"))
+ SettingsItem(name: String(localized: "Withdraw
\(DEMOCURRENCY)"), id1: "demo1with",
+ description: hideDescriptions ? nil :
String(localized: "Get money for testing")) {
Button("Withdraw") {
withDrawDisabled = true // don't run twice
Task { // runs on MainActor
@@ -135,9 +145,9 @@ struct SettingsView: View {
}
.buttonStyle(.bordered)
.disabled(withDrawDisabled)
- }
- SettingsItem(name: String(localized: "Withdraw
\(TESTCURRENCY)"),
- description: String(localized: "Get money for
testing")) {
+ }.id("demoWithdraw")
+ SettingsItem(name: String(localized: "Withdraw
\(TESTCURRENCY)"), id1: "test1with",
+ description: hideDescriptions ? nil :
String(localized: "Get money for testing")) {
Button("Withdraw") {
withDrawDisabled = true // don't run twice
Task { // runs on MainActor
@@ -151,9 +161,9 @@ struct SettingsView: View {
}
.buttonStyle(.bordered)
.disabled(withDrawDisabled)
- }
- SettingsItem(name: String(localized: "Run Integration
Test"),
- description: String(localized: "Perform basic
test transactions")) {
+ }.id("testWithdraw")
+ SettingsItem(name: String(localized: "Run Integration
Test"), id1: "demo1test",
+ description: hideDescriptions ? nil :
String(localized: "Perform basic test transactions")) {
Button("Demo 1") {
checkDisabled = true // don't run twice
Task { // runs on MainActor
@@ -168,8 +178,8 @@ struct SettingsView: View {
.buttonStyle(.bordered)
.disabled(checkDisabled)
}
- SettingsItem(name: String(localized: "Run Integration
Test"),
- description: String(localized: "Perform basic
test transactions")) {
+ SettingsItem(name: String(localized: "Run Integration
Test"), id1: "test1test",
+ description: hideDescriptions ? nil :
String(localized: "Perform basic test transactions")) {
Button("Test 1") {
checkDisabled = true // don't run twice
Task { // runs on MainActor
@@ -184,8 +194,8 @@ struct SettingsView: View {
.buttonStyle(.bordered)
.disabled(checkDisabled)
}
- SettingsItem(name: String(localized: "Run Integration
Test V2"),
- description: String(localized: "Perform more
test transactions")) {
+ SettingsItem(name: String(localized: "Run Integration
Test V2"), id1: "demo2test",
+ description: hideDescriptions ? nil :
String(localized: "Perform more test transactions")) {
Button("Demo 2") {
checkDisabled = true // don't run twice
Task { // runs on MainActor
@@ -200,8 +210,8 @@ struct SettingsView: View {
.buttonStyle(.bordered)
.disabled(checkDisabled)
}
- SettingsItem(name: String(localized: "Run Integration
Test V2"),
- description: String(localized: "Perform more
test transactions")) {
+ SettingsItem(name: String(localized: "Run Integration
Test V2"), id1: "test2test",
+ description: hideDescriptions ? nil :
String(localized: "Perform more test transactions")) {
Button("Test 2") {
checkDisabled = true // don't run twice
Task { // runs on MainActor
@@ -216,8 +226,8 @@ struct SettingsView: View {
.buttonStyle(.bordered)
.disabled(checkDisabled)
}
- SettingsItem(name: String(localized: "Save Logfile"),
- description: String(localized: "Help debugging
wallet-core")) {
+ SettingsItem(name: String(localized: "Save Logfile"),
id1: "save",
+ description: hideDescriptions ? nil :
String(localized: "Help debugging wallet-core")) {
Button("Save") {
symLog.log("Saving Log")
// FIXME: Save Logfile
@@ -225,8 +235,8 @@ struct SettingsView: View {
.buttonStyle(.bordered)
.disabled(true)
}
- SettingsItem(name: String(localized: "Reset Wallet"),
- description: String(localized: "Throw away all
your money")) {
+ SettingsItem(name: String(localized: "Reset Wallet"),
id1: "reset",
+ description: hideDescriptions ? nil :
String(localized: "Throw away all your money")) {
Button("Reset") {
showResetAlert = true
}
@@ -235,27 +245,6 @@ struct SettingsView: View {
}
}
}
-
- VStack {
- SettingsItem(name: "App Version") {
- Text(verbatim:
"\(Bundle.main.releaseVersionNumberPretty)")
- }
- SettingsItem(name: "Wallet Core Version") {
- Text(verbatim: "\(walletCore.versionInfo!.version)")
- }
- SettingsItem(name: "Wallet Core DevMode") {
- Text(verbatim: "\(walletCore.versionInfo!.devMode ?
"YES" : "NO")")
- }
- SettingsItem(name: "Supported Exchange Versions") {
- Text(verbatim: "\(walletCore.versionInfo!.exchange)")
- }
- SettingsItem(name: "Supported Merchant Versions") {
- Text(verbatim: "\(walletCore.versionInfo!.merchant)")
- }
- SettingsItem(name: "Used Bank") {
- Text(verbatim: "\(walletCore.versionInfo!.bank)")
- }
- } // App version info
}
.id(listID)
.listStyle(myListStyle.style).anyView
@@ -264,6 +253,7 @@ struct SettingsView: View {
.navigationBarItems(leading: hamburger)
.onAppear() {
showDevelopItems = developerMode
+ hideDescriptions = iconOnly
DebugViewC.shared.setViewID(VIEW_SETTINGS, stack: stack.push())
}
.onDisappear() {
@@ -287,17 +277,6 @@ struct SettingsView: View {
#endif
}
}
-extension Bundle {
- var releaseVersionNumber: String? {
- return infoDictionary?["CFBundleShortVersionString"] as? String
- }
- var buildVersionNumber: String? {
- return infoDictionary?["CFBundleVersion"] as? String
- }
- var releaseVersionNumberPretty: String {
- return "v\(releaseVersionNumber ?? "1.0.0")"
- }
-}
// MARK: -
#if DEBUG
struct SettingsView_Previews: PreviewProvider {
diff --git a/taler-swift/Sources/taler-swift/Amount.swift
b/taler-swift/Sources/taler-swift/Amount.swift
index 088e237..ea952a8 100644
--- a/taler-swift/Sources/taler-swift/Amount.swift
+++ b/taler-swift/Sources/taler-swift/Amount.swift
@@ -38,34 +38,6 @@ enum AmountError: Error {
case divideByZero
}
-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
- /// 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.
public final class Amount: Codable, Hashable, @unchecked Sendable,
CustomStringConvertible { // TODO: @unchecked
@@ -90,9 +62,6 @@ public final class Amount: Codable, Hashable, @unchecked
Sendable, CustomStringC
/// The fractional value of the amount (number to the right of the decimal
point).
var fraction: UInt32
- /// Additional info for formatting currency strings
- var currencySpecification: CurrencySpecification?
-
public func hash(into hasher: inout Hasher) {
hasher.combine(currency)
if let normalized = try? normalizedCopy() {
@@ -118,9 +87,9 @@ 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 currencySpecification { // TODO: use locale
- decimalSeparator = currencySpecification.decimalSeparator
- }
+// if let currencySpecification { // TODO: use locale
+// decimalSeparator = currencySpecification.decimalSeparator
+// }
if fraction == 0 {
return "\(integer)"
} else {
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
- [taler-taler-ios] branch master updated (adfc919 -> a0c6bdb),
gnunet <=
- [taler-taler-ios] 10/11: Use CurrencyFormatter for Balance, gnunet, 2023/10/21
- [taler-taler-ios] 11/11: Minimalistic, gnunet, 2023/10/21
- [taler-taler-ios] 06/11: listExchangesForScopedCurrency, gnunet, 2023/10/21
- [taler-taler-ios] 04/11: cleanup, gnunet, 2023/10/21
- [taler-taler-ios] 01/11: CurrencySpecification, gnunet, 2023/10/21
- [taler-taler-ios] 03/11: ExchangeListItem, gnunet, 2023/10/21
- [taler-taler-ios] 05/11: getCurrencyInfo(scope), gnunet, 2023/10/21
- [taler-taler-ios] 02/11: HTTPError, gnunet, 2023/10/21
- [taler-taler-ios] 09/11: Call AboutView, gnunet, 2023/10/21
- [taler-taler-ios] 08/11: AboutView, gnunet, 2023/10/21